From 902747f3618c2ba285058670ee6d0cf57e51c34e Mon Sep 17 00:00:00 2001
From: matthew_swift <matthew_swift@localhost>
Date: Mon, 30 Nov 2009 16:27:13 +0000
Subject: [PATCH] Initial import of SDK source from data provider branch.
---
sdk/src/org/opends/sdk/AttributeDescription.java | 1496
sdk/src/org/opends/sdk/asn1/ASN1.java | 221
sdk/src/org/opends/sdk/schema/IntegerFirstComponentEqualityMatchingRuleImpl.java | 131
sdk/src/org/opends/sdk/tools/LDAPCompare.java | 674
sdk/src/org/opends/sdk/responses/IntermediateResponse.java | 116
sdk/src/org/opends/sdk/ResultFuture.java | 153
sdk/src/org/opends/sdk/asn1/ASN1ByteSequenceReader.java | 563
sdk/src/org/opends/sdk/schema/OctetStringSubstringMatchingRuleImpl.java | 49
sdk/src/org/opends/sdk/requests/GenericExtendedRequest.java | 188
sdk/src/org/opends/sdk/AbstractConnection.java | 315
sdk/src/org/opends/sdk/schema/UserPasswordSyntaxImpl.java | 202
sdk/src/org/opends/sdk/SynchronousConnection.java | 261
sdk/src/org/opends/sdk/responses/AbstractIntermediateResponse.java | 98
sdk/src/org/opends/sdk/schema/NameAndOptionalUIDSyntaxImpl.java | 152
sdk/src/org/opends/sdk/util/Predicate.java | 57
sdk/src/org/opends/sdk/schema/SchemaConstants.java | 1623
sdk/src/org/opends/sdk/schema/DirectoryStringSyntaxImpl.java | 125
sdk/src/org/opends/sdk/asn1/ASN1OutputStreamWriter.java | 560
sdk/src/org/opends/sdk/requests/AbstractBindRequest.java | 73
sdk/src/org/opends/sdk/schema/DistinguishedNameSyntaxImpl.java | 95
sdk/src/org/opends/sdk/requests/GenericExtendedRequestImpl.java | 192
sdk/src/org/opends/sdk/responses/GenericExtendedResult.java | 198
sdk/src/org/opends/sdk/tools/ArgumentParser.java | 2007 +
sdk/src/org/opends/sdk/extensions/CancelRequest.java | 171
sdk/src/org/opends/sdk/ldif/ChangeRecordReader.java | 86
sdk/src/org/opends/sdk/schema/Schema.java | 2531 +
sdk/src/org/opends/sdk/controls/PasswordExpiringControl.java | 175
sdk/src/org/opends/sdk/schema/TelephoneNumberEqualityMatchingRuleImpl.java | 67
sdk/src/org/opends/sdk/schema/UUIDEqualityMatchingRuleImpl.java | 135
sdk/src/org/opends/sdk/ldap/ExtendedResultFutureImpl.java | 90
sdk/src/org/opends/sdk/ldif/ChangeRecord.java | 71
sdk/src/org/opends/sdk/tools/ArgumentGroup.java | 209
sdk/src/org/opends/sdk/ldap/AbstractLDAPTransport.java | 244
sdk/src/org/opends/sdk/schema/RegexSyntaxImpl.java | 127
sdk/src/org/opends/sdk/extensions/PasswordPolicyStateExtendedOperation.java | 1077
sdk/src/org/opends/sdk/tools/Argument.java | 808
sdk/src/org/opends/sdk/util/SubstringReader.java | 84
sdk/src/org/opends/sdk/requests/UnbindRequest.java | 119
sdk/src/org/opends/sdk/ldif/EntryReader.java | 85
sdk/src/org/opends/sdk/util/Platform.java | 650
sdk/src/org/opends/sdk/schema/SubstringAssertionSyntaxImpl.java | 150
sdk/src/org/opends/sdk/schema/PostalAddressSyntaxImpl.java | 101
sdk/src/org/opends/sdk/controls/ServerSideSortControl.java | 549
sdk/src/org/opends/sdk/schema/MatchingRuleSyntaxImpl.java | 215
sdk/src/org/opends/sdk/schema/TelexNumberSyntaxImpl.java | 230
sdk/src/org/opends/sdk/Assertion.java | 53
sdk/src/org/opends/sdk/ldap/ResolvedSchema.java | 65
sdk/src/org/opends/sdk/sasl/GSSAPISASLBindRequest.java | 304
sdk/src/org/opends/sdk/util/ssl/TrustStoreTrustManager.java | 285
sdk/src/org/opends/sdk/controls/PasswordExpiredControl.java | 137
sdk/src/org/opends/sdk/sasl/PlainSASLBindRequest.java | 320
sdk/src/org/opends/sdk/controls/PreReadControl.java | 447
sdk/src/org/opends/sdk/requests/GenericBindRequestImpl.java | 182
sdk/src/org/opends/sdk/sasl/SASLContext.java | 77
sdk/src/org/opends/sdk/controls/ProxiedAuthV1Control.java | 212
sdk/src/org/opends/sdk/schema/PresentationAddressEqualityMatchingRuleImpl.java | 85
sdk/src/org/opends/sdk/requests/Requests.java | 699
sdk/src/org/opends/sdk/schema/AbstractSubstringMatchingRuleImpl.java | 270
sdk/src/org/opends/sdk/RootDSE.java | 463
sdk/src/org/opends/sdk/controls/ProxiedAuthV2Control.java | 216
sdk/src/org/opends/sdk/schema/AbstractMatchingRuleImpl.java | 132
sdk/src/org/opends/sdk/requests/AddRequest.java | 341
sdk/src/org/opends/sdk/Attribute.java | 563
sdk/src/org/opends/sdk/schema/SchemaElement.java | 185
sdk/src/org/opends/sdk/controls/PagedResultsControl.java | 258
sdk/src/org/opends/sdk/responses/ExtendedResult.java | 172
sdk/src/org/opends/sdk/schema/SyntaxImpl.java | 144
sdk/src/org/opends/sdk/tools/LDAPPasswordModify.java | 475
sdk/src/org/opends/sdk/controls/GenericControl.java | 157
sdk/src/org/opends/sdk/requests/AbstractUnmodifiableRequestImpl.java | 138
sdk/src/org/opends/sdk/sasl/TextInputCallbackHandler.java | 42
sdk/src/org/opends/sdk/requests/AbstractRequestImpl.java | 170
sdk/src/org/opends/sdk/tools/DataSource.java | 481
sdk/src/org/opends/sdk/util/ByteSequenceReader.java | 510
sdk/src/org/opends/sdk/AbstractEntry.java | 421
sdk/src/org/opends/sdk/ldif/AbstractLDIFReader.java | 891
sdk/src/org/opends/sdk/util/ByteString.java | 681
sdk/src/org/opends/sdk/schema/OctetStringSyntaxImpl.java | 99
sdk/src/org/opends/sdk/Entry.java | 603
sdk/src/org/opends/sdk/schema/CaseIgnoreListSubstringMatchingRuleImpl.java | 140
sdk/src/org/opends/sdk/ldif/EntryWriter.java | 110
sdk/src/org/opends/sdk/schema/AttributeUsage.java | 112
sdk/src/org/opends/sdk/extensions/GetSymmetricKeyRequest.java | 231
sdk/src/org/opends/sdk/SearchScope.java | 207
sdk/src/org/opends/sdk/responses/SearchResultEntry.java | 230
sdk/src/org/opends/sdk/schema/ObjectIdentifierEqualityMatchingRuleImpl.java | 188
sdk/src/org/opends/sdk/schema/TelephoneNumberSyntaxImpl.java | 203
sdk/src/org/opends/sdk/requests/CompareRequest.java | 288
sdk/src/org/opends/sdk/sasl/PasswordCallbackHandler.java | 42
sdk/src/org/opends/sdk/schema/DITStructureRuleSyntaxImpl.java | 205
sdk/src/org/opends/sdk/extensions/WhoAmIResult.java | 113
sdk/src/org/opends/sdk/schema/SchemaLocal.java | 130
sdk/src/org/opends/sdk/asn1/ASN1Reader.java | 448
sdk/src/org/opends/sdk/extensions/GetConnectionIDResult.java | 122
sdk/src/org/opends/sdk/ldif/ChangeRecordWriter.java | 185
sdk/src/org/opends/sdk/schema/CaseExactIA5SubstringMatchingRuleImpl.java | 117
sdk/src/org/opends/sdk/ldap/SASLStreamReader.java | 118
sdk/src/org/opends/sdk/controls/SortResult.java | 113
sdk/src/org/opends/sdk/extensions/PasswordModifyResult.java | 119
sdk/src/org/opends/sdk/Types.java | 985
sdk/src/org/opends/sdk/AsynchronousConnection.java | 495
sdk/src/org/opends/sdk/responses/AbstractUnmodifiableResultImpl.java | 167
sdk/src/org/opends/sdk/schema/CaseIgnoreSubstringMatchingRuleImpl.java | 101
sdk/src/org/opends/sdk/util/StaticUtils.java | 1998 +
sdk/src/org/opends/sdk/responses/SearchResultEntryImpl.java | 377
sdk/src/org/opends/sdk/controls/PersistentSearchControl.java | 328
sdk/src/org/opends/sdk/sasl/AnonymousSASLBindRequest.java | 161
sdk/src/org/opends/sdk/responses/GenericIntermediateResponseImpl.java | 133
sdk/src/org/opends/sdk/responses/SearchResultReference.java | 151
sdk/src/org/opends/sdk/schema/EnumOrderingMatchingRule.java | 73
sdk/src/org/opends/sdk/ErrorResultException.java | 103
sdk/src/org/opends/sdk/schema/MatchingRuleImpl.java | 160
sdk/src/org/opends/sdk/util/Iterators.java | 549
sdk/src/org/opends/sdk/schema/DistinguishedNameEqualityMatchingRuleImpl.java | 213
sdk/src/org/opends/sdk/sasl/NameCallbackHandler.java | 42
sdk/src/org/opends/sdk/requests/ModifyRequest.java | 280
sdk/src/org/opends/sdk/ldap/LDAPConnectionFactory.java | 129
sdk/src/org/opends/sdk/responses/BindResultImpl.java | 126
sdk/src/org/opends/sdk/schema/UUIDSyntaxImpl.java | 150
sdk/src/org/opends/sdk/LinkedAttribute.java | 1063
sdk/src/org/opends/sdk/schema/GenerateCoreSchema.java | 415
sdk/src/org/opends/sdk/controls/PersistentSearchChangeType.java | 108
sdk/src/org/opends/sdk/SortedEntry.java | 295
sdk/src/org/opends/sdk/schema/AuthPasswordExactEqualityMatchingRuleImpl.java | 62
sdk/src/org/opends/sdk/schema/IntegerOrderingMatchingRuleImpl.java | 66
sdk/src/org/opends/sdk/schema/UTCTimeSyntaxImpl.java | 767
sdk/src/org/opends/sdk/util/Function.java | 58
sdk/src/org/opends/sdk/Filter.java | 1978 +
sdk/src/org/opends/sdk/schema/CaseExactSubstringMatchingRuleImpl.java | 100
sdk/src/org/opends/sdk/schema/AuthPasswordSyntaxImpl.java | 340
sdk/src/org/opends/sdk/responses/AbstractUnmodifiableResponseImpl.java | 137
sdk/src/org/opends/sdk/tools/ArgumentException.java | 96
sdk/src/org/opends/sdk/schema/IA5StringSyntaxImpl.java | 131
sdk/src/org/opends/sdk/schema/NumericStringSubstringMatchingRuleImpl.java | 59
sdk/src/org/opends/sdk/schema/UUIDOrderingMatchingRuleImpl.java | 135
sdk/src/org/opends/sdk/util/ssl/PromptingTrustManager.java | 412
sdk/src/org/opends/sdk/util/SSLUtils.java | 47
sdk/src/org/opends/sdk/tools/ArgumentParserConnectionFactory.java | 940
sdk/src/org/opends/sdk/schema/PresentationAddressSyntaxImpl.java | 113
sdk/src/org/opends/sdk/ldif/ChangeRecordVisitorWriter.java | 106
sdk/src/org/opends/sdk/tools/Utils.java | 476
sdk/src/org/opends/sdk/controls/AuthorizationIdentityControl.java | 295
sdk/src/org/opends/sdk/schema/DirectoryStringFirstComponentEqualityMatchingRuleImpl.java | 140
sdk/src/org/opends/sdk/schema/CaseIgnoreIA5SubstringMatchingRuleImpl.java | 115
sdk/src/org/opends/sdk/ConnectionEventListener.java | 109
sdk/src/org/opends/sdk/schema/CountryStringSyntaxImpl.java | 136
sdk/src/org/opends/sdk/schema/KeywordEqualityMatchingRuleImpl.java | 184
sdk/src/org/opends/sdk/schema/SchemaNotFoundException.java | 93
sdk/src/org/opends/sdk/schema/Syntax.java | 439
sdk/src/org/opends/sdk/ldap/LDAPConnectionOptions.java | 210
sdk/src/org/opends/sdk/schema/BinarySyntaxImpl.java | 98
sdk/src/org/opends/sdk/schema/CaseExactEqualityMatchingRuleImpl.java | 83
sdk/src/org/opends/sdk/requests/GenericBindRequest.java | 243
sdk/src/org/opends/sdk/extensions/PasswordModifyRequest.java | 278
sdk/src/org/opends/sdk/schema/CoreSchemaImpl.java | 1146
sdk/src/org/opends/sdk/requests/Request.java | 124
sdk/src/org/opends/sdk/tools/LDAPSearch.java | 1241
sdk/src/org/opends/sdk/tools/MultiChoiceArgument.java | 273
sdk/src/org/opends/sdk/controls/VLVTarget.java | 191
sdk/src/org/opends/sdk/responses/Result.java | 279
sdk/src/org/opends/sdk/schema/BitStringEqualityMatchingRuleImpl.java | 89
sdk/src/org/opends/sdk/requests/DeleteRequestImpl.java | 133
sdk/src/org/opends/sdk/ldap/UnexpectedRequestException.java | 71
sdk/src/org/opends/sdk/schema/CaseIgnoreIA5EqualityMatchingRuleImpl.java | 98
sdk/src/org/opends/sdk/schema/SupportedAlgorithmSyntaxImpl.java | 108
sdk/src/org/opends/sdk/responses/CompareResult.java | 166
sdk/src/org/opends/sdk/schema/BitStringSyntaxImpl.java | 127
sdk/src/org/opends/sdk/controls/ControlDecoder.java | 72
sdk/src/org/opends/sdk/ldap/LDAPUtils.java | 738
sdk/src/org/opends/sdk/schema/UniqueMemberEqualityMatchingRuleImpl.java | 50
sdk/src/org/opends/sdk/schema/FaxSyntaxImpl.java | 100
sdk/src/org/opends/sdk/sasl/SASLBindRequest.java | 66
sdk/src/org/opends/sdk/tools/FileBasedArgument.java | 283
sdk/src/org/opends/sdk/ldif/ConnectionEntryWriter.java | 156
sdk/src/org/opends/sdk/util/Functions.java | 345
sdk/src/org/opends/sdk/ldap/BindResultFutureImpl.java | 98
sdk/src/org/opends/sdk/schema/NumericStringSyntaxImpl.java | 137
sdk/src/org/opends/sdk/requests/SearchRequestImpl.java | 416
sdk/src/org/opends/sdk/requests/AbstractExtendedRequest.java | 117
sdk/src/org/opends/sdk/requests/UnbindRequestImpl.java | 67
sdk/src/org/opends/sdk/schema/BooleanSyntaxImpl.java | 105
sdk/src/org/opends/sdk/schema/NumericStringEqualityMatchingRuleImpl.java | 60
sdk/src/org/opends/sdk/ResultHandler.java | 81
sdk/src/org/opends/sdk/controls/PostReadControl.java | 447
sdk/src/org/opends/sdk/ldif/ConnectionChangeRecordWriter.java | 323
sdk/src/org/opends/sdk/responses/GenericExtendedResultImpl.java | 144
sdk/src/org/opends/sdk/tools/BooleanArgument.java | 125
sdk/src/org/opends/sdk/responses/CompareResultImpl.java | 97
sdk/src/org/opends/sdk/schema/DeliveryMethodSyntaxImpl.java | 172
sdk/src/org/opends/sdk/schema/OctetStringEqualityMatchingRuleImpl.java | 49
sdk/src/org/opends/sdk/extensions/GetConnectionIDRequest.java | 140
sdk/src/org/opends/sdk/util/Validator.java | 215
sdk/src/org/opends/sdk/controls/PasswordPolicyControl.java | 412
sdk/src/org/opends/sdk/AuthenticatedConnectionFactory.java | 702
sdk/src/org/opends/sdk/requests/ModifyDNRequest.java | 334
sdk/src/org/opends/sdk/asn1/AbstractASN1Writer.java | 157
sdk/src/org/opends/sdk/controls/AssertionControl.java | 201
sdk/src/org/opends/sdk/ldif/package-info.java | 46
sdk/src/org/opends/sdk/ldif/LDIFChangeRecordWriter.java | 478
sdk/src/org/opends/sdk/sasl/AbstractSASLContext.java | 248
sdk/src/org/opends/sdk/schema/DITContentRule.java | 623
sdk/src/org/opends/sdk/extensions/StartTLSRequest.java | 120
sdk/src/org/opends/sdk/schema/OIDSyntaxImpl.java | 108
sdk/src/org/opends/sdk/schema/GuideSyntaxImpl.java | 430
sdk/src/org/opends/sdk/ldap/ASN1StreamWriter.java | 675
sdk/src/org/opends/sdk/ldap/LDAPMessageHandler.java | 190
sdk/src/org/opends/sdk/requests/ModifyDNRequestImpl.java | 244
sdk/src/org/opends/sdk/asn1/ASN1InputStreamReader.java | 789
sdk/src/org/opends/sdk/AbstractFilterVisitor.java | 233
sdk/src/org/opends/sdk/ModificationType.java | 212
sdk/src/org/opends/sdk/ldap/UnsupportedMessageException.java | 81
sdk/src/org/opends/sdk/DereferenceAliasesPolicy.java | 223
sdk/src/org/opends/sdk/responses/GenericIntermediateResponse.java | 137
sdk/src/org/opends/sdk/schema/CertificateListSyntaxImpl.java | 108
sdk/src/org/opends/sdk/schema/SchemaCompatOptions.java | 164
sdk/src/org/opends/sdk/schema/TelephoneNumberSubstringMatchingRuleImpl.java | 67
sdk/src/org/opends/sdk/schema/NumericStringOrderingMatchingRuleImpl.java | 59
sdk/src/org/opends/sdk/schema/ObjectClassType.java | 82
sdk/src/org/opends/sdk/DN.java | 763
sdk/src/org/opends/sdk/requests/CompareRequestImpl.java | 229
sdk/src/org/opends/sdk/schema/AbstractSyntaxImpl.java | 77
sdk/src/org/opends/sdk/schema/AbstractOrderingMatchingRuleImpl.java | 104
sdk/src/org/opends/sdk/schema/UnknownSchemaElementException.java | 56
sdk/src/org/opends/sdk/schema/EqualLengthApproximateMatchingRuleImpl.java | 71
sdk/src/org/opends/sdk/schema/GeneralizedTimeOrderingMatchingRuleImpl.java | 50
sdk/src/org/opends/sdk/controls/SubtreeDeleteControl.java | 112
sdk/src/org/opends/sdk/ldap/CompareResultFutureImpl.java | 80
sdk/src/org/opends/sdk/schema/ObjectClass.java | 810
sdk/src/org/opends/sdk/ldap/LDAPConnection.java | 1676
sdk/src/org/opends/sdk/schema/BooleanEqualityMatchingRuleImpl.java | 64
sdk/src/org/opends/sdk/schema/FacsimileNumberSyntaxImpl.java | 240
sdk/src/org/opends/sdk/schema/OctetStringOrderingMatchingRuleImpl.java | 49
sdk/src/org/opends/sdk/util/ByteSequence.java | 348
sdk/src/org/opends/sdk/util/ssl/DistrustAllTrustManager.java | 66
sdk/src/org/opends/sdk/ldif/LDIFEntryWriter.java | 360
sdk/src/org/opends/sdk/requests/SearchRequest.java | 527
sdk/src/org/opends/sdk/schema/CertificatePairSyntaxImpl.java | 107
sdk/src/org/opends/sdk/ldap/ResultFutureImpl.java | 80
sdk/src/org/opends/sdk/package-info.java | 87
sdk/src/org/opends/sdk/schema/CaseIgnoreEqualityMatchingRuleImpl.java | 83
sdk/src/org/opends/sdk/util/LocalizedIllegalArgumentException.java | 101
sdk/src/org/opends/sdk/SearchResultHandler.java | 82
sdk/src/org/opends/sdk/schema/NameForm.java | 452
sdk/src/org/opends/sdk/controls/PasswordPolicyWarningType.java | 107
sdk/src/org/opends/sdk/ldap/SearchResultFutureImpl.java | 127
sdk/src/org/opends/sdk/ConditionResult.java | 295
sdk/src/org/opends/sdk/schema/GeneralizedTimeEqualityMatchingRuleImpl.java | 50
sdk/src/org/opends/sdk/schema/LDAPSyntaxDescriptionSyntaxImpl.java | 236
sdk/src/org/opends/sdk/requests/BindRequest.java | 137
sdk/src/org/opends/sdk/schema/CaseIgnoreOrderingMatchingRuleImpl.java | 83
sdk/src/org/opends/sdk/schema/MatchingRuleUseSyntaxImpl.java | 217
sdk/src/org/opends/sdk/schema/ObjectIdentifierFirstComponentEqualityMatchingRuleImpl.java | 110
sdk/src/org/opends/sdk/requests/AddRequestImpl.java | 387
sdk/src/org/opends/sdk/responses/SearchResultReferenceImpl.java | 145
sdk/src/org/opends/sdk/sasl/ExternalSASLBindRequest.java | 190
sdk/src/org/opends/sdk/schema/TeletexTerminalIdentifierSyntaxImpl.java | 271
sdk/src/org/opends/sdk/util/Base64.java | 388
sdk/src/org/opends/sdk/controls/EntryChangeNotificationControl.java | 396
sdk/src/org/opends/sdk/ConnectionFuture.java | 150
sdk/src/org/opends/sdk/schema/EnhancedGuideSyntaxImpl.java | 184
sdk/src/org/opends/sdk/schema/OtherMailboxSyntaxImpl.java | 177
sdk/src/org/opends/sdk/util/LocalizableException.java | 48
sdk/src/org/opends/sdk/sasl/DigestMD5SASLBindRequest.java | 436
sdk/src/org/opends/sdk/AbstractAttribute.java | 512
sdk/src/org/opends/sdk/ldap/AbstractLDAPMessageHandler.java | 258
sdk/src/org/opends/sdk/ldif/LDIFChangeRecordReader.java | 719
sdk/src/org/opends/sdk/ldif/ChangeRecordVisitor.java | 110
sdk/src/org/opends/sdk/controls/MatchedValuesControl.java | 351
sdk/src/org/opends/sdk/AbstractConnectionFactory.java | 125
sdk/src/org/opends/sdk/util/StringPrepProfile.java | 696
sdk/src/org/opends/sdk/schema/IntegerSyntaxImpl.java | 228
sdk/src/org/opends/sdk/schema/CoreSchema.java | 2666 +
sdk/src/org/opends/sdk/controls/PasswordPolicyErrorType.java | 128
sdk/src/org/opends/sdk/responses/AbstractExtendedResult.java | 113
sdk/src/org/opends/sdk/schema/SchemaBuilder.java | 3007 +
sdk/src/org/opends/sdk/schema/AttributeTypeSyntaxImpl.java | 277
sdk/src/org/opends/sdk/ldif/AbstractLDIFStream.java | 174
sdk/src/org/opends/sdk/tools/StringArgument.java | 150
sdk/src/org/opends/sdk/util/ssl/HostnameMismatchCertificateException.java | 67
sdk/src/org/opends/sdk/schema/DoubleMetaphoneApproximateMatchingRuleImpl.java | 1117
sdk/src/org/opends/sdk/DecodeException.java | 154
sdk/src/org/opends/sdk/schema/MatchingRule.java | 457
sdk/src/org/opends/sdk/ldap/LDAPConstants.java | 332
sdk/src/org/opends/sdk/responses/ResultImpl.java | 86
sdk/src/org/opends/sdk/controls/SortKey.java | 144
sdk/src/org/opends/sdk/requests/DeleteRequest.java | 190
sdk/src/org/opends/sdk/responses/BindResult.java | 208
sdk/src/org/opends/sdk/schema/MatchingRuleUse.java | 371
sdk/src/org/opends/sdk/asn1/ASN1Writer.java | 401
sdk/src/org/opends/sdk/ldap/UnexpectedResponseException.java | 71
sdk/src/org/opends/sdk/responses/AbstractResultImpl.java | 245
sdk/src/org/opends/sdk/controls/AccountUsabilityControl.java | 691
sdk/src/org/opends/sdk/ldap/SASLStreamWriter.java | 87
sdk/src/org/opends/sdk/schema/CaseExactIA5EqualityMatchingRuleImpl.java | 98
sdk/src/org/opends/sdk/ErrorResultIOException.java | 75
sdk/src/org/opends/sdk/extensions/WhoAmIRequest.java | 110
sdk/src/org/opends/sdk/ldif/LDIFEntryReader.java | 432
sdk/src/org/opends/sdk/ldif/AbstractLDIFWriter.java | 545
sdk/src/org/opends/sdk/schema/AttributeType.java | 908
sdk/src/org/opends/sdk/schema/IntegerEqualityMatchingRuleImpl.java | 66
sdk/src/org/opends/sdk/ConnectionFactory.java | 95
sdk/src/org/opends/sdk/ResultCode.java | 764
sdk/src/org/opends/sdk/util/ByteStringBuilder.java | 1108
sdk/src/org/opends/sdk/sasl/GenericSASLBindRequest.java | 175
sdk/src/org/opends/sdk/requests/ModifyRequestImpl.java | 226
sdk/src/org/opends/sdk/schema/WordEqualityMatchingRuleImpl.java | 184
sdk/src/org/opends/sdk/util/Iterables.java | 397
sdk/src/org/opends/sdk/tools/ModRate.java | 431
sdk/src/org/opends/sdk/tools/PerformanceRunner.java | 943
sdk/src/org/opends/sdk/ldap/SASLFilter.java | 350
sdk/src/org/opends/sdk/controls/VLVResult.java | 113
sdk/src/org/opends/sdk/util/ASCIICharProp.java | 372
sdk/src/org/opends/sdk/sasl/CRAMMD5SASLBindRequest.java | 275
sdk/src/org/opends/sdk/Matcher.java | 871
sdk/src/org/opends/sdk/controls/Control.java | 80
sdk/src/org/opends/sdk/schema/JPEGSyntaxImpl.java | 99
sdk/src/org/opends/sdk/ldap/LDAPEncoder.java | 723
sdk/src/org/opends/sdk/requests/AbandonRequest.java | 147
sdk/src/org/opends/sdk/asn1/ASN1Constants.java | 167
sdk/src/org/opends/sdk/schema/DITContentRuleSyntaxImpl.java | 203
sdk/src/org/opends/sdk/schema/NameFormSyntaxImpl.java | 223
sdk/src/org/opends/sdk/ldap/LDAPConnectionFactoryImpl.java | 636
sdk/src/org/opends/sdk/ldap/AbstractResultFutureImpl.java | 259
sdk/src/org/opends/sdk/schema/ProtocolInformationSyntaxImpl.java | 113
sdk/src/org/opends/sdk/schema/EnumSyntaxImpl.java | 200
sdk/src/org/opends/sdk/schema/CaseIgnoreListEqualityMatchingRuleImpl.java | 96
sdk/src/org/opends/sdk/controls/VLVControl.java | 654
sdk/src/org/opends/sdk/tools/SearchRate.java | 519
sdk/src/org/opends/sdk/ConnectionResultHandler.java | 81
sdk/src/org/opends/sdk/schema/CaseExactOrderingMatchingRuleImpl.java | 83
sdk/src/org/opends/sdk/schema/PrintableStringSyntaxImpl.java | 250
sdk/src/org/opends/sdk/tools/LDAPModify.java | 801
sdk/src/org/opends/sdk/schema/ProtocolInformationEqualityMatchingRuleImpl.java | 85
sdk/src/org/opends/sdk/tools/IntegerArgument.java | 547
sdk/src/org/opends/sdk/util/SizeLimitInputStream.java | 182
sdk/src/org/opends/sdk/Connection.java | 1084
sdk/src/org/opends/sdk/responses/Response.java | 124
sdk/src/org/opends/sdk/ldap/LDAPDecoder.java | 1922 +
sdk/src/org/opends/sdk/schema/ConflictingSchemaElementException.java | 58
sdk/src/org/opends/sdk/requests/SimpleBindRequest.java | 254
sdk/src/org/opends/sdk/schema/CertificateSyntaxImpl.java | 107
sdk/src/org/opends/sdk/responses/Responses.java | 278
sdk/src/org/opends/sdk/requests/AbandonRequestImpl.java | 95
sdk/src/org/opends/sdk/util/ssl/TrustAllTrustManager.java | 64
sdk/src/org/opends/sdk/schema/GeneralizedTimeSyntaxImpl.java | 1463
sdk/src/org/opends/sdk/util/ByteSequenceOutputStream.java | 113
sdk/src/org/opends/sdk/tools/MultiColumnPrinter.java | 519
sdk/src/org/opends/sdk/schema/ObjectClassSyntaxImpl.java | 214
sdk/src/org/opends/sdk/FilterVisitor.java | 238
sdk/src/org/opends/sdk/schema/SchemaException.java | 89
sdk/src/org/opends/sdk/requests/SimpleBindRequestImpl.java | 174
sdk/src/org/opends/sdk/schema/DITStructureRule.java | 342
sdk/src/org/opends/sdk/schema/UserPasswordExactEqualityMatchingRuleImpl.java | 78
sdk/src/org/opends/sdk/ldap/ASN1StreamReader.java | 838
sdk/src/org/opends/sdk/extensions/ExtendedOperation.java | 34
sdk/src/org/opends/sdk/asn1/AbstractASN1Reader.java | 210
sdk/src/org/opends/sdk/RDN.java | 899
sdk/src/org/opends/sdk/Change.java | 126
sdk/src/org/opends/sdk/requests/ExtendedRequest.java | 165
sdk/src/org/opends/sdk/controls/GetEffectiveRightsRequestControl.java | 271
sdk/src/org/opends/sdk/responses/AbstractResponseImpl.java | 170
sdk/src/org/opends/sdk/schema/SchemaUtils.java | 883
362 files changed, 114,483 insertions(+), 0 deletions(-)
diff --git a/sdk/src/org/opends/sdk/AbstractAttribute.java b/sdk/src/org/opends/sdk/AbstractAttribute.java
new file mode 100644
index 0000000..1e55080
--- /dev/null
+++ b/sdk/src/org/opends/sdk/AbstractAttribute.java
@@ -0,0 +1,512 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.*;
+
+import org.opends.sdk.schema.AttributeType;
+import org.opends.sdk.schema.MatchingRule;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Function;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class provides a skeletal implementation of the {@code
+ * Attribute} interface, to minimize the effort required to implement
+ * this interface.
+ */
+public abstract class AbstractAttribute extends AbstractSet<ByteString>
+ implements Attribute
+{
+
+ /**
+ * Returns {@code true} if {@code object} is an attribute which is
+ * equal to {@code attribute}. Two attributes are considered equal if
+ * their attribute descriptions are equal, they both have the same
+ * number of attribute values, and every attribute value contained in
+ * the first attribute is also contained in the second attribute.
+ *
+ * @param attribute
+ * The attribute to be tested for equality.
+ * @param object
+ * The object to be tested for equality with the attribute.
+ * @return {@code true} if {@code object} is an attribute which is
+ * equal to {@code attribute}, or {@code false} if not.
+ */
+ static boolean equals(Attribute attribute, Object object)
+ {
+ if (attribute == object)
+ {
+ return true;
+ }
+
+ if (!(object instanceof Attribute))
+ {
+ return false;
+ }
+
+ Attribute other = (Attribute) object;
+ if (!attribute.getAttributeDescription().equals(
+ other.getAttributeDescription()))
+ {
+ return false;
+ }
+
+ // Attribute description is the same, compare values.
+ if (attribute.size() != other.size())
+ {
+ return false;
+ }
+
+ return attribute.containsAll(other);
+ }
+
+
+
+ /**
+ * Returns the hash code for {@code attribute}. It will be calculated
+ * as the sum of the hash codes of the attribute description and all
+ * of the attribute values.
+ *
+ * @param attribute
+ * The attribute whose hash code should be calculated.
+ * @return The hash code for {@code attribute}.
+ */
+ static int hashCode(Attribute attribute)
+ {
+ int hashCode = attribute.getAttributeDescription().hashCode();
+ for (ByteString value : attribute)
+ {
+ hashCode += normalizeValue(attribute, value).hashCode();
+ }
+ return hashCode;
+ }
+
+
+
+ /**
+ * Returns the normalized form of {@code value} normalized using
+ * {@code attribute}'s equality matching rule.
+ *
+ * @param attribute
+ * The attribute whose equality matching rule should be used
+ * for normalization.
+ * @param value
+ * The attribute value to be normalized.
+ * @return The normalized form of {@code value} normalized using
+ * {@code attribute}'s equality matching rule.
+ */
+ static ByteString normalizeValue(Attribute attribute, ByteString value)
+ {
+ AttributeDescription attributeDescription = attribute
+ .getAttributeDescription();
+ AttributeType attributeType = attributeDescription
+ .getAttributeType();
+ MatchingRule matchingRule = attributeType.getEqualityMatchingRule();
+
+ try
+ {
+ return matchingRule.normalizeAttributeValue(value);
+ }
+ catch (Exception e)
+ {
+ // Fall back to provided value.
+ return value;
+ }
+ }
+
+
+
+ /**
+ * Returns a string representation of {@code attribute}.
+ *
+ * @param attribute
+ * The attribute whose string representation should be
+ * returned.
+ * @return The string representation of {@code attribute}.
+ */
+ static String toString(Attribute attribute)
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append("Attribute(");
+ builder.append(attribute.getAttributeDescriptionAsString());
+ builder.append(", {");
+
+ boolean firstValue = true;
+ for (ByteString value : attribute)
+ {
+ if (!firstValue)
+ {
+ builder.append(", ");
+ }
+
+ builder.append(value);
+ firstValue = false;
+ }
+
+ builder.append("})");
+ return builder.toString();
+ }
+
+
+
+ /**
+ * Sole constructor.
+ */
+ protected AbstractAttribute()
+ {
+ // No implementation required.
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public abstract boolean add(ByteString value)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean add(Object firstValue, Object... remainingValues)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(firstValue);
+
+ boolean modified = add(ByteString.valueOf(firstValue));
+ if (remainingValues != null)
+ {
+ for (Object value : remainingValues)
+ {
+ modified |= add(ByteString.valueOf(value));
+ }
+ }
+ return modified;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean addAll(Collection<? extends ByteString> values)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ return addAll(values, null);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean addAll(Collection<? extends ByteString> values,
+ Collection<? super ByteString> duplicateValues)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ boolean modified = false;
+ for (ByteString value : values)
+ {
+ if (add(value))
+ {
+ modified = true;
+ }
+ else if (duplicateValues != null)
+ {
+ duplicateValues.add(value);
+ }
+ }
+ return modified;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public abstract boolean contains(Object value)
+ throws NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean containsAll(Collection<?> values)
+ throws NullPointerException
+ {
+ for (Object value : values)
+ {
+ if (!contains(value))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean equals(Object object)
+ {
+ return equals(this, object);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteString firstValue() throws NoSuchElementException
+ {
+ return iterator().next();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <T> T firstValueAsObject(
+ Function<? super ByteString, T, Void> type)
+ throws NoSuchElementException
+ {
+ return type.apply(firstValue(), null);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <T, P> T firstValueAsObject(
+ Function<? super ByteString, T, P> type, P p)
+ throws NoSuchElementException
+ {
+ return type.apply(firstValue(), p);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String firstValueAsString() throws NoSuchElementException
+ {
+ return firstValue().toString();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public abstract AttributeDescription getAttributeDescription();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getAttributeDescriptionAsString()
+ {
+ return getAttributeDescription().toString();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int hashCode()
+ {
+ return hashCode(this);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public abstract Iterator<ByteString> iterator();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public abstract boolean remove(Object value)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean removeAll(Collection<?> values)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ return removeAll(values, null);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <T> boolean removeAll(Collection<T> values,
+ Collection<? super T> missingValues)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ boolean modified = false;
+ for (T value : values)
+ {
+ if (remove(value))
+ {
+ modified = true;
+ }
+ else if (missingValues != null)
+ {
+ missingValues.add(value);
+ }
+ }
+ return modified;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean retainAll(Collection<?> values)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ return retainAll(values, null);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <T> boolean retainAll(Collection<T> values,
+ Collection<? super T> missingValues)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ if (values.isEmpty())
+ {
+ if (isEmpty())
+ {
+ return false;
+ }
+ else
+ {
+ clear();
+ return true;
+ }
+ }
+
+ if (isEmpty())
+ {
+ if (missingValues != null)
+ {
+ for (T value : values)
+ {
+ missingValues.add(value);
+ }
+ }
+ return false;
+ }
+
+ Map<ByteString, T> valuesToRetain = new HashMap<ByteString, T>(
+ values.size());
+ for (T value : values)
+ {
+ valuesToRetain.put(
+ normalizeValue(this, ByteString.valueOf(value)), value);
+ }
+
+ boolean modified = false;
+ Iterator<ByteString> iterator = iterator();
+ while (iterator.hasNext())
+ {
+ ByteString value = iterator.next();
+ ByteString normalizedValue = normalizeValue(this, value);
+ if (valuesToRetain.remove(normalizedValue) == null)
+ {
+ modified = true;
+ iterator.remove();
+ }
+ }
+
+ if (missingValues != null)
+ {
+ missingValues.addAll(valuesToRetain.values());
+ }
+
+ return modified;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public abstract int size();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteString[] toArray()
+ {
+ return toArray(new ByteString[size()]);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String toString()
+ {
+ return toString(this);
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/AbstractConnection.java b/sdk/src/org/opends/sdk/AbstractConnection.java
new file mode 100644
index 0000000..58d7e51
--- /dev/null
+++ b/sdk/src/org/opends/sdk/AbstractConnection.java
@@ -0,0 +1,315 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import static org.opends.messages.ProtocolMessages.*;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opends.sdk.requests.Requests;
+import org.opends.sdk.requests.SearchRequest;
+import org.opends.sdk.responses.*;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class provides a skeletal implementation of the {@code
+ * Connection} interface, to minimize the effort required to implement
+ * this interface.
+ */
+public abstract class AbstractConnection implements Connection
+{
+
+ /**
+ * Creates a new abstract connection.
+ */
+ protected AbstractConnection()
+ {
+ // No implementation required.
+ }
+
+
+
+ private static final class SingleEntryHandler implements
+ SearchResultHandler<Void>
+ {
+ // FIXME: does this need to be thread safe?
+ private SearchResultEntry firstEntry = null;
+
+ private SearchResultReference firstReference = null;
+
+ private int entryCount = 0;
+
+
+
+ public void handleReference(Void p, SearchResultReference reference)
+ {
+ if (firstReference == null)
+ {
+ firstReference = reference;
+ }
+ }
+
+
+
+ public void handleEntry(Void p, SearchResultEntry entry)
+ {
+ if (firstEntry == null)
+ {
+ firstEntry = entry;
+ }
+ entryCount++;
+ }
+
+ }
+
+
+
+ public Result add(Entry entry) throws ErrorResultException,
+ InterruptedException, UnsupportedOperationException,
+ IllegalStateException, NullPointerException
+ {
+ return add(Requests.newAddRequest(entry));
+ }
+
+
+
+ public Result add(String... ldifLines) throws ErrorResultException,
+ InterruptedException, UnsupportedOperationException,
+ LocalizedIllegalArgumentException, IllegalStateException,
+ NullPointerException
+ {
+ return add(Requests.newAddRequest(ldifLines));
+ }
+
+
+
+ public BindResult bind(String name, String password)
+ throws ErrorResultException, InterruptedException,
+ LocalizedIllegalArgumentException, UnsupportedOperationException,
+ IllegalStateException, NullPointerException
+ {
+ return bind(Requests.newSimpleBindRequest(name, password));
+ }
+
+
+
+ public CompareResult compare(String name,
+ String attributeDescription, String assertionValue)
+ throws ErrorResultException, InterruptedException,
+ LocalizedIllegalArgumentException, UnsupportedOperationException,
+ IllegalStateException, NullPointerException
+ {
+ return compare(Requests.newCompareRequest(name,
+ attributeDescription, assertionValue));
+ }
+
+
+
+ public Result delete(String name) throws ErrorResultException,
+ InterruptedException, LocalizedIllegalArgumentException,
+ UnsupportedOperationException, IllegalStateException,
+ NullPointerException
+ {
+ return delete(Requests.newDeleteRequest(name));
+ }
+
+
+
+ public GenericExtendedResult extendedRequest(String requestName,
+ ByteString requestValue) throws ErrorResultException,
+ InterruptedException, UnsupportedOperationException,
+ IllegalStateException, NullPointerException
+ {
+ return extendedRequest(Requests.newGenericExtendedRequest(
+ requestName, requestValue));
+ }
+
+
+
+ public Result modify(String... ldifLines)
+ throws ErrorResultException, InterruptedException,
+ UnsupportedOperationException, LocalizedIllegalArgumentException,
+ IllegalStateException, NullPointerException
+ {
+ return modify(Requests.newModifyRequest(ldifLines));
+ }
+
+
+
+ public Result modifyDN(String name, String newRDN)
+ throws ErrorResultException, InterruptedException,
+ LocalizedIllegalArgumentException, UnsupportedOperationException,
+ IllegalStateException, NullPointerException
+ {
+ return modifyDN(Requests.newModifyDNRequest(name, newRDN));
+ }
+
+
+
+ public Result search(SearchRequest request,
+ final Collection<? super SearchResultEntry> entries,
+ final Collection<? super SearchResultReference> references)
+ throws ErrorResultException, InterruptedException,
+ UnsupportedOperationException, IllegalStateException,
+ NullPointerException
+ {
+ Validator.ensureNotNull(request, entries);
+
+ // FIXME: does this need to be thread safe?
+ SearchResultHandler<Void> handler = new SearchResultHandler<Void>()
+ {
+
+ public void handleReference(Void p,
+ SearchResultReference reference)
+ {
+ if (references != null)
+ {
+ references.add(reference);
+ }
+ }
+
+
+
+ public void handleEntry(Void p, SearchResultEntry entry)
+ {
+ entries.add(entry);
+ }
+ };
+
+ return search(request, handler, null);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Result search(SearchRequest request,
+ Collection<? super SearchResultEntry> entries)
+ throws ErrorResultException, InterruptedException,
+ UnsupportedOperationException, IllegalStateException,
+ NullPointerException
+ {
+ return search(request, entries, null);
+ }
+
+
+
+ public List<SearchResultEntry> search(String baseObject,
+ SearchScope scope, String filter, String... attributeDescriptions)
+ throws ErrorResultException, InterruptedException,
+ LocalizedIllegalArgumentException, UnsupportedOperationException,
+ IllegalStateException, NullPointerException
+ {
+ List<SearchResultEntry> entries = new LinkedList<SearchResultEntry>();
+ SearchRequest request = Requests.newSearchRequest(baseObject,
+ scope, filter, attributeDescriptions);
+ search(request, entries);
+ return entries;
+ }
+
+
+
+ public SearchResultEntry searchSingleEntry(SearchRequest request)
+ throws ErrorResultException, InterruptedException,
+ UnsupportedOperationException, IllegalStateException,
+ NullPointerException
+ {
+ SingleEntryHandler handler = new SingleEntryHandler();
+ search(request, handler, null);
+ if (handler.entryCount > 1)
+ {
+ // Got more entries than expected.
+ Result result = Responses.newResult(
+ ResultCode.CLIENT_SIDE_LOCAL_ERROR).setDiagnosticMessage(
+ ERR_UNEXPECTED_SEARCH_RESULT_ENTRIES.get(handler.entryCount)
+ .toString());
+ throw new ErrorResultException(result);
+ }
+ else if (handler.firstReference != null)
+ {
+ // Got an unexpected search result reference.
+ Result result = Responses.newResult(
+ ResultCode.CLIENT_SIDE_LOCAL_ERROR).setDiagnosticMessage(
+ ERR_UNEXPECTED_SEARCH_RESULT_REFERENCES.get(
+ handler.firstReference.getURIs().iterator().next())
+ .toString());
+ throw new ErrorResultException(result);
+ }
+ else
+ {
+ return handler.firstEntry;
+ }
+ }
+
+
+
+ public SearchResultEntry searchSingleEntry(String baseObject,
+ SearchScope scope, String filter, String... attributeDescriptions)
+ throws ErrorResultException, InterruptedException,
+ LocalizedIllegalArgumentException, UnsupportedOperationException,
+ IllegalStateException, NullPointerException
+ {
+ SearchRequest request = Requests.newSearchRequest(baseObject,
+ scope, filter, attributeDescriptions);
+ return searchSingleEntry(request);
+ }
+
+
+
+ public SearchResultEntry readEntry(String baseObject,
+ String... attributeDescriptions) throws ErrorResultException,
+ InterruptedException, LocalizedIllegalArgumentException,
+ UnsupportedOperationException, IllegalStateException,
+ NullPointerException
+ {
+ return readEntry(DN.valueOf(baseObject));
+ }
+
+
+
+ public SearchResultEntry readEntry(DN baseObject,
+ String... attributeDescriptions) throws ErrorResultException,
+ InterruptedException, UnsupportedOperationException,
+ IllegalStateException, NullPointerException
+ {
+ SearchRequest request = Requests.newSearchRequest(baseObject,
+ SearchScope.BASE_OBJECT, Filter.getObjectClassPresentFilter(),
+ attributeDescriptions);
+ return searchSingleEntry(request);
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/AbstractConnectionFactory.java b/sdk/src/org/opends/sdk/AbstractConnectionFactory.java
new file mode 100644
index 0000000..f80baf0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/AbstractConnectionFactory.java
@@ -0,0 +1,125 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import org.opends.sdk.responses.Responses;
+import org.opends.sdk.responses.Result;
+
+
+
+/**
+ * This class provides a skeletal implementation of the {@code
+ * ConnectionFactory} interface, to minimize the effort required to
+ * implement this interface.
+ *
+ * @param <C>
+ * The type of asynchronous connection returned by this
+ * connection factory.
+ */
+public abstract class AbstractConnectionFactory<C extends AsynchronousConnection>
+ implements ConnectionFactory<C>
+{
+ /**
+ * Creates a new abstract connection factory.
+ */
+ protected AbstractConnectionFactory()
+ {
+ // Nothing to do.
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public abstract <P> ConnectionFuture<? extends C> getAsynchronousConnection(
+ ConnectionResultHandler<? super C, P> handler, P p);
+
+
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The default implementation is to convert the asynchronous
+ * connection returned from {@code
+ * blockingGetAsynchronousConnection()} to a synchronous connection
+ * using a {@link SynchronousConnection} as per the following code:
+ *
+ * <pre>
+ * return new SynchronousConnection(blockingGetAsynchronousConnection());
+ * </pre>
+ *
+ * Implementations should override this method if they wish to return
+ * a different type of synchronous connection.
+ *
+ * @return A connection to the Directory Server associated with this
+ * connection factory.
+ * @throws ErrorResultException
+ * If the connection request failed for some reason.
+ */
+ public Connection getConnection() throws ErrorResultException
+ {
+ return new SynchronousConnection(
+ blockingGetAsynchronousConnection());
+ }
+
+
+
+ /**
+ * Invokes {@code getAsynchronousConnection}, blocking until the
+ * asynchronous connection is obtained or the attempt fails.
+ *
+ * @return An asynchronous connection obtained using {@code
+ * getAsynchronousConnection}.
+ * @throws ErrorResultException
+ * If the connection request failed for some reason.
+ */
+ protected final C blockingGetAsynchronousConnection()
+ throws ErrorResultException
+ {
+ ConnectionFuture<? extends C> future =
+ getAsynchronousConnection(null, null);
+ try
+ {
+ return future.get();
+ }
+ catch (InterruptedException e)
+ {
+ // Cancel the request if possible.
+ future.cancel(false);
+
+ Result result =
+ Responses.newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR)
+ .setCause(e)
+ .setDiagnosticMessage(e.getLocalizedMessage());
+ throw new ErrorResultException(result);
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/AbstractEntry.java b/sdk/src/org/opends/sdk/AbstractEntry.java
new file mode 100644
index 0000000..3732bc5
--- /dev/null
+++ b/sdk/src/org/opends/sdk/AbstractEntry.java
@@ -0,0 +1,421 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import org.opends.sdk.schema.ObjectClass;
+import org.opends.sdk.util.*;
+
+
+
+/**
+ * This class provides a skeletal implementation of the {@code Entry}
+ * interface, to minimize the effort required to implement this
+ * interface.
+ */
+public abstract class AbstractEntry implements Entry
+{
+
+ // Function used for getObjectClasses
+ private static final Function<ByteString, String, Void> BYTE_STRING_TO_STRING_FUNCTION = new Function<ByteString, String, Void>()
+ {
+
+ public String apply(ByteString value, Void p)
+ {
+ return value.toString();
+ }
+
+ };
+
+ // Predicate used for findAttributes.
+ private static final Predicate<Attribute, AttributeDescription> FIND_ATTRIBUTES_PREDICATE = new Predicate<Attribute, AttributeDescription>()
+ {
+
+ public boolean matches(Attribute value, AttributeDescription p)
+ {
+ return value.getAttributeDescription().isSubTypeOf(p);
+ }
+
+ };
+
+
+
+ /**
+ * Returns {@code true} if {@code object} is an entry which is equal
+ * to {@code entry}. Two entry are considered equal if their
+ * distinguished names are equal, they both have the same number of
+ * attributes, and every attribute contained in the first entry is
+ * also contained in the second entry.
+ *
+ * @param entry
+ * The entry to be tested for equality.
+ * @param object
+ * The object to be tested for equality with the entry.
+ * @return {@code true} if {@code object} is an entry which is equal
+ * to {@code entry}, or {@code false} if not.
+ */
+ static boolean equals(Entry entry, Object object)
+ {
+ if (entry == object)
+ {
+ return true;
+ }
+
+ if (!(object instanceof Entry))
+ {
+ return false;
+ }
+
+ Entry other = (Entry) object;
+ if (!entry.getName().equals(other.getName()))
+ {
+ return false;
+ }
+
+ // Distinguished name is the same, compare attributes.
+ if (entry.getAttributeCount() != other.getAttributeCount())
+ {
+ return false;
+ }
+
+ for (Attribute attribute : entry.getAttributes())
+ {
+ Attribute otherAttribute = other.getAttribute(attribute
+ .getAttributeDescription());
+
+ if (!attribute.equals(otherAttribute))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+
+
+ /**
+ * Returns the hash code for {@code entry}. It will be calculated as
+ * the sum of the hash codes of the distinguished name and all of the
+ * attributes.
+ *
+ * @param entry
+ * The entry whose hash code should be calculated.
+ * @return The hash code for {@code entry}.
+ */
+ static int hashCode(Entry entry)
+ {
+ int hashCode = entry.getName().hashCode();
+ for (Attribute attribute : entry.getAttributes())
+ {
+ hashCode += attribute.hashCode();
+ }
+ return hashCode;
+ }
+
+
+
+ /**
+ * Returns a string representation of {@code entry}.
+ *
+ * @param entry
+ * The entry whose string representation should be returned.
+ * @return The string representation of {@code entry}.
+ */
+ static String toString(Entry entry)
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append("Entry(");
+ builder.append(entry.getName());
+ builder.append(", {");
+
+ boolean firstValue = true;
+ for (Attribute attribute : entry.getAttributes())
+ {
+ if (!firstValue)
+ {
+ builder.append(", ");
+ }
+
+ builder.append(attribute);
+ firstValue = false;
+ }
+
+ builder.append("})");
+ return builder.toString();
+ }
+
+
+
+ /**
+ * Sole constructor.
+ */
+ protected AbstractEntry()
+ {
+ // No implementation required.
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean addAttribute(Attribute attribute)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ return addAttribute(attribute, null);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Entry addAttribute(String attributeDescription,
+ Object... values) throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ addAttribute(new LinkedAttribute(attributeDescription, values), null);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean containsAttribute(String attributeDescription)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ return containsAttribute(AttributeDescription
+ .valueOf(attributeDescription));
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean containsObjectClass(ObjectClass objectClass)
+ throws NullPointerException
+ {
+ return containsObjectClass(objectClass.getOID());
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean containsObjectClass(String objectClass)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(objectClass);
+
+ Attribute attribute = getAttribute(AttributeDescription
+ .objectClass());
+ return attribute != null ? attribute.contains(objectClass) : false;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean equals(Object object)
+ {
+ return equals(this, object);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterable<Attribute> findAttributes(
+ AttributeDescription attributeDescription)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(attributeDescription);
+
+ return Iterables.filter(getAttributes(), FIND_ATTRIBUTES_PREDICATE,
+ attributeDescription);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterable<Attribute> findAttributes(String attributeDescription)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ return findAttributes(AttributeDescription
+ .valueOf(attributeDescription));
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Attribute getAttribute(String attributeDescription)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ return getAttribute(AttributeDescription
+ .valueOf(attributeDescription));
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterable<String> getObjectClasses()
+ {
+ Attribute attribute = getAttribute(AttributeDescription
+ .objectClass());
+
+ if (attribute == null)
+ {
+ return Iterables.empty();
+ }
+ else
+ {
+ return Iterables.transform(attribute,
+ BYTE_STRING_TO_STRING_FUNCTION);
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int hashCode()
+ {
+ return hashCode(this);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean removeAttribute(
+ AttributeDescription attributeDescription)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ return removeAttribute(Types.emptyAttribute(attributeDescription),
+ null);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Entry removeAttribute(String attributeDescription)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ removeAttribute(new LinkedAttribute(attributeDescription), null);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Entry removeAttribute(String attributeDescription,
+ Object... values) throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ removeAttribute(new LinkedAttribute(attributeDescription, values),
+ null);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean replaceAttribute(Attribute attribute)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ if (attribute.isEmpty())
+ {
+ return removeAttribute(attribute.getAttributeDescription());
+ }
+ else
+ {
+ removeAttribute(attribute.getAttributeDescription());
+ addAttribute(attribute);
+ return true;
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Entry replaceAttribute(String attributeDescription,
+ Object... values) throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ replaceAttribute(new LinkedAttribute(attributeDescription, values));
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Entry setName(String dn)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ return setName(DN.valueOf(dn));
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String toString()
+ {
+ return toString(this);
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/AbstractFilterVisitor.java b/sdk/src/org/opends/sdk/AbstractFilterVisitor.java
new file mode 100644
index 0000000..7a62823
--- /dev/null
+++ b/sdk/src/org/opends/sdk/AbstractFilterVisitor.java
@@ -0,0 +1,233 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.List;
+
+import org.opends.sdk.util.ByteSequence;
+
+
+/**
+ * An abstract filter visitor whose default implementation for all
+ * {@code Visitor} methods is to invoke
+ * {@link #visitDefaultFilter(Object)}.
+ * <p>
+ * Implementations can override the methods on a case by case behavior.
+ *
+ * @param <R>
+ * The return type of this visitor's methods. Use
+ * {@link java.lang.Void} for visitors that do not need to
+ * return results.
+ * @param <P>
+ * The type of the additional parameter to this visitor's
+ * methods. Use {@link java.lang.Void} for visitors that do not
+ * need an additional parameter.
+ */
+public abstract class AbstractFilterVisitor<R, P> implements
+ FilterVisitor<R, P>
+{
+
+ /**
+ * Default constructor.
+ */
+ protected AbstractFilterVisitor()
+ {
+ // Nothing to do.
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The default implementation is to call
+ * {@link #visitDefaultFilter(Object)}.
+ */
+ public R visitAndFilter(P p, List<Filter> subFilters)
+ {
+ return visitDefaultFilter(p);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The default implementation is to call
+ * {@link #visitDefaultFilter(Object)}.
+ */
+ public R visitApproxMatchFilter(P p, String attributeDescription,
+ ByteSequence assertionValue)
+ {
+ return visitDefaultFilter(p);
+ }
+
+
+
+ /**
+ * Visits any filters which are not explicitly handled by other
+ * visitor methods.
+ * <p>
+ * The default implementation of this method is to return {@code null}.
+ *
+ * @param p
+ * A visitor specified parameter.
+ * @return A visitor specified result.
+ */
+ public R visitDefaultFilter(P p)
+ {
+ return null;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The default implementation is to call
+ * {@link #visitDefaultFilter(Object)}.
+ */
+ public R visitEqualityMatchFilter(P p, String attributeDescription,
+ ByteSequence assertionValue)
+ {
+ return visitDefaultFilter(p);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The default implementation is to call
+ * {@link #visitDefaultFilter(Object)}.
+ */
+ public R visitExtensibleMatchFilter(P p, String matchingRule,
+ String attributeDescription, ByteSequence assertionValue,
+ boolean dnAttributes)
+ {
+ return visitDefaultFilter(p);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The default implementation is to call
+ * {@link #visitDefaultFilter(Object)}.
+ */
+ public R visitGreaterOrEqualFilter(P p, String attributeDescription,
+ ByteSequence assertionValue)
+ {
+ return visitDefaultFilter(p);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The default implementation is to call
+ * {@link #visitDefaultFilter(Object)}.
+ */
+ public R visitLessOrEqualFilter(P p, String attributeDescription,
+ ByteSequence assertionValue)
+ {
+ return visitDefaultFilter(p);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The default implementation is to call
+ * {@link #visitDefaultFilter(Object)}.
+ */
+ public R visitNotFilter(P p, Filter subFilter)
+ {
+ return visitDefaultFilter(p);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The default implementation is to call
+ * {@link #visitDefaultFilter(Object)}.
+ */
+ public R visitOrFilter(P p, List<Filter> subFilters)
+ {
+ return visitDefaultFilter(p);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The default implementation is to call
+ * {@link #visitDefaultFilter(Object)}.
+ */
+ public R visitPresentFilter(P p, String attributeDescription)
+ {
+ return visitDefaultFilter(p);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The default implementation is to call
+ * {@link #visitDefaultFilter(Object)}.
+ */
+ public R visitSubstringsFilter(P p, String attributeDescription,
+ ByteSequence initialSubstring, List<ByteSequence> anySubstrings,
+ ByteSequence finalSubstring)
+ {
+ return visitDefaultFilter(p);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The default implementation is to call
+ * {@link #visitDefaultFilter(Object)}.
+ */
+ public R visitUnrecognizedFilter(P p, byte filterTag,
+ ByteSequence filterBytes)
+ {
+ return visitDefaultFilter(p);
+ }
+}
diff --git a/sdk/src/org/opends/sdk/Assertion.java b/sdk/src/org/opends/sdk/Assertion.java
new file mode 100644
index 0000000..96d052b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/Assertion.java
@@ -0,0 +1,53 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * A compiled attribute value assertion.
+ */
+public interface Assertion
+{
+ /**
+ * Indicates whether the provided attribute value should be considered
+ * a match for this assertion value according to the matching rule.
+ *
+ * @param attributeValue
+ * The attribute value.
+ * @return {@code TRUE} if the attribute value should be considered a
+ * match for the provided assertion value, {@code FALSE} if it
+ * does not match, or {@code UNDEFINED} if the result is
+ * undefined.
+ */
+ public abstract ConditionResult matches(ByteSequence attributeValue);
+}
diff --git a/sdk/src/org/opends/sdk/AsynchronousConnection.java b/sdk/src/org/opends/sdk/AsynchronousConnection.java
new file mode 100644
index 0000000..ac0837c
--- /dev/null
+++ b/sdk/src/org/opends/sdk/AsynchronousConnection.java
@@ -0,0 +1,495 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.io.Closeable;
+
+import org.opends.sdk.requests.*;
+import org.opends.sdk.responses.*;
+
+
+
+/**
+ * An asynchronous connection with a Directory Server over which read
+ * and update operations may be performed. See RFC 4511 for the LDAPv3
+ * protocol specification and more information about the types of
+ * operations defined in LDAP.
+ * <p>
+ * <h3>Operation processing</h3>
+ * <p>
+ * All operations are performed asynchronously and return a
+ * {@link ResultFuture} or sub-type thereof which can be used for
+ * retrieving the result using the {@link ResultFuture#get} method.
+ * Operation failures, for whatever reason, are signalled by the
+ * {@link ResultFuture#get()} method throwing an
+ * {@link ErrorResultException}.
+ * <p>
+ * Synchronous operations are easily simulated by immediately getting
+ * the result:
+ *
+ * <pre>
+ * Connection connection = ...;
+ * AddRequest request = ...;
+ * // Will block until operation completes, and
+ * // throws exception on failure.
+ * connection.add(request).get();
+ * </pre>
+ *
+ * Operations can be performed in parallel while taking advantage of the
+ * simplicity of a synchronous application design:
+ *
+ * <pre>
+ * Connection connection1 = ...;
+ * Connection connection2 = ...;
+ * AddRequest request = ...;
+ * // Add the entry to the first server (don't block).
+ * ResultFuture future1 = connection1.add(request);
+ * // Add the entry to the second server (in parallel).
+ * ResultFuture future2 = connection2.add(request);
+ * // Total time = is O(1) instead of O(n).
+ * future1.get();
+ * future2.get();
+ * </pre>
+ *
+ * More complex client applications can take advantage of a fully
+ * asynchronous event driven design using {@link ResultHandler}s:
+ *
+ * <pre>
+ * Connection connection = ...;
+ * SearchRequest request = ...;
+ * // Process results in the search result handler
+ * // in a separate thread.
+ * SearchResponseHandler handle = ...;
+ * connection.search(request, handler);
+ * </pre>
+ * <p>
+ * <h3>Closing connections</h3>
+ * <p>
+ * Applications must ensure that a connection is closed by calling
+ * {@link #close()} even if a fatal error occurs on the connection. Once
+ * a connection has been closed by the client application, any attempts
+ * to continue to use the connection will result in an
+ * {@link IllegalStateException} being thrown. Note that, if a fatal
+ * error is encountered on the connection, then the application can
+ * continue to use the connection. In this case all requests subsequent
+ * to the failure will fail with an appropriate
+ * {@link ErrorResultException} when their result is retrieved.
+ * <p>
+ * <h3>Event notification</h3>
+ * <p>
+ * Applications can choose to be notified when a connection is closed by
+ * the application, receives an unsolicited notification, or experiences
+ * a fatal error by registering a {@link ConnectionEventListener} with
+ * the connection using the {@link #addConnectionEventListener} method.
+ * <p>
+ * <h3>TO DO</h3>
+ * <p>
+ * <ul>
+ * <li>do we need isClosed() and isValid()?
+ * <li>do we need connection event notification of client close? JDBC
+ * and JCA have this functionality in their pooled (managed) connection
+ * APIs. We need some form of event notification at the app level for
+ * unsolicited notifications.
+ * <li>method for performing update operation (e.g. LDIF change
+ * records).
+ * <li>should unsupported methods throw UnsupportedOperationException or
+ * throw an ErrorResultException using an UnwillingToPerform result code
+ * (or something similar)?
+ * <li>Implementations should indicate whether or not they are thread
+ * safe and support concurrent requests.
+ * </ul>
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc4511">RFC 4511 -
+ * Lightweight Directory Access Protocol (LDAP): The Protocol </a>
+ */
+public interface AsynchronousConnection extends Closeable
+{
+
+ /**
+ * Abandons the unfinished operation identified in the provided
+ * abandon request.
+ * <p>
+ * <b>Note:</b> a more convenient approach to abandoning unfinished
+ * operations is provided via the {@link ResultFuture#cancel(boolean)}
+ * method.
+ *
+ * @param request
+ * The request identifying the operation to be abandoned.
+ * @throws UnsupportedOperationException
+ * If this connection does not support abandon operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code request} was {@code null}.
+ */
+ void abandon(AbandonRequest request)
+ throws UnsupportedOperationException, IllegalStateException,
+ NullPointerException;
+
+
+
+ /**
+ * Adds an entry to the Directory Server using the provided add
+ * request.
+ *
+ * @param <P>
+ * The type of the additional parameter to the handler's
+ * methods.
+ * @param request
+ * The add request.
+ * @param handler
+ * A result handler which can be used to asynchronously
+ * process the operation result when it is received, may be
+ * {@code null}.
+ * @param p
+ * Optional additional handler parameter.
+ * @return A future representing the result of the operation.
+ * @throws UnsupportedOperationException
+ * If this connection does not support add operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code request} was {@code null}.
+ */
+ <P> ResultFuture<Result> add(AddRequest request,
+ ResultHandler<Result, P> handler, P p)
+ throws UnsupportedOperationException, IllegalStateException,
+ NullPointerException;
+
+
+
+ /**
+ * Authenticates to the Directory Server using the provided bind
+ * request.
+ *
+ * @param <P>
+ * The type of the additional parameter to the handler's
+ * methods.
+ * @param request
+ * The bind request.
+ * @param handler
+ * A result handler which can be used to asynchronously
+ * process the operation result when it is received, may be
+ * {@code null}.
+ * @param p
+ * Optional additional handler parameter.
+ * @return A future representing the result of the operation.
+ * @throws UnsupportedOperationException
+ * If this connection does not support bind operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code request} was {@code null}.
+ */
+ <P> ResultFuture<BindResult> bind(BindRequest request,
+ ResultHandler<? super BindResult, P> handler, P p)
+ throws UnsupportedOperationException, IllegalStateException,
+ NullPointerException;
+
+
+
+ /**
+ * Releases any resources associated with this connection. For
+ * physical connections to a Directory Server this will mean that an
+ * unbind request is sent and the underlying socket is closed.
+ * <p>
+ * Other connection implementations may behave differently, and may
+ * choose not to send an unbind request if its use is inappropriate
+ * (for example a pooled connection will be released and returned to
+ * its connection pool without ever issuing an unbind request).
+ * <p>
+ * This method is semantically equivalent to the following code:
+ *
+ * <pre>
+ * UnbindRequest request = Requests.newUnbindRequest();
+ * connection.close(request);
+ * </pre>
+ *
+ * Calling {@code close} on a connection that is already closed has no
+ * effect.
+ */
+ void close();
+
+
+
+ /**
+ * Releases any resources associated with this connection. For
+ * physical connections to a Directory Server this will mean that the
+ * provided unbind request is sent and the underlying socket is
+ * closed.
+ * <p>
+ * Other connection implementations may behave differently, and may
+ * choose to ignore the provided unbind request if its use is
+ * inappropriate (for example a pooled connection will be released and
+ * returned to its connection pool without ever issuing an unbind
+ * request).
+ * <p>
+ * Calling {@code close} on a connection that is already closed has no
+ * effect.
+ *
+ * @param request
+ * The unbind request to use in the case where a physical
+ * connection is closed.
+ * @throws NullPointerException
+ * If {@code request} was {@code null}.
+ */
+ void close(UnbindRequest request) throws NullPointerException;
+
+
+
+ /**
+ * Compares an entry in the Directory Server using the provided
+ * compare request.
+ *
+ * @param <P>
+ * The type of the additional parameter to the handler's
+ * methods.
+ * @param request
+ * The compare request.
+ * @param handler
+ * A result handler which can be used to asynchronously
+ * process the operation result when it is received, may be
+ * {@code null}.
+ * @param p
+ * Optional additional handler parameter.
+ * @return A future representing the result of the operation.
+ * @throws UnsupportedOperationException
+ * If this connection does not support compare operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code request} was {@code null}.
+ */
+ <P> ResultFuture<CompareResult> compare(CompareRequest request,
+ ResultHandler<? super CompareResult, P> handler, P p)
+ throws UnsupportedOperationException, IllegalStateException,
+ NullPointerException;
+
+
+
+ /**
+ * Deletes an entry from the Directory Server using the provided
+ * delete request.
+ *
+ * @param <P>
+ * The type of the additional parameter to the handler's
+ * methods.
+ * @param request
+ * The delete request.
+ * @param handler
+ * A result handler which can be used to asynchronously
+ * process the operation result when it is received, may be
+ * {@code null}.
+ * @param p
+ * Optional additional handler parameter.
+ * @return A future representing the result of the operation.
+ * @throws UnsupportedOperationException
+ * If this connection does not support delete operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code request} was {@code null}.
+ */
+ <P> ResultFuture<Result> delete(DeleteRequest request,
+ ResultHandler<Result, P> handler, P p)
+ throws UnsupportedOperationException, IllegalStateException,
+ NullPointerException;
+
+
+
+ /**
+ * Requests that the Directory Server performs the provided extended
+ * request.
+ *
+ * @param <R>
+ * The type of result returned by the extended request.
+ * @param <P>
+ * The type of the additional parameter to the handler's
+ * methods.
+ * @param request
+ * The extended request.
+ * @param handler
+ * A result handler which can be used to asynchronously
+ * process the operation result when it is received, may be
+ * {@code null}.
+ * @param p
+ * Optional additional handler parameter.
+ * @return A future representing the result of the operation.
+ * @throws UnsupportedOperationException
+ * If this connection does not support extended operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code request} was {@code null}.
+ */
+ <R extends Result, P> ResultFuture<R> extendedRequest(
+ ExtendedRequest<R> request, ResultHandler<? super R, P> handler,
+ P p) throws UnsupportedOperationException, IllegalStateException,
+ NullPointerException;
+
+
+
+ /**
+ * Modifies an entry in the Directory Server using the provided modify
+ * request.
+ *
+ * @param <P>
+ * The type of the additional parameter to the handler's
+ * methods.
+ * @param request
+ * The modify request.
+ * @param handler
+ * A result handler which can be used to asynchronously
+ * process the operation result when it is received, may be
+ * {@code null}.
+ * @param p
+ * Optional additional handler parameter.
+ * @return A future representing the result of the operation.
+ * @throws UnsupportedOperationException
+ * If this connection does not support modify operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code request} was {@code null}.
+ */
+ <P> ResultFuture<Result> modify(ModifyRequest request,
+ ResultHandler<Result, P> handler, P p)
+ throws UnsupportedOperationException, IllegalStateException,
+ NullPointerException;
+
+
+
+ /**
+ * Renames an entry in the Directory Server using the provided modify
+ * DN request.
+ *
+ * @param <P>
+ * The type of the additional parameter to the handler's
+ * methods.
+ * @param request
+ * The modify DN request.
+ * @param handler
+ * A result handler which can be used to asynchronously
+ * process the operation result when it is received, may be
+ * {@code null}.
+ * @param p
+ * Optional additional handler parameter.
+ * @return A future representing the result of the operation.
+ * @throws UnsupportedOperationException
+ * If this connection does not support modify DN operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code request} was {@code null}.
+ */
+ <P> ResultFuture<Result> modifyDN(ModifyDNRequest request,
+ ResultHandler<Result, P> handler, P p)
+ throws UnsupportedOperationException, IllegalStateException,
+ NullPointerException;
+
+
+
+ /**
+ * Searches the Directory Server using the provided search request.
+ *
+ * @param <P>
+ * The type of the additional parameter to the handler's
+ * methods.
+ * @param request
+ * The search request.
+ * @param resultHandler
+ * A result handler which can be used to asynchronously
+ * process the operation result when it is received, may be
+ * {@code null}.
+ * @param searchResulthandler
+ * A search result handler which can be used to
+ * asynchronously process the search result entries and
+ * references as they are received, may be {@code null}.
+ * @param p
+ * Optional additional handler parameter.
+ * @return A future representing the result of the operation.
+ * @throws UnsupportedOperationException
+ * If this connection does not support search operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code request} was {@code null}.
+ */
+ <P> ResultFuture<Result> search(SearchRequest request,
+ ResultHandler<Result, P> resultHandler,
+ SearchResultHandler<P> searchResulthandler, P p)
+ throws UnsupportedOperationException, IllegalStateException,
+ NullPointerException;
+
+
+
+ /**
+ * Registers the provided connection event listener so that it will be
+ * notified when this connection is closed by the application,
+ * receives an unsolicited notification, or experiences a fatal error.
+ *
+ * @param listener
+ * The listener which wants to be notified when events occur
+ * on this connection.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If the {@code listener} was {@code null}.
+ */
+ void addConnectionEventListener(ConnectionEventListener listener)
+ throws IllegalStateException, NullPointerException;
+
+
+
+ /**
+ * Removes the provided connection event listener from this connection
+ * so that it will no longer be notified when this connection is
+ * closed by the application, receives an unsolicited notification, or
+ * experiences a fatal error.
+ *
+ * @param listener
+ * The listener which no longer wants to be notified when
+ * events occur on this connection.
+ * @throws NullPointerException
+ * If the {@code listener} was {@code null}.
+ */
+ void removeConnectionEventListener(ConnectionEventListener listener)
+ throws NullPointerException;
+}
diff --git a/sdk/src/org/opends/sdk/Attribute.java b/sdk/src/org/opends/sdk/Attribute.java
new file mode 100644
index 0000000..d76a1da
--- /dev/null
+++ b/sdk/src/org/opends/sdk/Attribute.java
@@ -0,0 +1,563 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Function;
+
+
+
+/**
+ * An attribute, comprising of an attribute description and zero or more
+ * attribute values.
+ * <p>
+ * Any methods which perform comparisons between attribute values use
+ * the equality matching rule associated with the attribute description.
+ * <p>
+ * Any methods which accept {@code Object} based attribute values
+ * convert the attribute values to instances of {@code ByteString} as
+ * follows:
+ *
+ * <pre>
+ * Object object = ...;
+ * ByteString value = null;
+ * if (object instanceof ByteSequence)
+ * {
+ * value = ((ByteSequence)object).toByteString();
+ * }
+ * else
+ * {
+ * value = ByteString.valueOf(object.toString());
+ * }
+ * </pre>
+ * <p>
+ * TODO: matching against attribute value assertions.
+ */
+public interface Attribute extends Set<ByteString>
+{
+ /**
+ * Adds {@code value} to this attribute if it is not already present
+ * (optional operation). If this attribute already contains {@code
+ * value}, the call leaves the attribute unchanged and returns {@code
+ * false}.
+ *
+ * @param value
+ * The attribute value to be added to this attribute.
+ * @return {@code true} if this attribute changed as a result of this
+ * call.
+ * @throws UnsupportedOperationException
+ * If this attribute does not support addition of attribute
+ * values.
+ * @throws NullPointerException
+ * If {@code value} was {@code null}.
+ */
+ boolean add(ByteString value) throws UnsupportedOperationException,
+ NullPointerException;
+
+
+
+ /**
+ * Adds all of the provided attribute values to this attribute if they
+ * are not already present (optional operation).
+ * <p>
+ * Any attribute values which are not instances of {@code ByteString}
+ * will be converted using the {@link ByteString#valueOf(Object)}
+ * method.
+ *
+ * @param firstValue
+ * The first attribute value to be added to this attribute.
+ * @param remainingValues
+ * The remaining attribute values to be added to this
+ * attribute.
+ * @return {@code true} if this attribute changed as a result of this
+ * call.
+ * @throws UnsupportedOperationException
+ * If this attribute does not support addition of attribute
+ * values.
+ * @throws NullPointerException
+ * If {@code firstValue} was {@code null}.
+ */
+ boolean add(Object firstValue, Object... remainingValues)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Adds all of the attribute values contained in {@code values} to
+ * this attribute if they are not already present (optional
+ * operation).
+ * <p>
+ * An invocation of this method is equivalent to:
+ *
+ * <pre>
+ * attribute.addAll(values, null);
+ * </pre>
+ *
+ * @param values
+ * The attribute values to be added to this attribute.
+ * @return {@code true} if this attribute changed as a result of this
+ * call.
+ * @throws UnsupportedOperationException
+ * If this attribute does not support addition of attribute
+ * values.
+ * @throws NullPointerException
+ * If {@code values} was {@code null}.
+ */
+ boolean addAll(Collection<? extends ByteString> values)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Adds all of the attribute values contained in {@code values} to
+ * this attribute if they are not already present (optional
+ * operation). Any attribute values which are already present will be
+ * added to {@code duplicateValues} if specified.
+ *
+ * @param values
+ * The attribute values to be added to this attribute.
+ * @param duplicateValues
+ * A collection into which duplicate values will be added, or
+ * {@code null} if duplicate values should not be saved.
+ * @return {@code true} if this attribute changed as a result of this
+ * call.
+ * @throws UnsupportedOperationException
+ * If this attribute does not support addition of attribute
+ * values.
+ * @throws NullPointerException
+ * If {@code values} was {@code null}.
+ */
+ boolean addAll(Collection<? extends ByteString> values,
+ Collection<? super ByteString> duplicateValues)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Removes all of the attribute values from this attribute (optional
+ * operation). This attribute will be empty after this call returns.
+ */
+ void clear() throws UnsupportedOperationException;
+
+
+
+ /**
+ * Returns {@code true} if this attribute contains {@code value}.
+ * <p>
+ * If {@code value} is not an instance of {@code ByteString} then it
+ * will be converted using the {@link ByteString#valueOf(Object)}
+ * method.
+ *
+ * @param value
+ * The attribute value whose presence in this attribute is to
+ * be tested.
+ * @return {@code true} if this attribute contains {@code value}, or
+ * {@code false} if not.
+ * @throws NullPointerException
+ * If {@code value} was {@code null}.
+ */
+ boolean contains(Object value) throws NullPointerException;
+
+
+
+ /**
+ * Returns {@code true} if this attribute contains all of the
+ * attribute values contained in {@code values}.
+ * <p>
+ * Any attribute values which are not instances of {@code ByteString}
+ * will be converted using the {@link ByteString#valueOf(Object)}
+ * method.
+ *
+ * @param values
+ * The attribute values whose presence in this attribute is
+ * to be tested.
+ * @return {@code true} if this attribute contains all of the
+ * attribute values contained in {@code values}, or {@code
+ * false} if not.
+ * @throws NullPointerException
+ * If {@code values} was {@code null}.
+ */
+ boolean containsAll(Collection<?> values) throws NullPointerException;
+
+
+
+ /**
+ * Returns {@code true} if {@code object} is an attribute which is
+ * equal to this attribute. Two attributes are considered equal if
+ * their attribute descriptions are equal, they both have the same
+ * number of attribute values, and every attribute value contained in
+ * the first attribute is also contained in the second attribute.
+ *
+ * @param object
+ * The object to be tested for equality with this attribute.
+ * @return {@code true} if {@code object} is an attribute which is
+ * equal to this attribute, or {@code false} if not.
+ */
+ boolean equals(Object object);
+
+
+
+ /**
+ * Returns the first attribute value in this attribute.
+ *
+ * @return The first attribute value in this attribute.
+ * @throws NoSuchElementException
+ * If this attribute is empty.
+ */
+ ByteString firstValue() throws NoSuchElementException;
+
+
+
+ /**
+ * Returns the first attribute value in this attribute converted to a
+ * object of type {@code T} using the function {@code type}. Any
+ * run-time exceptions thrown during the conversion will be passed
+ * back to the caller (e.g. {@code IllegalArgumentException}).
+ *
+ * @param <T>
+ * The type of object to decode the first value as.
+ * @param type
+ * The function to use for decoding the first attribute value
+ * as a type {@code T}.
+ * @return The first attribute value in this attribute.
+ * @throws NoSuchElementException
+ * If this attribute is empty.
+ * @throws NullPointerException
+ * If {@code type} was {@code null}.
+ */
+ <T> T firstValueAsObject(Function<? super ByteString, T, Void> type)
+ throws NoSuchElementException;
+
+
+
+ /**
+ * Returns the first attribute value in this attribute converted to a
+ * object of type {@code T} using the function {@code type} and
+ * passing parameter {@code p}. Any run-time exceptions thrown during
+ * the conversion will be passed back to the caller (e.g. {@code
+ * IllegalArgumentException}).
+ *
+ * @param <T>
+ * The type of object to decode the first value as.
+ * @param <P>
+ * The type of the additional parameter to {@code type}'s
+ * {@code apply} method. Use {@link java.lang.Void} for
+ * functions that do not need an additional parameter.
+ * @param type
+ * The function to use for decoding the first attribute value
+ * as a type {@code T}.
+ * @param p
+ * The parameter to pass to {@code type}.
+ * @return The first attribute value in this attribute.
+ * @throws NoSuchElementException
+ * If this attribute is empty.
+ * @throws NullPointerException
+ * If {@code type} was {@code null}.
+ */
+ <T, P> T firstValueAsObject(Function<? super ByteString, T, P> type,
+ P p) throws NoSuchElementException;
+
+
+
+ /**
+ * Returns the first attribute value in this attribute decoded as a
+ * UTF-8 string.
+ *
+ * @return The first attribute value in this attribute decoded as a
+ * UTF-8 string.
+ * @throws NoSuchElementException
+ * If this attribute is empty.
+ */
+ String firstValueAsString() throws NoSuchElementException;
+
+
+
+ /**
+ * Returns the attribute description of this attribute, which includes
+ * its attribute type and any options.
+ *
+ * @return The attribute description.
+ */
+ AttributeDescription getAttributeDescription();
+
+
+
+ /**
+ * Returns the string representation of the attribute description of
+ * this attribute, which includes its attribute type and any options.
+ *
+ * @return The string representation of the attribute description.
+ */
+ String getAttributeDescriptionAsString();
+
+
+
+ /**
+ * Returns the hash code for this attribute. It will be calculated as
+ * the sum of the hash codes of the attribute description and all of
+ * the attribute values.
+ *
+ * @return The hash code for this attribute.
+ */
+ int hashCode();
+
+
+
+ /**
+ * Returns {@code true} if this attribute contains no attribute
+ * values.
+ *
+ * @return {@code true} if this attribute contains no attribute
+ * values.
+ */
+ boolean isEmpty();
+
+
+
+ /**
+ * Returns an iterator over the attribute values in this attribute.
+ * The attribute values are returned in no particular order, unless
+ * the implementation of this attribute provides such a guarantee.
+ *
+ * @return An iterator over the attribute values in this attribute.
+ */
+ Iterator<ByteString> iterator();
+
+
+
+ /**
+ * Removes {@code value} from this attribute if it is present
+ * (optional operation). If this attribute does not contain {@code
+ * value}, the call leaves the attribute unchanged and returns {@code
+ * false}.
+ * <p>
+ * If {@code value} is not an instance of {@code ByteString} then it
+ * will be converted using the {@link ByteString#valueOf(Object)}
+ * method.
+ *
+ * @param value
+ * The attribute value to be removed from this attribute.
+ * @return {@code true} if this attribute changed as a result of this
+ * call.
+ * @throws UnsupportedOperationException
+ * If this attribute does not support removal of attribute
+ * values.
+ * @throws NullPointerException
+ * If {@code value} was {@code null}.
+ */
+ boolean remove(Object value) throws UnsupportedOperationException,
+ NullPointerException;
+
+
+
+ /**
+ * Removes all of the attribute values contained in {@code values}
+ * from this attribute if they are present (optional operation).
+ * <p>
+ * Any attribute values which are not instances of {@code ByteString}
+ * will be converted using the {@link ByteString#valueOf(Object)}
+ * method.
+ * <p>
+ * An invocation of this method is equivalent to:
+ *
+ * <pre>
+ * attribute.removeAll(values, null);
+ * </pre>
+ *
+ * @param values
+ * The attribute values to be removed from this attribute.
+ * @return {@code true} if this attribute changed as a result of this
+ * call.
+ * @throws UnsupportedOperationException
+ * If this attribute does not support removal of attribute
+ * values.
+ * @throws NullPointerException
+ * If {@code values} was {@code null}.
+ */
+ boolean removeAll(Collection<?> values)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Removes all of the attribute values contained in {@code values}
+ * from this attribute if they are present (optional operation). Any
+ * attribute values which are not already present will be added to
+ * {@code missingValues} if specified.
+ * <p>
+ * Any attribute values which are not instances of {@code ByteString}
+ * will be converted using the {@link ByteString#valueOf(Object)}
+ * method.
+ *
+ * @param <T>
+ * The type of the attribute value objects being removed.
+ * @param values
+ * The attribute values to be removed from this attribute.
+ * @param missingValues
+ * A collection into which missing values will be added, or
+ * {@code null} if missing values should not be saved.
+ * @return {@code true} if this attribute changed as a result of this
+ * call.
+ * @throws UnsupportedOperationException
+ * If this attribute does not support removal of attribute
+ * values.
+ * @throws NullPointerException
+ * If {@code values} was {@code null}.
+ */
+ <T> boolean removeAll(Collection<T> values,
+ Collection<? super T> missingValues)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Retains only the attribute values in this attribute which are
+ * contained in {@code values} (optional operation).
+ * <p>
+ * Any attribute values which are not instances of {@code ByteString}
+ * will be converted using the {@link ByteString#valueOf(Object)}
+ * method.
+ * <p>
+ * An invocation of this method is equivalent to:
+ *
+ * <pre>
+ * attribute.retainAll(values, null);
+ * </pre>
+ *
+ * @param values
+ * The attribute values to be retained in this attribute.
+ * @return {@code true} if this attribute changed as a result of this
+ * call.
+ * @throws UnsupportedOperationException
+ * If this attribute does not support removal of attribute
+ * values.
+ * @throws NullPointerException
+ * If {@code values} was {@code null}.
+ */
+ boolean retainAll(Collection<?> values)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Retains only the attribute values in this attribute which are
+ * contained in {@code values} (optional operation). Any attribute
+ * values which are not already present will be added to {@code
+ * missingValues} if specified.
+ * <p>
+ * Any attribute values which are not instances of {@code ByteString}
+ * will be converted using the {@link ByteString#valueOf(Object)}
+ * method.
+ *
+ * @param <T>
+ * The type of the attribute value objects being retained.
+ * @param values
+ * The attribute values to be retained in this attribute.
+ * @param missingValues
+ * A collection into which missing values will be added, or
+ * {@code null} if missing values should not be saved.
+ * @return {@code true} if this attribute changed as a result of this
+ * call.
+ * @throws UnsupportedOperationException
+ * If this attribute does not support removal of attribute
+ * values.
+ * @throws NullPointerException
+ * If {@code values} was {@code null}.
+ */
+ <T> boolean retainAll(Collection<T> values,
+ Collection<? super T> missingValues)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Returns the number of attribute values in this attribute.
+ *
+ * @return The number of attribute values in this attribute.
+ */
+ int size();
+
+
+
+ /**
+ * Returns an array containing all of the attribute values contained
+ * in this attribute.
+ * <p>
+ * If this attribute makes any guarantees as to what order its
+ * attribute values are returned by its iterator, this method must
+ * return the attribute values in the same order.
+ * <p>
+ * The returned array will be "safe" in that no references to it are
+ * maintained by this attribute. The caller is thus free to modify the
+ * returned array.
+ */
+ ByteString[] toArray();
+
+
+
+ /**
+ * Returns an array containing all of the attribute values in this
+ * attribute; the runtime type of the returned array is that of the
+ * specified array.
+ * <p>
+ * If the set fits in the specified array, it is returned therein.
+ * Otherwise, a new array is allocated with the runtime type of the
+ * specified array and the size of this attribute. If this attribute
+ * fits in the specified array with room to spare (i.e., the array has
+ * more elements than this attribute), the elements in the array
+ * immediately following the end of the set is set to {@code null}.
+ * <p>
+ * If this attribute makes any guarantees as to what order its
+ * attribute values are returned by its iterator, this method must
+ * return the attribute values in the same order.
+ *
+ * @throws ArrayStoreException
+ * If the runtime type of {@code array} is not a supertype
+ * of {@code ByteString}.
+ * @throws NullPointerException
+ * If {@code array} was {@code null}.
+ */
+ <T> T[] toArray(T[] array) throws ArrayStoreException,
+ NullPointerException;
+
+
+
+ /**
+ * Returns a string representation of this attribute.
+ *
+ * @return The string representation of this attribute.
+ */
+ String toString();
+}
diff --git a/sdk/src/org/opends/sdk/AttributeDescription.java b/sdk/src/org/opends/sdk/AttributeDescription.java
new file mode 100644
index 0000000..92dfd56
--- /dev/null
+++ b/sdk/src/org/opends/sdk/AttributeDescription.java
@@ -0,0 +1,1496 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.util.StaticUtils.*;
+
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.sdk.schema.AttributeType;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.schema.UnknownSchemaElementException;
+import org.opends.sdk.util.ASCIICharProp;
+import org.opends.sdk.util.Iterators;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * An attribute description as defined in RFC 4512 section 2.5.
+ * Attribute descriptions are used to identify an attribute in an entry
+ * and are composed of an attribute type and a set of zero or more
+ * attribute options.
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc4512#section-2.5">RFC
+ * 4512 - Lightweight Directory Access Protocol (LDAP): Directory
+ * Information Models </a>
+ */
+public final class AttributeDescription implements
+ Comparable<AttributeDescription>
+{
+ private static abstract class Impl implements Iterable<String>
+ {
+ protected Impl()
+ {
+ // Nothing to do.
+ }
+
+
+
+ public abstract int compareTo(Impl other);
+
+
+
+ public abstract boolean containsOption(String normalizedOption);
+
+
+
+ public abstract boolean equals(Impl other);
+
+
+
+ public abstract String firstNormalizedOption();
+
+
+
+ @Override
+ public abstract int hashCode();
+
+
+
+ public abstract boolean hasOptions();
+
+
+
+ public abstract boolean isSubTypeOf(Impl other);
+
+
+
+ public abstract boolean isSuperTypeOf(Impl other);
+
+
+
+ public abstract int size();
+
+ }
+
+
+
+ private static final class MultiOptionImpl extends Impl
+ {
+
+ private final String[] normalizedOptions;
+
+ private final String[] options;
+
+
+
+ private MultiOptionImpl(String[] options, String[] normalizedOptions)
+ {
+ if (normalizedOptions.length < 2)
+ {
+ throw new AssertionError();
+ }
+
+ this.options = options;
+ this.normalizedOptions = normalizedOptions;
+ }
+
+
+
+ @Override
+ public int compareTo(Impl other)
+ {
+ final int thisSize = normalizedOptions.length;
+ final int otherSize = other.size();
+
+ if (thisSize < otherSize)
+ {
+ return -1;
+ }
+ else if (thisSize > otherSize)
+ {
+ return 1;
+ }
+ else
+ {
+ // Same number of options.
+ final MultiOptionImpl otherImpl = (MultiOptionImpl) other;
+ for (int i = 0; i < thisSize; i++)
+ {
+ final String o1 = normalizedOptions[i];
+ final String o2 = otherImpl.normalizedOptions[i];
+ final int result = o1.compareTo(o2);
+ if (result != 0)
+ {
+ return result;
+ }
+ }
+
+ // All options the same.
+ return 0;
+ }
+ }
+
+
+
+ @Override
+ public boolean containsOption(String normalizedOption)
+ {
+ final int sz = normalizedOptions.length;
+ for (int i = 0; i < sz; i++)
+ {
+ if (normalizedOptions[i].equals(normalizedOption))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+
+ @Override
+ public boolean equals(Impl other)
+ {
+ if (other instanceof MultiOptionImpl)
+ {
+ final MultiOptionImpl tmp = (MultiOptionImpl) other;
+ return Arrays.equals(normalizedOptions, tmp.normalizedOptions);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+
+ @Override
+ public String firstNormalizedOption()
+ {
+ return normalizedOptions[0];
+ }
+
+
+
+ @Override
+ public int hashCode()
+ {
+ return Arrays.hashCode(normalizedOptions);
+ }
+
+
+
+ @Override
+ public boolean hasOptions()
+ {
+ return true;
+ }
+
+
+
+ @Override
+ public boolean isSubTypeOf(Impl other)
+ {
+ // Must contain a super-set of other's options.
+ if (other == ZERO_OPTION_IMPL)
+ {
+ return true;
+ }
+ else if (other.size() == 1)
+ {
+ return containsOption(other.firstNormalizedOption());
+ }
+ else if (other.size() > size())
+ {
+ return false;
+ }
+ else
+ {
+ // Check this contains other's options.
+ //
+ // This could be optimized more if required, but it's probably
+ // not worth it.
+ final MultiOptionImpl tmp = (MultiOptionImpl) other;
+ for (final String normalizedOption : tmp.normalizedOptions)
+ {
+ if (!containsOption(normalizedOption))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+
+
+ @Override
+ public boolean isSuperTypeOf(Impl other)
+ {
+ // Must contain a sub-set of other's options.
+ for (final String normalizedOption : normalizedOptions)
+ {
+ if (!other.containsOption(normalizedOption))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+
+ public Iterator<String> iterator()
+ {
+ return Iterators.arrayIterator(options);
+ }
+
+
+
+ @Override
+ public int size()
+ {
+ return normalizedOptions.length;
+ }
+
+ }
+
+
+
+ private static final class SingleOptionImpl extends Impl
+ {
+
+ private final String normalizedOption;
+
+ private final String option;
+
+
+
+ private SingleOptionImpl(String option, String normalizedOption)
+ {
+ this.option = option;
+ this.normalizedOption = normalizedOption;
+ }
+
+
+
+ @Override
+ public int compareTo(Impl other)
+ {
+ if (other == ZERO_OPTION_IMPL)
+ {
+ // If other has zero options then this sorts after.
+ return 1;
+ }
+ else if (other.size() == 1)
+ {
+ // Same number of options, so compare.
+ return normalizedOption
+ .compareTo(other.firstNormalizedOption());
+ }
+ else
+ {
+ // Other has more options, so comes after.
+ return -1;
+ }
+ }
+
+
+
+ @Override
+ public boolean containsOption(String normalizedOption)
+ {
+ return this.normalizedOption.equals(normalizedOption);
+ }
+
+
+
+ @Override
+ public boolean equals(Impl other)
+ {
+ return other.size() == 1
+ && other.containsOption(normalizedOption);
+ }
+
+
+
+ @Override
+ public String firstNormalizedOption()
+ {
+ return normalizedOption;
+ }
+
+
+
+ @Override
+ public int hashCode()
+ {
+ return normalizedOption.hashCode();
+ }
+
+
+
+ @Override
+ public boolean hasOptions()
+ {
+ return true;
+ }
+
+
+
+ @Override
+ public boolean isSubTypeOf(Impl other)
+ {
+ // Other must have no options or the same option.
+ if (other == ZERO_OPTION_IMPL)
+ {
+ return true;
+ }
+ else
+ {
+ return equals(other);
+ }
+ }
+
+
+
+ @Override
+ public boolean isSuperTypeOf(Impl other)
+ {
+ // Other must have this option.
+ return other.containsOption(normalizedOption);
+ }
+
+
+
+ public Iterator<String> iterator()
+ {
+ return Iterators.singleton(option);
+ }
+
+
+
+ @Override
+ public int size()
+ {
+ return 1;
+ }
+
+ }
+
+
+
+ private static final class ZeroOptionImpl extends Impl
+ {
+ private ZeroOptionImpl()
+ {
+ // Nothing to do.
+ }
+
+
+
+ @Override
+ public int compareTo(Impl other)
+ {
+ // If other has options then this sorts before.
+ return this == other ? 0 : -1;
+ }
+
+
+
+ @Override
+ public boolean containsOption(String normalizedOption)
+ {
+ return false;
+ }
+
+
+
+ @Override
+ public boolean equals(Impl other)
+ {
+ return this == other;
+ }
+
+
+
+ @Override
+ public String firstNormalizedOption()
+ {
+ // No first option.
+ return null;
+ }
+
+
+
+ @Override
+ public int hashCode()
+ {
+ // Use attribute type hash code.
+ return 0;
+ }
+
+
+
+ @Override
+ public boolean hasOptions()
+ {
+ return false;
+ }
+
+
+
+ @Override
+ public boolean isSubTypeOf(Impl other)
+ {
+ // Can only be a sub-type if other has no options.
+ return this == other;
+ }
+
+
+
+ @Override
+ public boolean isSuperTypeOf(Impl other)
+ {
+ // Will always be a super-type.
+ return true;
+ }
+
+
+
+ public Iterator<String> iterator()
+ {
+ return Iterators.empty();
+ }
+
+
+
+ @Override
+ public int size()
+ {
+ return 0;
+ }
+
+ }
+
+
+
+ private static final ThreadLocal<WeakHashMap<Schema, Map<String, AttributeDescription>>> CACHE = new ThreadLocal<WeakHashMap<Schema, Map<String, AttributeDescription>>>()
+ {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected WeakHashMap<Schema, Map<String, AttributeDescription>> initialValue()
+ {
+ return new WeakHashMap<Schema, Map<String, AttributeDescription>>();
+ }
+
+ };
+
+ // Object class attribute description.
+ private static final ZeroOptionImpl ZERO_OPTION_IMPL = new ZeroOptionImpl();
+
+ private static final AttributeDescription OBJECT_CLASS;
+ static
+ {
+ final AttributeType attributeType = Schema.getCoreSchema()
+ .getAttributeType("2.5.4.0");
+ OBJECT_CLASS = new AttributeDescription(attributeType
+ .getNameOrOID(), attributeType, ZERO_OPTION_IMPL);
+ }
+
+ // This is the size of the per-thread per-schema attribute description
+ // cache. We should be conservative here in case there are many
+ // threads.
+ private static final int ATTRIBUTE_DESCRIPTION_CACHE_SIZE = 512;
+
+
+
+ /**
+ * Creates an attribute description having the same attribute type and
+ * options as the provided attribute description and, in addition, the
+ * provided list of options.
+ *
+ * @param attributeDescription
+ * The attribute description.
+ * @param options
+ * The attribute options.
+ * @return The new attribute description containing {@code options}.
+ * @throws NullPointerException
+ * If {@code attributeDescription} or {@code options} was
+ * {@code null}.
+ */
+ public static AttributeDescription create(
+ AttributeDescription attributeDescription, String... options)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(attributeDescription, options);
+
+ // This should not be called very often, so don't optimize.
+ AttributeDescription newAttributeDescription = attributeDescription;
+ for (final String option : options)
+ {
+ newAttributeDescription = create(newAttributeDescription, option);
+ }
+ return newAttributeDescription;
+ }
+
+
+
+ /**
+ * Creates an attribute description having the same attribute type and
+ * options as the provided attribute description and, in addition, the
+ * provided new option.
+ *
+ * @param attributeDescription
+ * The attribute description.
+ * @param option
+ * The attribute option.
+ * @return The new attribute description containing {@code option}.
+ * @throws NullPointerException
+ * If {@code attributeDescription} or {@code option} was
+ * {@code null}.
+ */
+ public static AttributeDescription create(
+ AttributeDescription attributeDescription, String option)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(attributeDescription, option);
+
+ final String normalizedOption = toLowerCase(option);
+ if (attributeDescription.pimpl.containsOption(normalizedOption))
+ {
+ return attributeDescription;
+ }
+
+ final String oldAttributeDescription = attributeDescription.attributeDescription;
+ final StringBuilder builder = new StringBuilder(
+ oldAttributeDescription.length() + option.length() + 1);
+ builder.append(oldAttributeDescription);
+ builder.append(';');
+ builder.append(option);
+ final String newAttributeDescription = builder.toString();
+
+ final Impl impl = attributeDescription.pimpl;
+ if (impl instanceof ZeroOptionImpl)
+ {
+ return new AttributeDescription(newAttributeDescription,
+ attributeDescription.attributeType, new SingleOptionImpl(
+ option, normalizedOption));
+
+ }
+ else if (impl instanceof SingleOptionImpl)
+ {
+ final SingleOptionImpl simpl = (SingleOptionImpl) impl;
+
+ final String[] newOptions = new String[2];
+ newOptions[0] = simpl.option;
+ newOptions[1] = option;
+
+ final String[] newNormalizedOptions = new String[2];
+ if (normalizedOption.compareTo(simpl.normalizedOption) < 0)
+ {
+ newNormalizedOptions[0] = normalizedOption;
+ newNormalizedOptions[1] = simpl.normalizedOption;
+ }
+
+ return new AttributeDescription(newAttributeDescription,
+ attributeDescription.attributeType, new MultiOptionImpl(
+ newOptions, newNormalizedOptions));
+ }
+ else
+ {
+ final MultiOptionImpl mimpl = (MultiOptionImpl) impl;
+
+ final int sz1 = mimpl.options.length;
+ final String[] newOptions = new String[sz1 + 1];
+ for (int i = 0; i < sz1; i++)
+ {
+ newOptions[i] = mimpl.options[i];
+ }
+ newOptions[sz1] = option;
+
+ final int sz2 = mimpl.normalizedOptions.length;
+ final String[] newNormalizedOptions = new String[sz2 + 1];
+ boolean inserted = false;
+ for (int i = 0; i < sz2; i++)
+ {
+ if (!inserted)
+ {
+ final String s = mimpl.normalizedOptions[i];
+ if (normalizedOption.compareTo(s) < 0)
+ {
+ newNormalizedOptions[i] = normalizedOption;
+ newNormalizedOptions[i + 1] = s;
+ inserted = true;
+ }
+ else
+ {
+ newNormalizedOptions[i] = s;
+ }
+ }
+ else
+ {
+ newNormalizedOptions[i + 1] = mimpl.normalizedOptions[i];
+ }
+ }
+
+ if (!inserted)
+ {
+ newNormalizedOptions[sz2] = normalizedOption;
+ }
+
+ return new AttributeDescription(newAttributeDescription,
+ attributeDescription.attributeType, new MultiOptionImpl(
+ newOptions, newNormalizedOptions));
+ }
+ }
+
+
+
+ /**
+ * Creates an attribute description having the provided attribute type
+ * and no options.
+ *
+ * @param attributeType
+ * The attribute type.
+ * @return The attribute description.
+ * @throws NullPointerException
+ * If {@code attributeType} was {@code null}.
+ */
+ public static AttributeDescription create(AttributeType attributeType)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(attributeType);
+
+ // Use object identity in case attribute type does not come from
+ // core schema.
+ if (attributeType == OBJECT_CLASS.getAttributeType())
+ {
+ return OBJECT_CLASS;
+ }
+ else
+ {
+ return new AttributeDescription(attributeType.getNameOrOID(),
+ attributeType, ZERO_OPTION_IMPL);
+ }
+ }
+
+
+
+ /**
+ * Creates an attribute description having the provided attribute type
+ * and single option.
+ *
+ * @param attributeType
+ * The attribute type.
+ * @param option
+ * The attribute option.
+ * @return The attribute description.
+ * @throws NullPointerException
+ * If {@code attributeType} or {@code option} was {@code
+ * null}.
+ */
+ public static AttributeDescription create(
+ AttributeType attributeType, String option)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(attributeType, option);
+
+ final String oid = attributeType.getNameOrOID();
+ final StringBuilder builder = new StringBuilder(oid.length()
+ + option.length() + 1);
+ builder.append(oid);
+ builder.append(';');
+ builder.append(option);
+ final String attributeDescription = builder.toString();
+ final String normalizedOption = toLowerCase(option);
+
+ return new AttributeDescription(attributeDescription,
+ attributeType, new SingleOptionImpl(option, normalizedOption));
+ }
+
+
+
+ /**
+ * Creates an attribute description having the provided attribute type
+ * and options.
+ *
+ * @param attributeType
+ * The attribute type.
+ * @param options
+ * The attribute options.
+ * @return The attribute description.
+ * @throws NullPointerException
+ * If {@code attributeType} or {@code options} was {@code
+ * null}.
+ */
+ public static AttributeDescription create(
+ AttributeType attributeType, String... options)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(attributeType, options);
+
+ switch (options.length)
+ {
+ case 0:
+ return create(attributeType);
+ case 1:
+ return create(attributeType, options[0]);
+ default:
+ final String[] optionsList = new String[options.length];
+ final String[] normalizedOptions = new String[options.length];
+
+ final String oid = attributeType.getNameOrOID();
+ final StringBuilder builder = new StringBuilder(oid.length()
+ + options[0].length() + options[1].length() + 2);
+ builder.append(oid);
+
+ int i = 0;
+ for (final String option : options)
+ {
+ builder.append(';');
+ builder.append(option);
+ optionsList[i] = option;
+ final String normalizedOption = toLowerCase(option);
+ normalizedOptions[i++] = normalizedOption;
+ }
+ Arrays.sort(normalizedOptions);
+
+ final String attributeDescription = builder.toString();
+ return new AttributeDescription(attributeDescription,
+ attributeType, new MultiOptionImpl(optionsList,
+ normalizedOptions));
+ }
+
+ }
+
+
+
+ /**
+ * Returns an attribute description representing the object class
+ * attribute type with no options.
+ *
+ * @return The object class attribute description.
+ */
+ public static AttributeDescription objectClass()
+ {
+ return OBJECT_CLASS;
+ }
+
+
+
+ /**
+ * Parses the provided LDAP string representation of an attribute
+ * description using the default schema.
+ *
+ * @param attributeDescription
+ * The LDAP string representation of an attribute
+ * description.
+ * @return The parsed attribute description.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code attributeDescription} is not a valid LDAP
+ * string representation of an attribute description.
+ * @throws NullPointerException
+ * If {@code attributeDescription} was {@code null}.
+ */
+ public static AttributeDescription valueOf(String attributeDescription)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ return valueOf(attributeDescription, Schema.getDefaultSchema());
+ }
+
+
+
+ /**
+ * Parses the provided LDAP string representation of an attribute
+ * description using the provided schema.
+ *
+ * @param attributeDescription
+ * The LDAP string representation of an attribute
+ * description.
+ * @param schema
+ * The schema to use when parsing the attribute description.
+ * @return The parsed attribute description.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code attributeDescription} is not a valid LDAP
+ * string representation of an attribute description.
+ * @throws NullPointerException
+ * If {@code attributeDescription} or {@code schema} was
+ * {@code null}.
+ */
+ @SuppressWarnings("serial")
+ public static AttributeDescription valueOf(
+ String attributeDescription, Schema schema)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ Validator.ensureNotNull(attributeDescription, schema);
+
+ // First look up the attribute description in the cache.
+ final WeakHashMap<Schema, Map<String, AttributeDescription>> threadLocalMap = CACHE
+ .get();
+ Map<String, AttributeDescription> schemaLocalMap = threadLocalMap
+ .get(schema);
+
+ AttributeDescription ad = null;
+ if (schemaLocalMap == null)
+ {
+ schemaLocalMap = new LinkedHashMap<String, AttributeDescription>(
+ ATTRIBUTE_DESCRIPTION_CACHE_SIZE, 0.75f, true)
+ {
+ @Override
+ protected boolean removeEldestEntry(
+ Map.Entry<String, AttributeDescription> eldest)
+ {
+ return size() > ATTRIBUTE_DESCRIPTION_CACHE_SIZE;
+ }
+ };
+ threadLocalMap.put(schema, schemaLocalMap);
+ }
+ else
+ {
+ ad = schemaLocalMap.get(attributeDescription);
+ }
+
+ // Cache miss: decode and cache.
+ if (ad == null)
+ {
+ ad = valueOf0(attributeDescription, schema);
+ schemaLocalMap.put(attributeDescription, ad);
+ }
+
+ return ad;
+ }
+
+
+
+ private static int skipTrailingWhiteSpace(
+ String attributeDescription, int i, int length)
+ throws LocalizedIllegalArgumentException
+ {
+ char c;
+ while (i < length)
+ {
+ c = attributeDescription.charAt(i);
+ if (c != ' ')
+ {
+ final Message message = ERR_ATTRIBUTE_DESCRIPTION_INTERNAL_WHITESPACE
+ .get(attributeDescription);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ i++;
+ }
+ return i;
+ }
+
+
+
+ // Uncached valueOf implementation.
+ private static AttributeDescription valueOf0(
+ String attributeDescription, Schema schema)
+ throws LocalizedIllegalArgumentException
+ {
+ int i = 0;
+ final int length = attributeDescription.length();
+ char c = 0;
+
+ // Skip leading white space.
+ while (i < length)
+ {
+ c = attributeDescription.charAt(i);
+ if (c != ' ')
+ {
+ break;
+ }
+ i++;
+ }
+
+ // If we're already at the end then the attribute description only
+ // contained whitespace.
+ if (i == length)
+ {
+ final Message message = ERR_ATTRIBUTE_DESCRIPTION_EMPTY
+ .get(attributeDescription);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // Validate the first non-whitespace character.
+ ASCIICharProp cp = ASCIICharProp.valueOf(c);
+ if (cp == null)
+ {
+ final Message message = ERR_ATTRIBUTE_DESCRIPTION_ILLEGAL_CHARACTER
+ .get(attributeDescription, c, i);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // Mark the attribute type start position.
+ final int attributeTypeStart = i;
+ if (cp.isLetter())
+ {
+ // Non-numeric OID: letter + zero or more keychars.
+ i++;
+ while (i < length)
+ {
+ c = attributeDescription.charAt(i);
+
+ if (c == ';' || c == ' ')
+ {
+ break;
+ }
+
+ cp = ASCIICharProp.valueOf(c);
+ if (!cp.isKeyChar())
+ {
+ final Message message = ERR_ATTRIBUTE_DESCRIPTION_ILLEGAL_CHARACTER
+ .get(attributeDescription, c, i);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ i++;
+ }
+
+ // (charAt(i) == ';' || c == ' ' || i == length)
+ }
+ else if (cp.isDigit())
+ {
+ // Numeric OID: decimal digit + zero or more dots or decimals.
+ i++;
+ while (i < length)
+ {
+ c = attributeDescription.charAt(i);
+ if (c == ';' || c == ' ')
+ {
+ break;
+ }
+
+ cp = ASCIICharProp.valueOf(c);
+ if (c != '.' && !cp.isDigit())
+ {
+ final Message message = ERR_ATTRIBUTE_DESCRIPTION_ILLEGAL_CHARACTER
+ .get(attributeDescription, c, i);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ i++;
+ }
+
+ // (charAt(i) == ';' || charAt(i) == ' ' || i == length)
+ }
+ else
+ {
+ final Message message = ERR_ATTRIBUTE_DESCRIPTION_ILLEGAL_CHARACTER
+ .get(attributeDescription, c, i);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // Skip trailing white space.
+ final int attributeTypeEnd = i;
+ if (c == ' ')
+ {
+ i = skipTrailingWhiteSpace(attributeDescription, i + 1, length);
+ }
+
+ // Determine the portion of the string containing the attribute type
+ // name.
+ String oid;
+ if (attributeTypeStart == 0 && attributeTypeEnd == length)
+ {
+ oid = attributeDescription;
+ }
+ else
+ {
+ oid = attributeDescription.substring(attributeTypeStart,
+ attributeTypeEnd);
+ }
+
+ if (oid.length() == 0)
+ {
+ final Message message = ERR_ATTRIBUTE_DESCRIPTION_NO_TYPE
+ .get(attributeDescription);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // Get the attribute type from the schema.
+ AttributeType attributeType;
+ try
+ {
+ attributeType = schema.getAttributeType(oid);
+ }
+ catch (final UnknownSchemaElementException e)
+ {
+ final Message message = ERR_ATTRIBUTE_DESCRIPTION_TYPE_NOT_FOUND
+ .get(attributeDescription, e.getMessageObject());
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // If we're already at the end of the attribute description then it
+ // does not contain any options.
+ if (i == length)
+ {
+ // Use object identity in case attribute type does not come from
+ // core schema.
+ if (attributeType == OBJECT_CLASS.getAttributeType()
+ && attributeDescription.equals(OBJECT_CLASS.toString()))
+ {
+ return OBJECT_CLASS;
+ }
+ else
+ {
+ return new AttributeDescription(attributeDescription,
+ attributeType, ZERO_OPTION_IMPL);
+ }
+ }
+
+ // At this point 'i' must point at a semi-colon.
+ i++;
+ StringBuilder builder = null;
+ int optionStart = i;
+ while (i < length)
+ {
+ c = attributeDescription.charAt(i);
+ if (c == ' ' || c == ';')
+ {
+ break;
+ }
+
+ cp = ASCIICharProp.valueOf(c);
+ if (!cp.isKeyChar())
+ {
+ final Message message = ERR_ATTRIBUTE_DESCRIPTION_ILLEGAL_CHARACTER
+ .get(attributeDescription, c, i);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ if (builder == null)
+ {
+ if (cp.isUpperCase())
+ {
+ // Need to normalize the option.
+ builder = new StringBuilder(length - optionStart);
+ builder.append(attributeDescription, optionStart, i);
+ builder.append(cp.toLowerCase());
+ }
+ }
+ else
+ {
+ builder.append(cp.toLowerCase());
+ }
+ i++;
+ }
+
+ String option = attributeDescription.substring(optionStart, i);
+ String normalizedOption;
+ if (builder != null)
+ {
+ normalizedOption = builder.toString();
+ }
+ else
+ {
+ normalizedOption = option;
+ }
+
+ if (option.length() == 0)
+ {
+ final Message message = ERR_ATTRIBUTE_DESCRIPTION_EMPTY_OPTION
+ .get(attributeDescription);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // Skip trailing white space.
+ if (c == ' ')
+ {
+ i = skipTrailingWhiteSpace(attributeDescription, i + 1, length);
+ }
+
+ // If we're already at the end of the attribute description then it
+ // only contains a single option.
+ if (i == length)
+ {
+ return new AttributeDescription(attributeDescription,
+ attributeType, new SingleOptionImpl(option, normalizedOption));
+ }
+
+ // Multiple options need sorting and duplicates removed - we could
+ // optimize a bit further here for 2 option attribute descriptions.
+ final List<String> options = new LinkedList<String>();
+ options.add(option);
+
+ final SortedSet<String> normalizedOptions = new TreeSet<String>();
+ normalizedOptions.add(normalizedOption);
+
+ while (i < length)
+ {
+ // At this point 'i' must point at a semi-colon.
+ i++;
+ builder = null;
+ optionStart = i;
+ while (i < length)
+ {
+ c = attributeDescription.charAt(i);
+ if (c == ' ' || c == ';')
+ {
+ break;
+ }
+
+ cp = ASCIICharProp.valueOf(c);
+ if (!cp.isKeyChar())
+ {
+ final Message message = ERR_ATTRIBUTE_DESCRIPTION_ILLEGAL_CHARACTER
+ .get(attributeDescription, c, i);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ if (builder == null)
+ {
+ if (cp.isUpperCase())
+ {
+ // Need to normalize the option.
+ builder = new StringBuilder(length - optionStart);
+ builder.append(attributeDescription, optionStart, i);
+ builder.append(cp.toLowerCase());
+ }
+ }
+ else
+ {
+ builder.append(cp.toLowerCase());
+ }
+ i++;
+ }
+
+ option = attributeDescription.substring(optionStart, i);
+ if (builder != null)
+ {
+ normalizedOption = builder.toString();
+ }
+ else
+ {
+ normalizedOption = option;
+ }
+
+ if (option.length() == 0)
+ {
+ final Message message = ERR_ATTRIBUTE_DESCRIPTION_EMPTY_OPTION
+ .get(attributeDescription);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // Skip trailing white space.
+ if (c == ' ')
+ {
+ i = skipTrailingWhiteSpace(attributeDescription, i + 1, length);
+ }
+
+ options.add(option);
+ normalizedOptions.add(normalizedOption);
+ }
+
+ return new AttributeDescription(attributeDescription,
+ attributeType, new MultiOptionImpl(options
+ .toArray(new String[options.size()]), normalizedOptions
+ .toArray(new String[normalizedOptions.size()])));
+ }
+
+
+
+ private final String attributeDescription;
+
+ private final AttributeType attributeType;
+
+ private final Impl pimpl;
+
+
+
+ // Private constructor.
+ private AttributeDescription(String attributeDescription,
+ AttributeType attributeType, Impl pimpl)
+ {
+ this.attributeDescription = attributeDescription;
+ this.attributeType = attributeType;
+ this.pimpl = pimpl;
+ }
+
+
+
+ /**
+ * Compares this attribute description to the provided attribute
+ * description. The attribute types are compared first and then, if
+ * equal, the options are normalized, sorted, and compared.
+ *
+ * @param other
+ * The attribute description to be compared.
+ * @return A negative integer, zero, or a positive integer as this
+ * attribute description is less than, equal to, or greater
+ * than the specified attribute description.
+ * @throws NullPointerException
+ * If {@code name} was {@code null}.
+ */
+ public int compareTo(AttributeDescription other)
+ throws NullPointerException
+ {
+ final int result = attributeType.compareTo(other.attributeType);
+ if (result != 0)
+ {
+ return result;
+ }
+ else
+ {
+ // Attribute type is the same, so compare options.
+ return pimpl.compareTo(other.pimpl);
+ }
+ }
+
+
+
+ /**
+ * Indicates whether or not this attribute description contains the
+ * provided option.
+ *
+ * @param option
+ * The option for which to make the determination.
+ * @return {@code true} if this attribute description has the provided
+ * option, or {@code false} if not.
+ * @throws NullPointerException
+ * If {@code option} was {@code null}.
+ */
+ public boolean containsOption(String option)
+ throws NullPointerException
+ {
+ final String normalizedOption = toLowerCase(option);
+ return pimpl.containsOption(normalizedOption);
+ }
+
+
+
+ /**
+ * Indicates whether the provided object is an attribute description
+ * which is equal to this attribute description. It will be considered
+ * equal if the attribute type and normalized sorted list of options
+ * are identical.
+ *
+ * @param o
+ * The object for which to make the determination.
+ * @return {@code true} if the provided object is an attribute
+ * description that is equal to this attribute description, or
+ * {@code false} if not.
+ */
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ {
+ return true;
+ }
+
+ if (!(o instanceof AttributeDescription))
+ {
+ return false;
+ }
+
+ final AttributeDescription other = (AttributeDescription) o;
+ if (!attributeType.equals(other.attributeType))
+ {
+ return false;
+ }
+
+ // Attribute type is the same, compare options.
+ return pimpl.equals(other.pimpl);
+ }
+
+
+
+ /**
+ * Returns the attribute type associated with this attribute
+ * description.
+ *
+ * @return The attribute type associated with this attribute
+ * description.
+ */
+ public AttributeType getAttributeType()
+ {
+ return attributeType;
+ }
+
+
+
+ /**
+ * Returns an {@code Iterable} containing the options contained in
+ * this attribute description. Attempts to remove options using an
+ * iterator's {@code remove()} method are not permitted and will
+ * result in an {@code UnsupportedOperationException} being thrown.
+ *
+ * @return An {@code Iterable} containing the options.
+ */
+ public Iterable<String> getOptions()
+ {
+ return pimpl;
+ }
+
+
+
+ /**
+ * Returns the hash code for this attribute description. It will be
+ * calculated as the sum of the hash codes of the attribute type and
+ * normalized sorted list of options.
+ *
+ * @return The hash code for this attribute description.
+ */
+ @Override
+ public int hashCode()
+ {
+ // FIXME: should we cache this?
+ return attributeType.hashCode() * 31 + pimpl.hashCode();
+ }
+
+
+
+ /**
+ * Indicates whether or not this attribute description has any
+ * options.
+ *
+ * @return {@code true} if this attribute description has any options,
+ * or {@code false} if not.
+ */
+ public boolean hasOptions()
+ {
+ return pimpl.hasOptions();
+ }
+
+
+
+ /**
+ * Indicates whether or not this attribute description is the {@code
+ * objectClass} attribute description with no options.
+ *
+ * @return {@code true} if this attribute description is the {@code
+ * objectClass} attribute description with no options, or
+ * {@code false} if not.
+ */
+ public boolean isObjectClass()
+ {
+ return attributeType.isObjectClass() && !hasOptions();
+ }
+
+
+
+ /**
+ * Indicates whether or not this attribute description is a sub-type
+ * of the provided attribute description as defined in RFC 4512
+ * section 2.5. Specifically, this method will return {@code true} if
+ * and only if the following conditions are both {@code true}:
+ * <ul>
+ * <li>This attribute description has an attribute type which is equal
+ * to, or is a sub-type of, the attribute type in the provided
+ * attribute description.
+ * <li>This attribute description contains all of the options
+ * contained in the provided attribute description.
+ * </ul>
+ * Note that this method will return {@code true} if this attribute
+ * description is equal to the provided attribute description.
+ *
+ * @param other
+ * The attribute description for which to make the
+ * determination.
+ * @return {@code true} if this attribute description is a sub-type of
+ * the provided attribute description, or {@code false} if
+ * not.
+ * @throws NullPointerException
+ * If {@code name} was {@code null}.
+ */
+ public boolean isSubTypeOf(AttributeDescription other)
+ throws NullPointerException
+ {
+ if (!attributeType.isSubTypeOf(other.attributeType))
+ {
+ return false;
+ }
+ else
+ {
+ return pimpl.isSubTypeOf(other.pimpl);
+ }
+ }
+
+
+
+ /**
+ * Indicates whether or not this attribute description is a super-type
+ * of the provided attribute description as defined in RFC 4512
+ * section 2.5. Specifically, this method will return {@code true} if
+ * and only if the following conditions are both {@code true}:
+ * <ul>
+ * <li>This attribute description has an attribute type which is equal
+ * to, or is a super-type of, the attribute type in the provided
+ * attribute description.
+ * <li>This attribute description contains a sub-set of the options
+ * contained in the provided attribute description.
+ * </ul>
+ * Note that this method will return {@code true} if this attribute
+ * description is equal to the provided attribute description.
+ *
+ * @param other
+ * The attribute description for which to make the
+ * determination.
+ * @return {@code true} if this attribute description is a super-type
+ * of the provided attribute description, or {@code false} if
+ * not.
+ * @throws NullPointerException
+ * If {@code name} was {@code null}.
+ */
+ public boolean isSuperTypeOf(AttributeDescription other)
+ throws NullPointerException
+ {
+ if (!other.attributeType.isSubTypeOf(attributeType))
+ {
+ return false;
+ }
+ else
+ {
+ return pimpl.isSuperTypeOf(other.pimpl);
+ }
+ }
+
+
+
+ /**
+ * Returns the string representation of this attribute description as
+ * defined in RFC4512 section 2.5.
+ *
+ * @return The string representation of this attribute description.
+ */
+ @Override
+ public String toString()
+ {
+ return attributeDescription;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/AuthenticatedConnectionFactory.java b/sdk/src/org/opends/sdk/AuthenticatedConnectionFactory.java
new file mode 100644
index 0000000..1bb8ed5
--- /dev/null
+++ b/sdk/src/org/opends/sdk/AuthenticatedConnectionFactory.java
@@ -0,0 +1,702 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.opends.sdk.requests.*;
+import org.opends.sdk.responses.BindResult;
+import org.opends.sdk.responses.CompareResult;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * An authenticated connection factory can be used to create
+ * pre-authenticated connections to a Directory Server.
+ * <p>
+ * The connections returned by an authenticated connection factory
+ * support all operations with the exception of Bind requests. Attempts
+ * to perform a Bind will result in an {@code
+ * UnsupportedOperationException}.
+ * <p>
+ * In addition, the returned connections support retrieval of the
+ * {@code BindResult} returned from the initial Bind request, or last
+ * rebind.
+ * <p>
+ * Support for connection re-authentication is provided through the
+ * {@link #setRebindAllowed} method which, if set to {@code true},
+ * causes subsequent connections created using the factory to support
+ * the {@code rebind} method.
+ * <p>
+ * If the Bind request fails for some reason (e.g. invalid credentials),
+ * then the connection attempt will fail and an {@code
+ * ErrorResultException} will be thrown.
+ */
+public final class AuthenticatedConnectionFactory
+ implements
+ ConnectionFactory<AuthenticatedConnectionFactory.AuthenticatedAsynchronousConnection>
+{
+ // We implement the factory using the pimpl idiom in order have
+ // cleaner Javadoc which does not expose implementation methods from
+ // AbstractConnectionFactory.
+
+ private static final class Impl
+ extends
+ AbstractConnectionFactory<AuthenticatedConnectionFactory.AuthenticatedAsynchronousConnection>
+ implements
+ ConnectionFactory<AuthenticatedConnectionFactory.AuthenticatedAsynchronousConnection>
+ {
+ private final BindRequest request;
+
+ private final ConnectionFactory<?> parentFactory;
+
+ private boolean allowRebinds = false;
+
+
+
+ private Impl(ConnectionFactory<?> factory, BindRequest request)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(factory, request);
+ this.parentFactory = factory;
+
+ // FIXME: should do a defensive copy.
+ this.request = request;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <P> ConnectionFuture<AuthenticatedAsynchronousConnection> getAsynchronousConnection(
+ ConnectionResultHandler<? super AuthenticatedAsynchronousConnection, P> handler,
+ P p)
+ {
+ ConnectionFutureImpl<P> future = new ConnectionFutureImpl<P>(
+ allowRebinds ? request : null, handler, p);
+ future.connectFuture = parentFactory.getAsynchronousConnection(
+ future, null);
+ return future;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public AuthenticatedConnection getConnection()
+ throws ErrorResultException
+ {
+ return new AuthenticatedConnection(
+ blockingGetAsynchronousConnection());
+ }
+
+ }
+
+
+
+ private final Impl impl;
+
+
+
+ /**
+ * An authenticated synchronous connection supports all operations
+ * except Bind operations.
+ */
+ public static final class AuthenticatedConnection extends
+ SynchronousConnection
+ {
+ private final AuthenticatedAsynchronousConnection connection;
+
+
+
+ private AuthenticatedConnection(
+ AuthenticatedAsynchronousConnection connection)
+ {
+ super(connection);
+ this.connection = connection;
+ }
+
+
+
+ /**
+ * Bind operations are not supported by pre-authenticated
+ * connections. This method will always throw {@code
+ * UnsupportedOperationException}.
+ */
+ public BindResult bind(BindRequest request)
+ throws UnsupportedOperationException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ /**
+ * Bind operations are not supported by pre-authenticated
+ * connections. This method will always throw {@code
+ * UnsupportedOperationException}.
+ */
+ public BindResult bind(String name, String password)
+ throws UnsupportedOperationException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ /**
+ * Re-authenticates to the Directory Server using the bind request
+ * associated with this connection. If re-authentication fails for
+ * some reason then this connection will be automatically closed.
+ *
+ * @return The result of the operation.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed
+ * for some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws UnsupportedOperationException
+ * If this connection does not support rebind operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ */
+ public BindResult rebind() throws ErrorResultException,
+ InterruptedException, UnsupportedOperationException,
+ IllegalStateException
+ {
+
+ if (connection.request == null)
+ {
+ throw new UnsupportedOperationException();
+ }
+ return super.bind(connection.request);
+ }
+
+
+
+ /**
+ * Returns an unmodifiable view of the Bind result which was
+ * returned from the server after authentication.
+ *
+ * @return The Bind result which was returned from the server after
+ * authentication.
+ */
+ public BindResult getAuthenticatedBindResult()
+ {
+ return connection.getAuthenticatedBindResult();
+ }
+ }
+
+
+
+ /**
+ * An authenticated asynchronous connection supports all operations
+ * except Bind operations.
+ */
+ public static final class AuthenticatedAsynchronousConnection
+ implements AsynchronousConnection
+ {
+
+ private final BindRequest request;
+
+ private volatile BindResult result;
+
+ private final AsynchronousConnection connection;
+
+
+
+ private AuthenticatedAsynchronousConnection(
+ AsynchronousConnection connection, BindRequest request,
+ BindResult result)
+ {
+ this.connection = connection;
+ this.request = request;
+ this.result = result;
+ }
+
+
+
+ /**
+ * Returns an unmodifiable view of the Bind result which was
+ * returned from the server after authentication.
+ *
+ * @return The Bind result which was returned from the server after
+ * authentication.
+ */
+ public BindResult getAuthenticatedBindResult()
+ {
+ return result;
+ }
+
+
+
+ public void abandon(AbandonRequest request)
+ throws UnsupportedOperationException, IllegalStateException,
+ NullPointerException
+ {
+ connection.abandon(request);
+ }
+
+
+
+ public <P> ResultFuture<Result> add(AddRequest request,
+ ResultHandler<Result, P> handler, P p)
+ throws UnsupportedOperationException, IllegalStateException,
+ NullPointerException
+ {
+ return connection.add(request, handler, p);
+ }
+
+
+
+ public void addConnectionEventListener(
+ ConnectionEventListener listener) throws IllegalStateException,
+ NullPointerException
+ {
+ connection.addConnectionEventListener(listener);
+ }
+
+
+
+ /**
+ * Bind operations are not supported by pre-authenticated
+ * connections. This method will always throw {@code
+ * UnsupportedOperationException}.
+ */
+ public <P> ResultFuture<BindResult> bind(BindRequest request,
+ ResultHandler<? super BindResult, P> handler, P p)
+ throws UnsupportedOperationException, IllegalStateException,
+ NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ public void close()
+ {
+ connection.close();
+ }
+
+
+
+ public void close(UnbindRequest request)
+ throws NullPointerException
+ {
+ connection.close(request);
+ }
+
+
+
+ public <P> ResultFuture<CompareResult> compare(
+ CompareRequest request,
+ ResultHandler<? super CompareResult, P> handler, P p)
+ throws UnsupportedOperationException, IllegalStateException,
+ NullPointerException
+ {
+ return connection.compare(request, handler, p);
+ }
+
+
+
+ public <P> ResultFuture<Result> delete(DeleteRequest request,
+ ResultHandler<Result, P> handler, P p)
+ throws UnsupportedOperationException, IllegalStateException,
+ NullPointerException
+ {
+ return connection.delete(request, handler, p);
+ }
+
+
+
+ public <R extends Result, P> ResultFuture<R> extendedRequest(
+ ExtendedRequest<R> request,
+ ResultHandler<? super R, P> handler, P p)
+ throws UnsupportedOperationException, IllegalStateException,
+ NullPointerException
+ {
+ return connection.extendedRequest(request, handler, p);
+ }
+
+
+
+ public <P> ResultFuture<Result> modify(ModifyRequest request,
+ ResultHandler<Result, P> handler, P p)
+ throws UnsupportedOperationException, IllegalStateException,
+ NullPointerException
+ {
+ return connection.modify(request, handler, p);
+ }
+
+
+
+ public <P> ResultFuture<Result> modifyDN(ModifyDNRequest request,
+ ResultHandler<Result, P> handler, P p)
+ throws UnsupportedOperationException, IllegalStateException,
+ NullPointerException
+ {
+ return connection.modifyDN(request, handler, p);
+ }
+
+
+
+ /**
+ * Re-authenticates to the Directory Server using the bind request
+ * associated with this connection. If re-authentication fails for
+ * some reason then this connection will be automatically closed.
+ *
+ * @param <P>
+ * The type of the additional parameter to the handler's
+ * methods.
+ * @param handler
+ * A result handler which can be used to asynchronously
+ * process the operation result when it is received, may be
+ * {@code null}.
+ * @param p
+ * Optional additional handler parameter.
+ * @return A future representing the result of the operation.
+ * @throws UnsupportedOperationException
+ * If this connection does not support rebind operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ */
+ public <P> ResultFuture<BindResult> rebind(
+ ResultHandler<? super BindResult, P> handler, P p)
+ throws UnsupportedOperationException, IllegalStateException
+ {
+ if (request == null)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ // Wrap the client handler so that we can update the connection
+ // state.
+ final ResultHandler<? super BindResult, P> clientHandler = handler;
+ final P clientParameter = p;
+
+ ResultHandler<BindResult, Void> handlerWrapper = new ResultHandler<BindResult, Void>()
+ {
+
+ public void handleErrorResult(Void p, ErrorResultException error)
+ {
+ // This connection is now unauthenticated so prevent
+ // further use.
+ connection.close();
+
+ if (clientHandler != null)
+ {
+ clientHandler.handleErrorResult(clientParameter, error);
+ }
+ }
+
+
+
+ public void handleResult(Void p, BindResult result)
+ {
+ // Save the result.
+ AuthenticatedAsynchronousConnection.this.result = result;
+
+ if (clientHandler != null)
+ {
+ clientHandler.handleResult(clientParameter, result);
+ }
+ }
+
+ };
+
+ return connection.bind(request, handlerWrapper, null);
+ }
+
+
+
+ public void removeConnectionEventListener(
+ ConnectionEventListener listener) throws NullPointerException
+ {
+ connection.removeConnectionEventListener(listener);
+ }
+
+
+
+ public <P> ResultFuture<Result> search(SearchRequest request,
+ ResultHandler<Result, P> resultHandler,
+ SearchResultHandler<P> searchResulthandler, P p)
+ throws UnsupportedOperationException, IllegalStateException,
+ NullPointerException
+ {
+ return connection.search(request, resultHandler,
+ searchResulthandler, p);
+ }
+
+ }
+
+
+
+ /**
+ * Creates a new authenticated connection factory which will obtain
+ * connections using the provided connection factory and immediately
+ * perform the provided Bind request.
+ *
+ * @param factory
+ * The connection factory to use for connecting to the
+ * Directory Server.
+ * @param request
+ * The Bind request to use for authentication.
+ * @throws NullPointerException
+ * If {@code factory} or {@code request} was {@code null}.
+ */
+ public AuthenticatedConnectionFactory(ConnectionFactory<?> factory,
+ BindRequest request) throws NullPointerException
+ {
+ impl = new Impl(factory, request);
+ }
+
+
+
+ private static final class ConnectionFutureImpl<P> implements
+ ConnectionFuture<AuthenticatedAsynchronousConnection>,
+ ConnectionResultHandler<AsynchronousConnection, Void>,
+ ResultHandler<BindResult, Void>
+ {
+ private volatile AuthenticatedAsynchronousConnection authenticatedConnection;
+
+ private volatile AsynchronousConnection connection;
+
+ private volatile ErrorResultException exception;
+
+ private volatile ConnectionFuture<?> connectFuture;
+
+ private volatile ResultFuture<BindResult> bindFuture;
+
+ private final CountDownLatch latch = new CountDownLatch(1);
+
+ private final ConnectionResultHandler<? super AuthenticatedAsynchronousConnection, P> handler;
+
+ private final P p;
+
+ private boolean cancelled;
+
+ private final BindRequest request;
+
+
+
+ private ConnectionFutureImpl(
+ BindRequest request,
+ ConnectionResultHandler<? super AuthenticatedAsynchronousConnection, P> handler,
+ P p)
+ {
+ this.request = request;
+ this.handler = handler;
+ this.p = p;
+ }
+
+
+
+ public boolean cancel(boolean mayInterruptIfRunning)
+ {
+ cancelled = connectFuture.cancel(mayInterruptIfRunning)
+ || bindFuture != null
+ && bindFuture.cancel(mayInterruptIfRunning);
+ if (cancelled)
+ {
+ latch.countDown();
+ }
+ return cancelled;
+ }
+
+
+
+ public AuthenticatedAsynchronousConnection get()
+ throws InterruptedException, ErrorResultException
+ {
+ latch.await();
+ if (cancelled)
+ {
+ throw new CancellationException();
+ }
+ if (exception != null)
+ {
+ throw exception;
+ }
+ return authenticatedConnection;
+ }
+
+
+
+ public AuthenticatedAsynchronousConnection get(long timeout,
+ TimeUnit unit) throws InterruptedException, TimeoutException,
+ ErrorResultException
+ {
+ latch.await(timeout, unit);
+ if (cancelled)
+ {
+ throw new CancellationException();
+ }
+ if (exception != null)
+ {
+ throw exception;
+ }
+ return authenticatedConnection;
+ }
+
+
+
+ public boolean isCancelled()
+ {
+ return cancelled;
+ }
+
+
+
+ public boolean isDone()
+ {
+ return latch.getCount() == 0;
+ }
+
+
+
+ public void handleConnection(Void v,
+ AsynchronousConnection connection)
+ {
+ this.connection = connection;
+ this.bindFuture = this.connection.bind(request, this, null);
+ }
+
+
+
+ public void handleConnectionError(Void v, ErrorResultException error)
+ {
+ exception = error;
+ latch.countDown();
+ }
+
+
+
+ public void handleResult(Void v, BindResult result)
+ {
+ // FIXME: should make the result unmodifiable.
+ authenticatedConnection = new AuthenticatedAsynchronousConnection(
+ connection, request, result);
+ latch.countDown();
+ if (handler != null)
+ {
+ handler.handleConnection(p, authenticatedConnection);
+ }
+ }
+
+
+
+ public void handleErrorResult(Void v, ErrorResultException error)
+ {
+ // Ensure that the connection is closed.
+ try
+ {
+ connection.close();
+ }
+ catch (Exception e)
+ {
+ // Ignore.
+ }
+
+ exception = error;
+ latch.countDown();
+ if (handler != null)
+ {
+ handler.handleConnectionError(p, exception);
+ }
+ }
+ }
+
+
+
+ /**
+ * Specifies whether or not rebind requests are to be supported by
+ * connections created by this authenticated connection factory.
+ * <p>
+ * Rebind requests are invoked using the connection's {@code rebind}
+ * method which will throw an {@code UnsupportedOperationException} if
+ * rebinds are not supported (the default).
+ *
+ * @param allowRebinds
+ * {@code true} if the {@code rebind} operation is to be
+ * supported, otherwise {@code false}.
+ * @return A reference to this connection factory.
+ */
+ public AuthenticatedConnectionFactory setRebindAllowed(
+ boolean allowRebinds)
+ {
+ impl.allowRebinds = allowRebinds;
+ return this;
+ }
+
+
+
+ /**
+ * Indicates whether or not rebind requests are to be supported by
+ * connections created by this authenticated connection factory.
+ * <p>
+ * Rebind requests are invoked using the connection's {@code rebind}
+ * method which will throw an {@code UnsupportedOperationException} if
+ * rebinds are not supported (the default).
+ *
+ * @return allowRebinds {@code true} if the {@code rebind} operation
+ * is to be supported, otherwise {@code false}.
+ */
+ public boolean isRebindAllowed()
+ {
+ return impl.allowRebinds;
+ }
+
+
+
+ public <P> ConnectionFuture<AuthenticatedAsynchronousConnection> getAsynchronousConnection(
+ ConnectionResultHandler<? super AuthenticatedAsynchronousConnection, P> handler,
+ P p)
+ {
+ return impl.getAsynchronousConnection(handler, p);
+ }
+
+
+
+ public AuthenticatedConnection getConnection()
+ throws ErrorResultException
+ {
+ return impl.getConnection();
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/Change.java b/sdk/src/org/opends/sdk/Change.java
new file mode 100644
index 0000000..04f7b4b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/Change.java
@@ -0,0 +1,126 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * A modification to be performed on an entry during a Modify operation.
+ * <p>
+ * TODO: other constructors.
+ */
+public final class Change
+{
+ private final ModificationType modificationType;
+
+ private final Attribute attribute;
+
+
+
+ /**
+ * Creates a new modification having the provided modification type
+ * and attribute values to be updated. Note that while the returned
+ * {@code Change} is immutable, the underlying attribute may not be.
+ * The following code ensures that the returned {@code Change} is
+ * fully immutable:
+ *
+ * <pre>
+ * Change change =
+ * new Change(modificationType, Types.unmodifiableAttribute(attribute));
+ * </pre>
+ *
+ * @param modificationType
+ * The type of change to be performed.
+ * @param attribute
+ * The the attribute containing the values to be modified.
+ */
+ public Change(ModificationType modificationType, Attribute attribute)
+ {
+ Validator.ensureNotNull(modificationType, attribute);
+
+ this.modificationType = modificationType;
+ this.attribute = attribute;
+ }
+
+
+
+ /**
+ * Returns the type of change to be performed.
+ *
+ * @return The type of change to be performed.
+ */
+ public ModificationType getModificationType()
+ {
+ return modificationType;
+ }
+
+
+
+ /**
+ * Returns the attribute containing the values to be modified.
+ *
+ * @return The the attribute containing the values to be modified.
+ */
+ public Attribute getAttribute()
+ {
+ return attribute;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append("Change(modificationType=");
+ builder.append(modificationType);
+ builder.append(", attributeDescription=");
+ builder.append(attribute.getAttributeDescriptionAsString());
+ builder.append(", attributeValues={");
+ boolean firstValue = true;
+ for (ByteString value : attribute)
+ {
+ if (!firstValue)
+ {
+ builder.append(", ");
+ }
+ builder.append(value);
+ firstValue = false;
+ }
+ builder.append("})");
+ return builder.toString();
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/ConditionResult.java b/sdk/src/org/opends/sdk/ConditionResult.java
new file mode 100644
index 0000000..f6a7ddd
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ConditionResult.java
@@ -0,0 +1,295 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+/**
+ * The result of a tri-state logical expression. Condition results are
+ * used to represent the result of a conditional evaluation that can
+ * yield three possible values: {@code FALSE} (i.e. "no"), {@code TRUE}
+ * (i.e. "yes"), or {@code UNDEFINED} (i.e. "maybe"). A result of
+ * {@code UNDEFINED} indicates that further investigation may be
+ * required.
+ */
+public enum ConditionResult
+{
+ /**
+ * Indicates that the condition evaluated to {@code false}.
+ */
+ FALSE("false"),
+
+ /**
+ * Indicates that the condition could not be evaluated and its result
+ * is undefined.
+ */
+ UNDEFINED("undefined"),
+
+ /**
+ * Indicates that the condition evaluated to {@code true}.
+ */
+ TRUE("true");
+
+ // Boolean -> ConditionResult map.
+ private static final boolean[] booleanMap = { false, false, true };
+
+ // AND truth table.
+ private static final ConditionResult[][] logicalAND =
+ { { FALSE, FALSE, FALSE }, { FALSE, UNDEFINED, UNDEFINED },
+ { FALSE, UNDEFINED, TRUE }, };
+
+ // NOT truth table.
+ private static final ConditionResult[] logicalNOT =
+ { TRUE, UNDEFINED, FALSE };
+
+ // OR truth table.
+ private static final ConditionResult[][] logicalOR =
+ { { FALSE, UNDEFINED, TRUE }, { UNDEFINED, UNDEFINED, TRUE },
+ { TRUE, TRUE, TRUE }, };
+
+
+
+ /**
+ * Returns the logical AND of zero condition results, which is always
+ * {@code TRUE}.
+ *
+ * @return The logical OR of zero condition results, which is always
+ * {@code TRUE}.
+ */
+ public static ConditionResult and()
+ {
+ return TRUE;
+ }
+
+
+
+ /**
+ * Returns the logical AND of the provided condition result, which is
+ * always {@code r}.
+ *
+ * @param r
+ * The condition result.
+ * @return The logical AND of the provided condition result, which is
+ * always {@code r}.
+ */
+ public static ConditionResult and(ConditionResult r)
+ {
+ return r;
+ }
+
+
+
+ /**
+ * Returns the logical AND of the provided condition results, which is
+ * {@code TRUE} if all of the provided condition results are {@code
+ * TRUE}, {@code FALSE} if at least one of them is {@code FALSE}, and
+ * {@code UNDEFINED} otherwise. Note that {@code TRUE} is returned if
+ * the provided list of results is empty.
+ *
+ * @param results
+ * The condition results to be compared.
+ * @return The logical AND of the provided condition results.
+ */
+ public static ConditionResult and(ConditionResult... results)
+ {
+ ConditionResult finalResult = TRUE;
+ for (ConditionResult result : results)
+ {
+ finalResult = and(finalResult, result);
+ if (finalResult == FALSE)
+ break;
+ }
+ return finalResult;
+ }
+
+
+
+ /**
+ * Returns the logical AND of the provided condition results, which is
+ * {@code TRUE} if both of the provided condition results are {@code
+ * TRUE}, {@code FALSE} if at least one of them is {@code FALSE} , and
+ * {@code UNDEFINED} otherwise.
+ *
+ * @param r1
+ * The first condition result to be compared.
+ * @param r2
+ * The second condition result to be compared.
+ * @return The logical AND of the provided condition results.
+ */
+ public static ConditionResult and(ConditionResult r1,
+ ConditionResult r2)
+ {
+ return logicalAND[r1.ordinal()][r2.ordinal()];
+ }
+
+
+
+ /**
+ * Returns the logical NOT of the provided condition result, which is
+ * {@code TRUE} if the provided condition result is {@code FALSE},
+ * {@code TRUE} if it is {@code FALSE}, and {@code UNDEFINED}
+ * otherwise.
+ *
+ * @param r
+ * The condition result to invert.
+ * @return The logical NOT of the provided condition result.
+ */
+ public static ConditionResult not(ConditionResult r)
+ {
+ return logicalNOT[r.ordinal()];
+ }
+
+
+
+ /**
+ * Returns the logical OR of zero condition results, which is always
+ * {@code FALSE}.
+ *
+ * @return The logical OR of zero condition results, which is always
+ * {@code FALSE}.
+ */
+ public static ConditionResult or()
+ {
+ return FALSE;
+ }
+
+
+
+ /**
+ * Returns the logical OR of the provided condition result, which is
+ * always {@code r}.
+ *
+ * @param r
+ * The condition result.
+ * @return The logical OR of the provided condition result, which is
+ * always {@code r}.
+ */
+ public static ConditionResult or(ConditionResult r)
+ {
+ return r;
+ }
+
+
+
+ /**
+ * Returns the logical OR of the provided condition results, which is
+ * {@code FALSE} if all of the provided condition results are {@code
+ * FALSE}, {@code TRUE} if at least one of them is {@code TRUE}, and
+ * {@code UNDEFINED} otherwise. Note that {@code FALSE} is returned if
+ * the provided list of results is empty.
+ *
+ * @param results
+ * The condition results to be compared.
+ * @return The logical OR of the provided condition results.
+ */
+ public static ConditionResult or(ConditionResult... results)
+ {
+ ConditionResult finalResult = FALSE;
+ for (ConditionResult result : results)
+ {
+ finalResult = and(finalResult, result);
+ if (finalResult == TRUE)
+ break;
+ }
+ return finalResult;
+ }
+
+
+
+ /**
+ * Returns the logical OR of the provided condition results, which is
+ * {@code FALSE} if both of the provided condition results are {@code
+ * FALSE}, {@code TRUE} if at least one of them is {@code TRUE} , and
+ * {@code UNDEFINED} otherwise.
+ *
+ * @param r1
+ * The first condition result to be compared.
+ * @param r2
+ * The second condition result to be compared.
+ * @return The logical OR of the provided condition results.
+ */
+ public static ConditionResult or(ConditionResult r1,
+ ConditionResult r2)
+ {
+ return logicalOR[r1.ordinal()][r2.ordinal()];
+ }
+
+
+
+ /**
+ * Returns the condition result which is equivalent to the provided
+ * boolean value.
+ *
+ * @param b
+ * The boolean value.
+ * @return {@code TRUE} if {@code b} was {@code true}, otherwise
+ * {@code FALSE}.
+ */
+ public static ConditionResult valueOf(boolean b)
+ {
+ return b ? TRUE : FALSE;
+ }
+
+ // The human-readable name for this result.
+ private final String resultName;
+
+
+
+ // Prevent instantiation.
+ private ConditionResult(String resultName)
+ {
+ this.resultName = resultName;
+ }
+
+
+
+ /**
+ * Converts this condition result to a boolean value. {@code FALSE}
+ * and {@code UNDEFINED} are both converted to {@code false}, and
+ * {@code TRUE} is converted to {@code true}.
+ *
+ * @return The boolean equivalent of this condition result.
+ */
+ public boolean toBoolean()
+ {
+ return booleanMap[ordinal()];
+ }
+
+
+
+ /**
+ * Returns the string representation of this condition result.
+ *
+ * @return The string representation of his condition result.
+ */
+ @Override
+ public String toString()
+ {
+ return resultName;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/Connection.java b/sdk/src/org/opends/sdk/Connection.java
new file mode 100644
index 0000000..a4be674
--- /dev/null
+++ b/sdk/src/org/opends/sdk/Connection.java
@@ -0,0 +1,1084 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.io.Closeable;
+import java.util.Collection;
+import java.util.List;
+
+import org.opends.sdk.requests.*;
+import org.opends.sdk.responses.*;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * A synchronous connection with a Directory Server over which read and
+ * update operations may be performed. See RFC 4511 for the LDAPv3
+ * protocol specification and more information about the types of
+ * operations defined in LDAP.
+ * <p>
+ * <h3>Operation processing</h3>
+ * <p>
+ * All operations are performed synchronously and return an appropriate
+ * {@link Result} representing the final status of the operation.
+ * Operation failures, for whatever reason, are signalled using an
+ * {@link ErrorResultException}.
+ * <p>
+ * <h3>Closing connections</h3>
+ * <p>
+ * Applications must ensure that a connection is closed by calling
+ * {@link #close()} even if a fatal error occurs on the connection. Once
+ * a connection has been closed by the client application, any attempts
+ * to continue to use the connection will result in an
+ * {@link IllegalStateException} being thrown. Note that, if a fatal
+ * error is encountered on the connection, then the application can
+ * continue to use the connection. In this case all requests subsequent
+ * to the failure will fail with an appropriate
+ * {@link ErrorResultException} when their result is retrieved.
+ * <p>
+ * <h3>Event notification</h3>
+ * <p>
+ * Applications can choose to be notified when a connection is closed by
+ * the application, receives an unsolicited notification, or experiences
+ * a fatal error by registering a {@link ConnectionEventListener} with
+ * the connection using the {@link #addConnectionEventListener} method.
+ * <p>
+ * <h3>TO DO</h3>
+ * <p>
+ * <ul>
+ * <li>do we need isClosed() and isValid()?
+ * <li>do we need connection event notification of client close? JDBC
+ * and JCA have this functionality in their pooled (managed) connection
+ * APIs. We need some form of event notification at the app level for
+ * unsolicited notifications.
+ * <li>method for performing update operation (e.g. LDIF change
+ * records).
+ * <li>should unsupported methods throw UnsupportedOperationException or
+ * throw an ErrorResultException using an UnwillingToPerform result code
+ * (or something similar)?
+ * <li>Handler version of search.
+ * <li>Finish off Search APIs to support blocking queues and
+ * collections.
+ * <li>searchSingleEntry Javadoc needs finishing. Code sample is
+ * incorrect. How about references? What exceptions are thrown?
+ * </ul>
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc4511">RFC 4511 -
+ * Lightweight Directory Access Protocol (LDAP): The Protocol </a>
+ */
+public interface Connection extends Closeable
+{
+
+ /**
+ * Adds an entry to the Directory Server using the provided add
+ * request.
+ *
+ * @param request
+ * The add request.
+ * @return The result of the operation.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws UnsupportedOperationException
+ * If this connection does not support add operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code request} was {@code null}.
+ */
+ Result add(AddRequest request) throws ErrorResultException,
+ InterruptedException, UnsupportedOperationException,
+ IllegalStateException, NullPointerException;
+
+
+
+ /**
+ * Adds an entry to the Directory Server using the provided lines of
+ * LDIF.
+ * <p>
+ * This method is equivalent to the following code:
+ *
+ * <pre>
+ * AddRequest request = new AddRequest(ldifLines);
+ * connection.add(request);
+ * </pre>
+ *
+ * @param ldifLines
+ * Lines of LDIF containing the an LDIF add change record or
+ * an LDIF entry record.
+ * @return The result of the operation.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws UnsupportedOperationException
+ * If this connection does not support add operations.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code ldifLines} was empty, or contained invalid
+ * LDIF, or could not be decoded using the default schema.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code ldifLines} was {@code null} .
+ */
+ Result add(String... ldifLines) throws ErrorResultException,
+ InterruptedException, UnsupportedOperationException,
+ LocalizedIllegalArgumentException, IllegalStateException,
+ NullPointerException;
+
+
+
+ /**
+ * Adds the provided entry to the Directory Server.
+ * <p>
+ * This method is equivalent to the following code:
+ *
+ * <pre>
+ * AddRequest request = new AddRequest(entry);
+ * connection.add(request);
+ * </pre>
+ *
+ * @param entry
+ * The entry to be added.
+ * @return The result of the operation.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws UnsupportedOperationException
+ * If this connection does not support add operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code entry} was {@code null} .
+ */
+ Result add(Entry entry) throws ErrorResultException,
+ InterruptedException, UnsupportedOperationException,
+ IllegalStateException, NullPointerException;
+
+
+
+ /**
+ * Authenticates to the Directory Server using the provided bind
+ * request.
+ *
+ * @param request
+ * The bind request.
+ * @return The result of the operation.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws UnsupportedOperationException
+ * If this connection does not support bind operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code request} was {@code null}.
+ */
+ BindResult bind(BindRequest request) throws ErrorResultException,
+ InterruptedException, UnsupportedOperationException,
+ IllegalStateException, NullPointerException;
+
+
+
+ /**
+ * Authenticates to the Directory Server using simple authentication
+ * and the provided user name and password.
+ * <p>
+ * This method is equivalent to the following code:
+ *
+ * <pre>
+ * BindRequest request = new SimpleBindRequest(name, password);
+ * connection.bind(request);
+ * </pre>
+ *
+ * @param name
+ * The distinguished name of the Directory object that the
+ * client wishes to bind as, which may be empty.
+ * @param password
+ * The password of the Directory object that the client
+ * wishes to bind as, which may be empty.
+ * @return The result of the operation.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code name} could not be decoded using the default
+ * schema.
+ * @throws UnsupportedOperationException
+ * If this connection does not support bind operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code name} or {@code password} was {@code null}.
+ */
+ BindResult bind(String name, String password)
+ throws ErrorResultException, InterruptedException,
+ LocalizedIllegalArgumentException, UnsupportedOperationException,
+ IllegalStateException, NullPointerException;
+
+
+
+ /**
+ * Releases any resources associated with this connection. For
+ * physical connections to a Directory Server this will mean that an
+ * unbind request is sent and the underlying socket is closed.
+ * <p>
+ * Other connection implementations may behave differently, and may
+ * choose not to send an unbind request if its use is inappropriate
+ * (for example a pooled connection will be released and returned to
+ * its connection pool without ever issuing an unbind request).
+ * <p>
+ * This method is equivalent to the following code:
+ *
+ * <pre>
+ * UnbindRequest request = new UnbindRequest();
+ * connection.close(request);
+ * </pre>
+ *
+ * Calling {@code close} on a connection that is already closed has no
+ * effect.
+ */
+ void close();
+
+
+
+ /**
+ * Releases any resources associated with this connection. For
+ * physical connections to a Directory Server this will mean that the
+ * provided unbind request is sent and the underlying socket is
+ * closed.
+ * <p>
+ * Other connection implementations may behave differently, and may
+ * choose to ignore the provided unbind request if its use is
+ * inappropriate (for example a pooled connection will be released and
+ * returned to its connection pool without ever issuing an unbind
+ * request).
+ * <p>
+ * Calling {@code close} on a connection that is already closed has no
+ * effect.
+ *
+ * @param request
+ * The unbind request to use in the case where a physical
+ * connection is closed.
+ * @throws NullPointerException
+ * If {@code request} was {@code null}.
+ */
+ void close(UnbindRequest request) throws NullPointerException;
+
+
+
+ /**
+ * Compares an entry in the Directory Server using the provided
+ * compare request.
+ *
+ * @param request
+ * The compare request.
+ * @return The result of the operation.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws UnsupportedOperationException
+ * If this connection does not support compare operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code request} was {@code null}.
+ */
+ CompareResult compare(CompareRequest request)
+ throws ErrorResultException, InterruptedException,
+ UnsupportedOperationException, IllegalStateException,
+ NullPointerException;
+
+
+
+ /**
+ * Compares the named entry in the Directory Server against the
+ * provided attribute value assertion.
+ * <p>
+ * This method is equivalent to the following code:
+ *
+ * <pre>
+ * CompareRequest request = new CompareRequest(name, attributeDescription,
+ * assertionValue);
+ * connection.compare(request);
+ * </pre>
+ *
+ * @param name
+ * The distinguished name of the entry to be compared.
+ * @param attributeDescription
+ * The name of the attribute to be compared.
+ * @param assertionValue
+ * The assertion value to be compared.
+ * @return The result of the operation.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code name} or {@code AttributeDescription} could not
+ * be decoded using the default schema.
+ * @throws UnsupportedOperationException
+ * If this connection does not support compare operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code name}, {@code attributeDescription}, or {@code
+ * assertionValue} was {@code null}.
+ */
+ CompareResult compare(String name, String attributeDescription,
+ String assertionValue) throws ErrorResultException,
+ InterruptedException, LocalizedIllegalArgumentException,
+ UnsupportedOperationException, IllegalStateException,
+ NullPointerException;
+
+
+
+ /**
+ * Deletes an entry from the Directory Server using the provided
+ * delete request.
+ *
+ * @param request
+ * The delete request.
+ * @return The result of the operation.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws UnsupportedOperationException
+ * If this connection does not support delete operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code request} was {@code null}.
+ */
+ Result delete(DeleteRequest request) throws ErrorResultException,
+ InterruptedException, UnsupportedOperationException,
+ IllegalStateException, NullPointerException;
+
+
+
+ /**
+ * Deletes the named entry from the Directory Server.
+ * <p>
+ * This method is equivalent to the following code:
+ *
+ * <pre>
+ * DeleteRequest request = new DeleteRequest(name);
+ * connection.delete(request);
+ * </pre>
+ *
+ * @param name
+ * The distinguished name of the entry to be deleted.
+ * @return The result of the operation.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code name} could not be decoded using the default
+ * schema.
+ * @throws UnsupportedOperationException
+ * If this connection does not support delete operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code name} was {@code null}.
+ */
+ Result delete(String name) throws ErrorResultException,
+ InterruptedException, LocalizedIllegalArgumentException,
+ UnsupportedOperationException, IllegalStateException,
+ NullPointerException;
+
+
+
+ /**
+ * Requests that the Directory Server performs the provided extended
+ * request.
+ *
+ * @param <R>
+ * The type of result returned by the extended request.
+ * @param request
+ * The extended request.
+ * @return The result of the operation.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws UnsupportedOperationException
+ * If this connection does not support extended operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code request} was {@code null}.
+ */
+ <R extends Result> R extendedRequest(ExtendedRequest<R> request)
+ throws ErrorResultException, InterruptedException,
+ UnsupportedOperationException, IllegalStateException,
+ NullPointerException;
+
+
+
+ /**
+ * Requests that the Directory Server performs the provided extended
+ * request.
+ * <p>
+ * This method is equivalent to the following code:
+ *
+ * <pre>
+ * GenericExtendedRequest request = new GenericExtendedRequest(
+ * requestName, requestValue);
+ * connection.extendedRequest(request);
+ * </pre>
+ *
+ * @param requestName
+ * The dotted-decimal representation of the unique OID
+ * corresponding to the extended request.
+ * @param requestValue
+ * The content of the extended request in a form defined by
+ * the extended operation, or {@code null} if there is no
+ * content.
+ * @return The result of the operation.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws UnsupportedOperationException
+ * If this connection does not support extended operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code requestName} was {@code null}.
+ */
+ GenericExtendedResult extendedRequest(String requestName,
+ ByteString requestValue) throws ErrorResultException,
+ InterruptedException, UnsupportedOperationException,
+ IllegalStateException, NullPointerException;
+
+
+
+ // /**
+ // * Indicates whether or not this connection has been explicitly
+ // closed
+ // * by calling {@code close}. This method will not return {@code
+ // true}
+ // * if a fatal error has occurred on the connection unless {@code
+ // * close} has been called.
+ // *
+ // * @return {@code true} if this connection has been explicitly
+ // closed
+ // * by calling {@code close}, or {@code false} otherwise.
+ // */
+ // boolean isClosed();
+ //
+ //
+ //
+ // /**
+ // * Indicates whether or not this connection is valid. A connection
+ // is
+ // * not valid if the method {@code close} has been called on it or if
+ // * certain fatal errors have occurred. This method is guaranteed to
+ // * return {@code false} only when it is called after the method
+ // * {@code close} has been called.
+ // * <p>
+ // * Implementations may choose to send a no-op request to the
+ // * underlying Directory Server in order to determine if the
+ // underlying
+ // * connection is still valid.
+ // *
+ // * @return {@code true} if this connection is valid, or {@code
+ // false}
+ // * otherwise.
+ // * @throws InterruptedException
+ // * If the current thread was interrupted while waiting.
+ // */
+ // boolean isValid() throws InterruptedException;
+ //
+ //
+ //
+ // /**
+ // * Indicates whether or not this connection is valid. A connection
+ // is
+ // * not valid if the method {@code close} has been called on it or if
+ // * certain fatal errors have occurred. This method is guaranteed to
+ // * return {@code false} only when it is called after the method
+ // * {@code close} has been called.
+ // * <p>
+ // * Implementations may choose to send a no-op request to the
+ // * underlying Directory Server in order to determine if the
+ // underlying
+ // * connection is still valid.
+ // *
+ // * @param timeout
+ // * The maximum time to wait.
+ // * @param unit
+ // * The time unit of the timeout argument.
+ // * @return {@code true} if this connection is valid, or {@code
+ // false}
+ // * otherwise.
+ // * @throws InterruptedException
+ // * If the current thread was interrupted while waiting.
+ // * @throws TimeoutException
+ // * If the wait timed out.
+ // */
+ // boolean isValid(long timeout, TimeUnit unit)
+ // throws InterruptedException, TimeoutException;
+
+ /**
+ * Modifies an entry in the Directory Server using the provided modify
+ * request.
+ *
+ * @param request
+ * The modify request.
+ * @return The result of the operation.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws UnsupportedOperationException
+ * If this connection does not support modify operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code request} was {@code null}.
+ */
+ Result modify(ModifyRequest request) throws ErrorResultException,
+ InterruptedException, UnsupportedOperationException,
+ IllegalStateException, NullPointerException;
+
+
+
+ /**
+ * Modifies an entry in the Directory Server using the provided lines
+ * of LDIF.
+ * <p>
+ * This method is equivalent to the following code:
+ *
+ * <pre>
+ * ModifyRequest request = new ModifyRequest(name, ldifChanges);
+ * connection.modify(request);
+ * </pre>
+ *
+ * @param ldifLines
+ * Lines of LDIF containing the a single LDIF modify change
+ * record.
+ * @return The result of the operation.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws UnsupportedOperationException
+ * If this connection does not support modify operations.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code ldifLines} was empty, or contained invalid
+ * LDIF, or could not be decoded using the default schema.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code ldifLines} was {@code null} .
+ */
+ Result modify(String... ldifLines) throws ErrorResultException,
+ InterruptedException, UnsupportedOperationException,
+ LocalizedIllegalArgumentException, IllegalStateException,
+ NullPointerException;
+
+
+
+ /**
+ * Renames an entry in the Directory Server using the provided modify
+ * DN request.
+ *
+ * @param request
+ * The modify DN request.
+ * @return The result of the operation.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws UnsupportedOperationException
+ * If this connection does not support modify DN operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code request} was {@code null}.
+ */
+ Result modifyDN(ModifyDNRequest request) throws ErrorResultException,
+ InterruptedException, UnsupportedOperationException,
+ IllegalStateException, NullPointerException;
+
+
+
+ /**
+ * Renames the named entry in the Directory Server using the provided
+ * new RDN.
+ * <p>
+ * This method is equivalent to the following code:
+ *
+ * <pre>
+ * ModifyDNRequest request = new ModifyDNRequest(name, newRDN);
+ * connection.modifyDN(request);
+ * </pre>
+ *
+ * @param name
+ * The distinguished name of the entry to be renamed.
+ * @param newRDN
+ * The new RDN of the entry.
+ * @return The result of the operation.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code name} or {@code newRDN} could not be decoded
+ * using the default schema.
+ * @throws UnsupportedOperationException
+ * If this connection does not support modify DN operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code name} or {@code newRDN} was {@code null}.
+ */
+ Result modifyDN(String name, String newRDN)
+ throws ErrorResultException, LocalizedIllegalArgumentException,
+ InterruptedException, UnsupportedOperationException,
+ IllegalStateException, NullPointerException;
+
+
+
+ /**
+ * Searches the Directory Server using the provided search request.
+ * Any matching entries returned by the search as well as any search
+ * result references will be passed to the provided search result
+ * handler.
+ *
+ * @param <P>
+ * The type of the additional parameter to the handler's
+ * methods.
+ * @param request
+ * The search request.
+ * @param handler
+ * A search result handler which can be used to process the
+ * search result entries and references as they are received,
+ * may be {@code null}.
+ * @param p
+ * Optional additional handler parameter.
+ * @return The result of the operation.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws UnsupportedOperationException
+ * If this connection does not support search operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code request} was {@code null}.
+ */
+ <P> Result search(SearchRequest request,
+ SearchResultHandler<P> handler, P p) throws ErrorResultException,
+ InterruptedException, UnsupportedOperationException,
+ IllegalStateException, NullPointerException;
+
+
+
+ /**
+ * Searches the Directory Server using the provided search request.
+ * Any matching entries returned by the search will be added to
+ * {@code entries}, even if the final search result indicates that the
+ * search failed. Search result references will be discarded.
+ * <p>
+ * <b>Warning:</b> Usage of this method is discouraged if the search
+ * request is expected to yield a large number of search results since
+ * the entire set of results will be stored in memory, potentially
+ * causing an {@code OutOfMemoryError}.
+ * <p>
+ * This method is equivalent to the following code:
+ *
+ * <pre>
+ * connection.search(request, entries, null);
+ * </pre>
+ *
+ * @param request
+ * The search request.
+ * @param entries
+ * The collection to which matching entries should be added.
+ * @return The result of the operation.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws UnsupportedOperationException
+ * If this connection does not support search operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code request} or {@code entries} was {@code null}.
+ */
+ Result search(SearchRequest request,
+ Collection<? super SearchResultEntry> entries)
+ throws ErrorResultException, InterruptedException,
+ UnsupportedOperationException, IllegalStateException,
+ NullPointerException;
+
+
+
+ /**
+ * Searches the Directory Server using the provided search request.
+ * Any matching entries returned by the search will be added to
+ * {@code entries}, even if the final search result indicates that the
+ * search failed. Similarly, search result references returned by the
+ * search will be added to {@code references}.
+ * <p>
+ * <b>Warning:</b> Usage of this method is discouraged if the search
+ * request is expected to yield a large number of search results since
+ * the entire set of results will be stored in memory, potentially
+ * causing an {@code OutOfMemoryError}.
+ *
+ * @param request
+ * The search request.
+ * @param entries
+ * The collection to which matching entries should be added.
+ * @param references
+ * The collection to which search result references should be
+ * added, or {@code null} if references are to be discarded.
+ * @return The result of the operation.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws UnsupportedOperationException
+ * If this connection does not support search operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code request} or {@code entries} was {@code null}.
+ */
+ Result search(SearchRequest request,
+ Collection<? super SearchResultEntry> entries,
+ Collection<? super SearchResultReference> references)
+ throws ErrorResultException, InterruptedException,
+ UnsupportedOperationException, IllegalStateException,
+ NullPointerException;
+
+
+
+ /**
+ * Searches the Directory Server using the provided search parameters.
+ * Any matching entries returned by the search will be added to a
+ * {@code List} which is returned if the search succeeds. Search
+ * result references will be discarded.
+ * <p>
+ * <b>Warning:</b> Usage of this method is discouraged if the search
+ * request is expected to yield a large number of search results since
+ * the entire set of results will be stored in memory, potentially
+ * causing an {@code OutOfMemoryError}.
+ * <p>
+ * This method is equivalent to the following code:
+ *
+ * <pre>
+ * SearchRequest request = new SearchRequest(baseDN, scope, filter,
+ * attributeDescriptions);
+ * connection.search(request, new LinkedList<SearchResultEntry>());
+ * </pre>
+ *
+ * @param baseObject
+ * The distinguished name of the base entry relative to which
+ * the search is to be performed.
+ * @param scope
+ * The scope of the search.
+ * @param filter
+ * The filter that defines the conditions that must be
+ * fulfilled in order for an entry to be returned.
+ * @param attributeDescriptions
+ * The names of the attributes to be included with each
+ * entry.
+ * @return A list containing any matching entries returned by the
+ * search.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code baseObject} could not be decoded using the
+ * default schema or if {@code filter} is not a valid LDAP
+ * string representation of a filter.
+ * @throws UnsupportedOperationException
+ * If this connection does not support search operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If the {@code baseObject}, {@code scope}, or {@code
+ * filter} were {@code null}.
+ */
+ List<SearchResultEntry> search(String baseObject, SearchScope scope,
+ String filter, String... attributeDescriptions)
+ throws ErrorResultException, InterruptedException,
+ LocalizedIllegalArgumentException, UnsupportedOperationException,
+ IllegalStateException, NullPointerException;
+
+
+
+ /**
+ * Searches the Directory Server for a single entry using the provided
+ * search request. If the search returns more than one entry then an
+ * {@code ErrorResultException} is thrown. If no entry is found then
+ * this method returns {@code null}.
+ *
+ * @param request
+ * The search request.
+ * @return The single search result entry returned from the search.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws UnsupportedOperationException
+ * If this connection does not support search operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If the {@code request} was {@code null}.
+ */
+ SearchResultEntry searchSingleEntry(SearchRequest request)
+ throws ErrorResultException, InterruptedException,
+ UnsupportedOperationException, IllegalStateException,
+ NullPointerException;
+
+
+
+ /**
+ * Searches the Directory Server for a single entry using the provided
+ * search parameters. If the search returns more than one entry then
+ * an {@code ErrorResultException} is thrown. If no entry is found
+ * then this method returns {@code null}.
+ * <p>
+ * This method is equivalent to the following code:
+ *
+ * <pre>
+ * SearchRequest request = new SearchRequest(baseObject, scope, filter,
+ * attributeDescriptions);
+ * connection.searchSingleEntry(request);
+ * </pre>
+ *
+ * @param baseObject
+ * The distinguished name of the base entry relative to which
+ * the search is to be performed.
+ * @param scope
+ * The scope of the search.
+ * @param filter
+ * The filter that defines the conditions that must be
+ * fulfilled in order for an entry to be returned.
+ * @param attributeDescriptions
+ * The names of the attributes to be included with each
+ * entry.
+ * @return The single search result entry returned from the search.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code baseObject} could not be decoded using the
+ * default schema or if {@code filter} is not a valid LDAP
+ * string representation of a filter.
+ * @throws UnsupportedOperationException
+ * If this connection does not support search operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If the {@code baseObject}, {@code scope}, or {@code
+ * filter} were {@code null}.
+ */
+ SearchResultEntry searchSingleEntry(String baseObject,
+ SearchScope scope, String filter, String... attributeDescriptions)
+ throws ErrorResultException, InterruptedException,
+ LocalizedIllegalArgumentException, UnsupportedOperationException,
+ IllegalStateException, NullPointerException;
+
+
+
+ /**
+ * Reads the named entry from the Directory Server. If no entry is
+ * found then this method returns {@code null}.
+ * <p>
+ * This method is equivalent to the following code:
+ *
+ * <pre>
+ * SearchRequest request = new SearchRequest(baseObject,
+ * SearchScope.BASE_OBJECT, "(objectClass=*)", attributeDescriptions);
+ * connection.searchSingleEntry(request);
+ * </pre>
+ *
+ * @param baseObject
+ * The distinguished name of the entry to be read.
+ * @param attributeDescriptions
+ * The names of the attributes to be included with the entry.
+ * @return The single search result entry returned from the search.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code baseObject} could not be decoded using the
+ * default schema.
+ * @throws UnsupportedOperationException
+ * If this connection does not support search operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If the {@code baseObject} was {@code null}.
+ */
+ SearchResultEntry readEntry(String baseObject,
+ String... attributeDescriptions) throws ErrorResultException,
+ InterruptedException, LocalizedIllegalArgumentException,
+ UnsupportedOperationException, IllegalStateException,
+ NullPointerException;
+
+
+
+ /**
+ * Reads the named entry from the Directory Server. If no entry is
+ * found then this method returns {@code null}.
+ * <p>
+ * This method is equivalent to the following code:
+ *
+ * <pre>
+ * SearchRequest request = new SearchRequest(baseObject,
+ * SearchScope.BASE_OBJECT, "(objectClass=*)", attributeDescriptions);
+ * connection.searchSingleEntry(request);
+ * </pre>
+ *
+ * @param baseObject
+ * The distinguished name of the entry to be read.
+ * @param attributeDescriptions
+ * The names of the attributes to be included with the entry.
+ * @return The single search result entry returned from the search.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws UnsupportedOperationException
+ * If this connection does not support search operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If the {@code baseObject} was {@code null}.
+ */
+ SearchResultEntry readEntry(DN baseObject,
+ String... attributeDescriptions) throws ErrorResultException,
+ InterruptedException, UnsupportedOperationException,
+ IllegalStateException, NullPointerException;
+
+
+
+ /**
+ * Registers the provided connection event listener so that it will be
+ * notified when this connection is closed by the application,
+ * receives an unsolicited notification, or experiences a fatal error.
+ *
+ * @param listener
+ * The listener which wants to be notified when events occur
+ * on this connection.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If the {@code listener} was {@code null}.
+ */
+ void addConnectionEventListener(ConnectionEventListener listener)
+ throws IllegalStateException, NullPointerException;
+
+
+
+ /**
+ * Removes the provided connection event listener from this connection
+ * so that it will no longer be notified when this connection is
+ * closed by the application, receives an unsolicited notification, or
+ * experiences a fatal error.
+ *
+ * @param listener
+ * The listener which no longer wants to be notified when
+ * events occur on this connection.
+ * @throws NullPointerException
+ * If the {@code listener} was {@code null}.
+ */
+ void removeConnectionEventListener(ConnectionEventListener listener)
+ throws NullPointerException;
+}
diff --git a/sdk/src/org/opends/sdk/ConnectionEventListener.java b/sdk/src/org/opends/sdk/ConnectionEventListener.java
new file mode 100644
index 0000000..d17475e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ConnectionEventListener.java
@@ -0,0 +1,109 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.EventListener;
+
+import org.opends.sdk.responses.GenericExtendedResult;
+
+
+
+/**
+ * An object that registers to be notified when a connection is closed
+ * by the application, receives an unsolicited notification, or
+ * experiences a fatal error.
+ * <p>
+ * TODO: isolate fatal connection errors as a sub-type of
+ * ErrorResultException.
+ * <p>
+ * TODO: do we need client initiated close notification as in JCA /
+ * JDBC? A simpler approach would be for the connection pool to wrap the
+ * underlying physical connection with its own. It can then intercept
+ * the close request from the client. This has the disadvantage in that
+ * we lose any specialized methods exposed by the underlying physical
+ * connection (i.e. if the physical connection extends Connection and
+ * provides additional methods) since the connection pool effectively
+ * hides them via its wrapper.
+ */
+public interface ConnectionEventListener extends EventListener
+{
+ // /**
+ // * Notifies this connection event listener that the application has
+ // * called {@link Connection#close} on the connection. The connection
+ // * event listener will be notified immediately after the application
+ // * calls the {@link Connection#close} method on the associated
+ // * connection.
+ // *
+ // * @param connection
+ // * The connection that has just been closed by the
+ // * application.
+ // */
+ // void connectionClosed(Connection connection);
+
+ /**
+ * Notifies this connection event listener that the connection has
+ * just received the provided unsolicited notification from the
+ * server.
+ * <p>
+ * <b>Note:</b> disconnect notifications are treated as fatal
+ * connection errors and are handled by the
+ * {@link #connectionErrorOccurred} method.
+ *
+ * @param notification
+ * The unsolicited notification
+ */
+ void connectionReceivedUnsolicitedNotification(
+ GenericExtendedResult notification);
+
+
+
+ /**
+ * Notifies this connection event listener that a fatal error has
+ * occurred and the connection can no longer be used - the server has
+ * crashed, for example. The connection implementation makes this
+ * notification just before it throws the provided
+ * {@link ErrorResultException} to the application.
+ * <p>
+ * <b>Note:</b> disconnect notifications are treated as fatal
+ * connection errors and are handled by this method. In this case
+ * {@code isDisconnectNotification} will be {@code true} and {@code
+ * error} will contain the result code and any diagnostic information
+ * contained in the notification message.
+ *
+ * @param isDisconnectNotification
+ * {@code true} if the error was triggered by a disconnect
+ * notification sent by the server, otherwise {@code false}.
+ * @param error
+ * The exception that is about to be thrown to the
+ * application.
+ */
+ void connectionErrorOccurred(boolean isDisconnectNotification,
+ ErrorResultException error);
+}
diff --git a/sdk/src/org/opends/sdk/ConnectionFactory.java b/sdk/src/org/opends/sdk/ConnectionFactory.java
new file mode 100644
index 0000000..8d8d998
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ConnectionFactory.java
@@ -0,0 +1,95 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+/**
+ * A connection factory provides an interface for obtaining a connection
+ * to a Directory Server. Connection factories can be used to wrap other
+ * connection factories in order to provide enhanced capabilities in a
+ * manner which is transparent to the application. For example:
+ * <ul>
+ * <li>Connection pooling
+ * <li>Load balancing
+ * <li>Keep alive
+ * <li>Transactional connections
+ * <li>Connections to LDIF files
+ * <li>Data transformations
+ * <li>Logging connections
+ * <li>Read-only connections
+ * <li>Pre-authenticated connections
+ * <li>Recording connections, with primitive roll-back functionality
+ * </ul>
+ * An application typically obtains a connection from a connection
+ * factory, performs one or more operations, and then closes the
+ * connection. Applications should aim to close connections as soon as
+ * possible in order to avoid resource contention.
+ *
+ * @param <C>
+ * The type of asynchronous connection returned by this
+ * connection factory.
+ */
+public interface ConnectionFactory<C extends AsynchronousConnection>
+{
+ /**
+ * Returns a connection to the Directory Server associated with this
+ * connection factory. The connection returned by this method can be
+ * used immediately.
+ *
+ * @return A connection to the Directory Server associated with this
+ * connection factory.
+ * @throws ErrorResultException
+ * If the connection request failed for some reason.
+ */
+ Connection getConnection() throws ErrorResultException;
+
+
+
+ /**
+ * Initiates an asynchronous connection request to the Directory
+ * Server associated with this connection factory. The returned
+ * {@code ConnectionFuture} can be used to retrieve the completed
+ * asynchronous connection. Alternatively, if a {@code
+ * ConnectionResultHandler} is provided, the handler will be notified
+ * when the connection is available and ready for use.
+ *
+ * @param <P>
+ * The type of the additional parameter to the handler's
+ * methods.
+ * @param handler
+ * The completion handler, or {@code null} if no handler is
+ * to be used.
+ * @param p
+ * Optional additional handler parameter.
+ * @return A future which can be used to retrieve the asynchronous
+ * connection.
+ */
+ <P> ConnectionFuture<? extends C> getAsynchronousConnection(
+ ConnectionResultHandler<? super C, P> handler, P p);
+}
diff --git a/sdk/src/org/opends/sdk/ConnectionFuture.java b/sdk/src/org/opends/sdk/ConnectionFuture.java
new file mode 100644
index 0000000..517f68c
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ConnectionFuture.java
@@ -0,0 +1,150 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+
+
+/**
+ * A handle which can be used to retrieve a requested {@code
+ * AsynchronousConnection}.
+ * <p>
+ * TODO: Do we want to throw an ErrorResultException? I think we do
+ * because exceptions are not limited to connection related errors. For
+ * example, a transacted connection would already have a physical
+ * connection; an error could occur when sending the start txn extended
+ * op.
+ *
+ * @param <C>
+ * The type of asynchronous connection returned by this
+ * connection future.
+ */
+public interface ConnectionFuture<C extends AsynchronousConnection>
+ extends Future<C>
+{
+ /**
+ * Attempts to cancel the request. This attempt will fail if the
+ * request has already completed or has already been cancelled. If
+ * successful, then cancellation results in an abandon or cancel
+ * request (if configured) being sent to the server.
+ * <p>
+ * After this method returns, subsequent calls to {@link #isDone} will
+ * always return {@code true}. Subsequent calls to
+ * {@link #isCancelled} will always return {@code true} if this method
+ * returned {@code true}.
+ *
+ * @param mayInterruptIfRunning
+ * {@code true} if the thread executing executing the
+ * response handler should be interrupted; otherwise,
+ * in-progress response handlers are allowed to complete.
+ * @return {@code false} if the request could not be cancelled,
+ * typically because it has already completed normally;
+ * {@code true} otherwise.
+ */
+ boolean cancel(boolean mayInterruptIfRunning);
+
+
+
+ /**
+ * Waits if necessary for the connection request to complete, and then
+ * returns the asynchronous connection if the connection request
+ * succeeded. If the connection request failed (i.e. a non-successful
+ * result code was obtained) then a {@link ErrorResultException} is
+ * thrown.
+ *
+ * @return The asynchronous connection, but only if the the connection
+ * request succeeded.
+ * @throws CancellationException
+ * If the connection request was cancelled using a call to
+ * {@link #cancel}.
+ * @throws ErrorResultException
+ * If the connection request failed for some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ */
+ C get() throws InterruptedException, ErrorResultException;
+
+
+
+ /**
+ * Waits if necessary for at most the given time for the connection
+ * request to complete, and then returns the asynchronous connection
+ * if the connection request succeeded. If the connection request
+ * failed (i.e. a non-successful result code was obtained) then a
+ * {@link ErrorResultException} is thrown.
+ *
+ * @param timeout
+ * The maximum time to wait.
+ * @param unit
+ * The time unit of the timeout argument.
+ * @return The asynchronous connection, but only if the the connection
+ * request succeeded.
+ * @throws CancellationException
+ * If the connection request was cancelled using a call to
+ * {@link #cancel}.
+ * @throws ErrorResultException
+ * If the connection request failed for some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws TimeoutException
+ * If the wait timed out.
+ */
+ C get(long timeout, TimeUnit unit) throws InterruptedException,
+ TimeoutException, ErrorResultException;
+
+
+
+ /**
+ * Returns {@code true} if the connection request was cancelled before
+ * it completed normally.
+ *
+ * @return {@code true} if the connection request was cancelled before
+ * it completed normally, otherwise {@code false}.
+ */
+ boolean isCancelled();
+
+
+
+ /**
+ * Returns {@code true} if the connection request has completed.
+ * <p>
+ * Completion may be due to normal termination, an exception, or
+ * cancellation. In all of these cases, this method will return
+ * {@code true}.
+ *
+ * @return {@code true} if the connection request has completed,
+ * otherwise {@code false}.
+ */
+ boolean isDone();
+}
diff --git a/sdk/src/org/opends/sdk/ConnectionResultHandler.java b/sdk/src/org/opends/sdk/ConnectionResultHandler.java
new file mode 100644
index 0000000..c4ee3d6
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ConnectionResultHandler.java
@@ -0,0 +1,81 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+/**
+ * A completion handler which is notified when an asynchronous
+ * connection attempt has completed.
+ * <p>
+ * {@link ConnectionFactory} objects allow a connection result
+ * completion handler to be specified when attempting to connect to a
+ * Directory Server. The {@link #handleConnection} method is invoked
+ * when the operation completes successfully. The
+ * {@link #handleConnectionError} method is invoked if the operations
+ * fails.
+ * <p>
+ * Implementations of these methods should complete in a timely manner
+ * so as to avoid keeping the invoking thread from dispatching to other
+ * completion handlers.
+ *
+ * @param <C>
+ * The type of asynchronous connection handled by this
+ * connection result handler.
+ * @param <P>
+ * The type of the additional parameter to this handler's
+ * methods. Use {@link java.lang.Void} for visitors that do not
+ * need an additional parameter.
+ */
+public interface ConnectionResultHandler<C extends AsynchronousConnection, P>
+{
+ /**
+ * Invoked when the asynchronous connection has completed
+ * successfully.
+ *
+ * @param p
+ * A handler specified parameter.
+ * @param connection
+ * The connection which can be used to interact with the
+ * Directory Server.
+ */
+ void handleConnection(P p, C connection);
+
+
+
+ /**
+ * Invoked when the asynchronous connection attempt has failed.
+ *
+ * @param p
+ * A handler specified parameter.
+ * @param error
+ * The error result exception indicating why the asynchronous
+ * connection attempt has failed.
+ */
+ void handleConnectionError(P p, ErrorResultException error);
+}
diff --git a/sdk/src/org/opends/sdk/DN.java b/sdk/src/org/opends/sdk/DN.java
new file mode 100644
index 0000000..40208c3
--- /dev/null
+++ b/sdk/src/org/opends/sdk/DN.java
@@ -0,0 +1,763 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.schema.UnknownSchemaElementException;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.SubstringReader;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * A distinguished name (DN) as defined in RFC 4512 section 2.3 is the
+ * concatenation of its relative distinguished name (RDN) and its
+ * immediate superior's DN. A DN unambiguously refers to an entry in the
+ * Directory.
+ * <p>
+ * The following are examples of string representations of DNs:
+ *
+ * <pre>
+ * UID=nobody@example.com,DC=example,DC=com CN=John
+ * Smith,OU=Sales,O=ACME Limited,L=Moab,ST=Utah,C=US
+ * </pre>
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc4512#section-2.3">RFC
+ * 4512 - Lightweight Directory Access Protocol (LDAP): Directory
+ * Information Models </a>
+ */
+public final class DN implements Iterable<RDN>, Comparable<DN>
+{
+ private static final DN ROOT_DN = new DN(null, null, "");
+
+ // This is the size of the per-thread per-schema DN cache. We should
+ // be conservative here in case there are many threads. We will only
+ // cache parent DNs, so there's no need for it to be big.
+ private static final int DN_CACHE_SIZE = 32;
+
+ private static final ThreadLocal<WeakHashMap<Schema, Map<String, DN>>> CACHE = new ThreadLocal<WeakHashMap<Schema, Map<String, DN>>>()
+ {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected WeakHashMap<Schema, Map<String, DN>> initialValue()
+ {
+ return new WeakHashMap<Schema, Map<String, DN>>();
+ }
+
+ };
+
+
+
+ /**
+ * Returns the Root DN. The Root DN does not contain and RDN
+ * components and is superior to all other DNs.
+ *
+ * @return The Root DN.
+ */
+ public static DN rootDN()
+ {
+ return ROOT_DN;
+ }
+
+
+
+ /**
+ * Parses the provided LDAP string representation of a DN using the
+ * default schema.
+ *
+ * @param dn
+ * The LDAP string representation of a DN.
+ * @return The parsed DN.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code dn} is not a valid LDAP string representation
+ * of a DN.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ public static DN valueOf(String dn)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ return valueOf(dn, Schema.getDefaultSchema());
+ }
+
+
+
+ /**
+ * Parses the provided LDAP string representation of a DN using the
+ * provided schema.
+ *
+ * @param dn
+ * The LDAP string representation of a DN.
+ * @param schema
+ * The schema to use when parsing the DN.
+ * @return The parsed DN.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code dn} is not a valid LDAP string representation
+ * of a DN.
+ * @throws NullPointerException
+ * If {@code dn} or {@code schema} was {@code null}.
+ */
+ public static DN valueOf(String dn, Schema schema)
+ throws LocalizedIllegalArgumentException
+ {
+ Validator.ensureNotNull(schema);
+ if (dn.length() == 0)
+ {
+ return ROOT_DN;
+ }
+
+ // First check if DN is already cached.
+ final Map<String, DN> cache = getCache(schema);
+ final DN cachedDN = cache.get(dn);
+ if (cachedDN != null)
+ {
+ return cachedDN;
+ }
+
+ // Not in cache so decode.
+ final SubstringReader reader = new SubstringReader(dn);
+ return decode(dn, reader, schema, cache);
+ }
+
+
+
+ // Decodes a DN using the provided reader and schema.
+ private static DN decode(String dnString, SubstringReader reader,
+ Schema schema, Map<String, DN> cache)
+ throws LocalizedIllegalArgumentException
+ {
+ reader.skipWhitespaces();
+ if (reader.remaining() == 0)
+ {
+ return ROOT_DN;
+ }
+
+ RDN rdn;
+ try
+ {
+ rdn = RDN.decode(null, reader, schema);
+ }
+ catch (final UnknownSchemaElementException e)
+ {
+ final Message message = ERR_DN_TYPE_NOT_FOUND.get(reader
+ .getString(), e.getMessageObject());
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ DN parent;
+ if (reader.remaining() > 0 && reader.read() == ',')
+ {
+ reader.mark();
+ final String parentString = reader.read(reader.remaining());
+
+ parent = cache.get(parentString);
+ if (parent == null)
+ {
+ reader.reset();
+ parent = decode(parentString, reader, schema, cache);
+
+ // Only cache parent DNs since leaf DNs are likely to make the
+ // cache to volatile.
+ cache.put(parentString, parent);
+ }
+ }
+ else
+ {
+ parent = ROOT_DN;
+ }
+
+ return new DN(rdn, parent, dnString);
+ }
+
+
+
+ @SuppressWarnings("serial")
+ private static Map<String, DN> getCache(Schema schema)
+ {
+ final WeakHashMap<Schema, Map<String, DN>> threadLocalMap = CACHE
+ .get();
+ Map<String, DN> schemaLocalMap = threadLocalMap.get(schema);
+
+ if (schemaLocalMap == null)
+ {
+ schemaLocalMap = new LinkedHashMap<String, DN>(DN_CACHE_SIZE,
+ 0.75f, true)
+ {
+ @Override
+ protected boolean removeEldestEntry(Map.Entry<String, DN> e)
+ {
+ return size() > DN_CACHE_SIZE;
+ }
+ };
+ threadLocalMap.put(schema, schemaLocalMap);
+ }
+ return schemaLocalMap;
+ }
+
+
+
+ private final RDN rdn;
+
+ private final DN parent;
+
+ private final int size;
+
+ // We need to store the original string value if provided in order to
+ // preserve the original whitespace.
+ private String stringValue;
+
+ private String normalizedStringValue = null;
+
+
+
+ // Private constructor.
+ private DN(RDN rdn, DN parent, String stringValue)
+ {
+ this.rdn = rdn;
+ this.parent = parent;
+ this.stringValue = stringValue;
+ this.size = parent != null ? parent.size + 1 : 0;
+ }
+
+
+
+ /**
+ * Returns a DN which is subordinate to this DN and having the
+ * additional RDN components contained in the provided DN.
+ *
+ * @param dn
+ * The DN containing the RDN components to be added to this
+ * DN.
+ * @return The subordinate DN.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ public DN child(DN dn) throws NullPointerException
+ {
+ Validator.ensureNotNull(dn);
+
+ if (dn.isRootDN())
+ {
+ return this;
+ }
+ else if (isRootDN())
+ {
+ return dn;
+ }
+ else
+ {
+ final RDN[] rdns = new RDN[dn.size()];
+ int i = rdns.length;
+ for (DN next = dn; next.rdn != null; next = next.parent)
+ {
+ rdns[--i] = next.rdn;
+ }
+ DN newDN = this;
+ for (i = 0; i < rdns.length; i++)
+ {
+ newDN = new DN(rdns[i], newDN, null);
+ }
+ return newDN;
+ }
+ }
+
+
+
+ /**
+ * Returns a DN which is an immediate child of this DN and having the
+ * specified RDN.
+ *
+ * @param rdn
+ * The RDN for the child DN.
+ * @return The child DN.
+ * @throws NullPointerException
+ * If {@code rdn} was {@code null}.
+ */
+ public DN child(RDN rdn) throws NullPointerException
+ {
+ Validator.ensureNotNull(rdn);
+ return new DN(rdn, this, null);
+ }
+
+
+
+ /**
+ * Returns a DN which is subordinate to this DN and having the
+ * additional RDN components contained in the provided DN decoded
+ * using the default schema.
+ *
+ * @param dn
+ * The DN containing the RDN components to be added to this
+ * DN.
+ * @return The subordinate DN.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code dn} is not a valid LDAP string representation
+ * of a DN.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ public DN child(String dn) throws LocalizedIllegalArgumentException,
+ NullPointerException
+ {
+ Validator.ensureNotNull(dn);
+ return child(valueOf(dn));
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int compareTo(DN dn)
+ {
+ final String s1 = toNormalizedString();
+ final String s2 = dn.toNormalizedString();
+ return s1.compareTo(s2);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ else if (obj instanceof DN)
+ {
+ final String s1 = toNormalizedString();
+ final String s2 = ((DN) obj).toNormalizedString();
+ return s1.equals(s2);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int hashCode()
+ {
+ final String s = toNormalizedString();
+ return s.hashCode();
+ }
+
+
+
+ /**
+ * Returns {@code true} if this DN is an immediate child of the
+ * provided DN.
+ *
+ * @param dn
+ * The potential parent DN.
+ * @return {@code true} if this DN is the immediate child of the
+ * provided DN, otherwise {@code false}.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ public boolean isChildOf(DN dn) throws NullPointerException
+ {
+ // If this is the Root DN then parent will be null but this is ok.
+ return dn.equals(parent);
+ }
+
+
+
+ /**
+ * Returns {@code true} if this DN is an immediate child of the
+ * provided DN decoded using the default schema.
+ *
+ * @param dn
+ * The potential parent DN.
+ * @return {@code true} if this DN is the immediate child of the
+ * provided DN, otherwise {@code false}.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code dn} is not a valid LDAP string representation
+ * of a DN.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ public boolean isChildOf(String dn)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ // If this is the Root DN then parent will be null but this is ok.
+ return isChildOf(valueOf(dn));
+ }
+
+
+
+ /**
+ * Returns {@code true} if this DN is the immediate parent of the
+ * provided DN.
+ *
+ * @param dn
+ * The potential child DN.
+ * @return {@code true} if this DN is the immediate parent of the
+ * provided DN, otherwise {@code false}.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ public boolean isParentOf(DN dn) throws NullPointerException
+ {
+ // If dn is the Root DN then parent will be null but this is ok.
+ return equals(dn.parent);
+ }
+
+
+
+ /**
+ * Returns {@code true} if this DN is the immediate parent of the
+ * provided DN.
+ *
+ * @param dn
+ * The potential child DN.
+ * @return {@code true} if this DN is the immediate parent of the
+ * provided DN, otherwise {@code false}.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code dn} is not a valid LDAP string representation
+ * of a DN.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ public boolean isParentOf(String dn)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ // If dn is the Root DN then parent will be null but this is ok.
+ return isParentOf(valueOf(dn));
+ }
+
+
+
+ /**
+ * Returns {@code true} if this DN is the Root DN.
+ *
+ * @return {@code true} if this DN is the Root DN, otherwise {@code
+ * false}.
+ */
+ public boolean isRootDN()
+ {
+ return size == 0;
+ }
+
+
+
+ /**
+ * Returns {@code true} if this DN is subordinate to or equal to the
+ * provided DN.
+ *
+ * @param dn
+ * The potential child DN.
+ * @return {@code true} if this DN is subordinate to or equal to the
+ * provided DN, otherwise {@code false}.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ public boolean isSubordinateOrEqualTo(DN dn)
+ throws NullPointerException
+ {
+ if (size < dn.size)
+ {
+ return false;
+ }
+ else if (size == dn.size)
+ {
+ return equals(dn);
+ }
+ else
+ {
+ // dn is a potential superior of this.
+ return parent(dn.size - size).equals(dn);
+ }
+ }
+
+
+
+ /**
+ * Returns {@code true} if this DN is subordinate to or equal to the
+ * provided DN.
+ *
+ * @param dn
+ * The potential child DN.
+ * @return {@code true} if this DN is subordinate to or equal to the
+ * provided DN, otherwise {@code false}.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code dn} is not a valid LDAP string representation
+ * of a DN.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ public boolean isSubordinateOrEqualTo(String dn)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ return isSubordinateOrEqualTo(valueOf(dn));
+ }
+
+
+
+ /**
+ * Returns {@code true} if this DN is superior to or equal to the
+ * provided DN.
+ *
+ * @param dn
+ * The potential child DN.
+ * @return {@code true} if this DN is superior to or equal to the
+ * provided DN, otherwise {@code false}.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ public boolean isSuperiorOrEqualTo(DN dn) throws NullPointerException
+ {
+ if (size > dn.size)
+ {
+ return false;
+ }
+ else if (size == dn.size)
+ {
+ return equals(dn);
+ }
+ else
+ {
+ // dn is a potential subordinate of this.
+ return dn.parent(dn.size - size).equals(this);
+ }
+ }
+
+
+
+ /**
+ * Returns {@code true} if this DN is superior to or equal to the
+ * provided DN.
+ *
+ * @param dn
+ * The potential child DN.
+ * @return {@code true} if this DN is superior to or equal to the
+ * provided DN, otherwise {@code false}.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code dn} is not a valid LDAP string representation
+ * of a DN.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ public boolean isSuperiorOrEqualTo(String dn)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ return isSuperiorOrEqualTo(valueOf(dn));
+ }
+
+
+
+ /**
+ * Returns an iterator of the RDNs contained in this DN. The RDNs will
+ * be returned in the order starting with this DN's RDN, followed by
+ * the RDN of the parent DN, and so on.
+ * <p>
+ * Attempts to remove RDNs using an iterator's {@code remove()} method
+ * are not permitted and will result in an {@code
+ * UnsupportedOperationException} being thrown.
+ *
+ * @return An iterator of the RDNs contained in this DN.
+ */
+ public Iterator<RDN> iterator()
+ {
+ return new Iterator<RDN>()
+ {
+ private DN dn = DN.this;
+
+
+
+ public boolean hasNext()
+ {
+ return dn.rdn != null;
+ }
+
+
+
+ public RDN next()
+ {
+ if (dn.rdn == null)
+ {
+ throw new NoSuchElementException();
+ }
+
+ final RDN rdn = dn.rdn;
+ dn = dn.parent;
+ return rdn;
+ }
+
+
+
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+
+
+ /**
+ * Returns the DN which is the immediate parent of this DN, or {@code
+ * null} if this DN is the Root DN.
+ * <p>
+ * This method is equivalent to:
+ *
+ * <pre>
+ * parent(1);
+ * </pre>
+ *
+ * @return The DN which is the immediate parent of this DN, or {@code
+ * null} if this DN is the Root DN.
+ */
+ public DN parent()
+ {
+ return parent;
+ }
+
+
+
+ /**
+ * Returns the DN which is equal to this DN with the specified number
+ * of RDNs removed. Note that if {@code index} is zero then this DN
+ * will be returned (identity).
+ *
+ * @param index
+ * The number of RDNs to be removed.
+ * @return The DN which is equal to this DN with the specified number
+ * of RDNs removed, or {@code null} if the parent of the Root
+ * DN is reached.
+ * @throws IllegalArgumentException
+ * If {@code index} is less than zero.
+ */
+ public DN parent(int index) throws IllegalArgumentException
+ {
+ // We allow size + 1 so that we can return null as the parent of the
+ // Root DN.
+ Validator.ensureTrue(index >= 0, "index less than zero");
+
+ DN parentDN = this;
+ for (int i = 0; parentDN != null && i < index; i++)
+ {
+ parentDN = parentDN.parent;
+ }
+ return parentDN;
+ }
+
+
+
+ /**
+ * Returns the RDN of this DN, or {@code null} if this DN is the Root
+ * DN.
+ *
+ * @return The RDN of this DN, or {@code null} if this DN is the Root
+ * DN.
+ */
+ public RDN rdn()
+ {
+ return rdn;
+ }
+
+
+
+ /**
+ * Returns the number of RDN components in this DN.
+ *
+ * @return The number of RDN components in this DN.
+ */
+ public int size()
+ {
+ return size();
+ }
+
+
+
+ /**
+ * Returns the normalized string representation of this DN.
+ *
+ * @return The normalized string representation of this DN.
+ */
+ public String toNormalizedString()
+ {
+ if (normalizedStringValue == null)
+ {
+ final StringBuilder builder = new StringBuilder();
+ if (!parent.isRootDN())
+ {
+ builder.append(parent.toNormalizedString());
+ builder.append(',');
+ }
+ rdn.toNormalizedString(builder);
+ normalizedStringValue = builder.toString();
+ }
+ return normalizedStringValue;
+ }
+
+
+
+ /**
+ * Returns the RFC 4514 string representation of this DN.
+ *
+ * @return The RFC 4514 string representation of this DN.
+ * @see <a href="http://tools.ietf.org/html/rfc4514">RFC 4514 -
+ * Lightweight Directory Access Protocol (LDAP): String
+ * Representation of Distinguished Names </a>
+ */
+ public String toString()
+ {
+ // We don't care about potential race conditions here.
+ if (stringValue == null)
+ {
+ final StringBuilder builder = new StringBuilder();
+ rdn.toString(builder);
+ if (!parent.isRootDN())
+ {
+ builder.append(',');
+ builder.append(parent.toString());
+ }
+ stringValue = builder.toString();
+ }
+ return stringValue;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/DecodeException.java b/sdk/src/org/opends/sdk/DecodeException.java
new file mode 100644
index 0000000..fabaca2
--- /dev/null
+++ b/sdk/src/org/opends/sdk/DecodeException.java
@@ -0,0 +1,154 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.LocalizableException;
+
+
+
+/**
+ * Thrown when data from an input source cannot be decoded, perhaps due
+ * to the data being malformed in some way. By default decoding
+ * exceptions are fatal, indicating that the associated input source is
+ * no longer usable.
+ */
+@SuppressWarnings("serial")
+public final class DecodeException extends IOException implements
+ LocalizableException
+{
+ private final Message message;
+
+ private final boolean isFatal;
+
+
+
+ /**
+ * Creates a new fatal decode exception with the provided message. The
+ * associated input source can no longer be used.
+ *
+ * @param message
+ * The message that explains the problem that occurred.
+ * @return The new fatal decode exception.
+ */
+ public static DecodeException fatalError(Message message)
+ {
+ return new DecodeException(message, true, null);
+ }
+
+
+
+ /**
+ * Creates a new fatal decode exception with the provided message and
+ * root cause. The associated input source can no longer be used.
+ *
+ * @param message
+ * The message that explains the problem that occurred.
+ * @param cause
+ * The underlying cause of this exception.
+ * @return The new fatal decode exception.
+ */
+ public static DecodeException fatalError(Message message,
+ Throwable cause)
+ {
+ return new DecodeException(message, true, cause);
+ }
+
+
+
+ /**
+ * Creates a new non-fatal decode exception with the provided message.
+ *
+ * @param message
+ * The message that explains the problem that occurred.
+ * @return The new non-fatal decode exception.
+ */
+ public static DecodeException error(Message message)
+ {
+ return new DecodeException(message, false, null);
+ }
+
+
+
+ /**
+ * Creates a new non-fatal decode exception with the provided message
+ * and root cause.
+ *
+ * @param message
+ * The message that explains the problem that occurred.
+ * @param cause
+ * The underlying cause of this exception.
+ * @return The new non-fatal decode exception.
+ */
+ public static DecodeException error(Message message, Throwable cause)
+ {
+ return new DecodeException(message, false, cause);
+ }
+
+
+
+ // Construction is provided via factory methods.
+ private DecodeException(Message message, boolean isFatal,
+ Throwable cause)
+ {
+ super(message.toString(), cause);
+ this.message = message;
+ this.isFatal = isFatal;
+ }
+
+
+
+ /**
+ * Returns the message that explains the problem that occurred.
+ *
+ * @return Message of the problem
+ */
+ public Message getMessageObject()
+ {
+ return message;
+ }
+
+
+
+ /**
+ * Indicates whether or not the error was fatal and the associated
+ * input source can no longer be used.
+ *
+ * @return {@code true} if the error was fatal and the associated
+ * input source can no longer be used, otherwise {@code false}
+ * .
+ */
+ public boolean isFatal()
+ {
+ return isFatal;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/DereferenceAliasesPolicy.java b/sdk/src/org/opends/sdk/DereferenceAliasesPolicy.java
new file mode 100644
index 0000000..43cf1fb
--- /dev/null
+++ b/sdk/src/org/opends/sdk/DereferenceAliasesPolicy.java
@@ -0,0 +1,223 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+
+
+/**
+ * A Search operation alias dereferencing policy as defined in RFC 4511
+ * section 4.5.1.3 is used to indicate whether or not alias entries (as
+ * defined in RFC 4512) are to be dereferenced during stages of a Search
+ * operation. The act of dereferencing an alias includes recursively
+ * dereferencing aliases that refer to aliases.
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc4511#section-4.5.1.3">RFC
+ * 4511 - Lightweight Directory Access Protocol (LDAP): The
+ * Protocol </a>
+ * @see <a href="http://tools.ietf.org/html/rfc4512">RFC 4512 -
+ * Lightweight Directory Access Protocol (LDAP): Directory
+ * Information Models </a>
+ */
+public final class DereferenceAliasesPolicy
+{
+ private static final DereferenceAliasesPolicy[] ELEMENTS = new DereferenceAliasesPolicy[4];
+
+ private static final List<DereferenceAliasesPolicy> IMMUTABLE_ELEMENTS = Collections
+ .unmodifiableList(Arrays.asList(ELEMENTS));
+
+ /**
+ * Do not dereference aliases in searching or in locating the base
+ * object of a Search operation.
+ */
+ public static final DereferenceAliasesPolicy NEVER = register(0,
+ "never");
+
+ /**
+ * While searching subordinates of the base object, dereference any
+ * alias within the scope of the Search operation. Dereferenced
+ * objects become the vertices of further search scopes where the
+ * Search operation is also applied. If the search scope is {@code
+ * WHOLE_SUBTREE}, the Search continues in the subtree(s) of any
+ * dereferenced object. If the search scope is {@code SINGLE_LEVEL},
+ * the search is applied to any dereferenced objects and is not
+ * applied to their subordinates.
+ */
+ public static final DereferenceAliasesPolicy IN_SEARCHING = register(
+ 1, "search");
+
+ /**
+ * Dereference aliases in locating the base object of a Search
+ * operation, but not when searching subordinates of the base object.
+ */
+ public static final DereferenceAliasesPolicy FINDING_BASE = register(
+ 2, "find");
+
+ /**
+ * Dereference aliases both in searching and in locating the base
+ * object of a Search operation.
+ */
+ public static final DereferenceAliasesPolicy ALWAYS = register(3,
+ "always");
+
+
+
+ /**
+ * Returns the alias dereferencing policy having the specified integer
+ * value as defined in RFC 4511 section 4.5.1.
+ *
+ * @param intValue
+ * The integer value of the alias dereferencing policy.
+ * @return The dereference aliases policy, or {@code null} if there
+ * was no alias dereferencing policy associated with {@code
+ * intValue}.
+ */
+ public static DereferenceAliasesPolicy valueOf(int intValue)
+ {
+ if (intValue < 0 || intValue >= ELEMENTS.length)
+ {
+ return null;
+ }
+ return ELEMENTS[intValue];
+ }
+
+
+
+ /**
+ * Returns an unmodifiable list containing the set of available alias
+ * dereferencing policies indexed on their integer value as defined in
+ * RFC 4511 section 4.5.1.
+ *
+ * @return An unmodifiable list containing the set of available alias
+ * dereferencing policies.
+ */
+ public static List<DereferenceAliasesPolicy> values()
+ {
+ return IMMUTABLE_ELEMENTS;
+ }
+
+
+
+ /**
+ * Creates and registers a new alias dereferencing policy with the
+ * application.
+ *
+ * @param intValue
+ * The integer value of the alias dereferencing policy as
+ * defined in RFC 4511 section 4.5.1.
+ * @param name
+ * The name of the alias dereferencing policy.
+ * @return The new alias dereferencing policy.
+ */
+ private static DereferenceAliasesPolicy register(int intValue,
+ String name)
+ {
+ final DereferenceAliasesPolicy t = new DereferenceAliasesPolicy(
+ intValue, name);
+ ELEMENTS[intValue] = t;
+ return t;
+ }
+
+
+
+ private final int intValue;
+
+ private final String name;
+
+
+
+ // Prevent direct instantiation.
+ private DereferenceAliasesPolicy(int intValue, String name)
+ {
+ this.intValue = intValue;
+ this.name = name;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ else if (obj instanceof DereferenceAliasesPolicy)
+ {
+ return this.intValue == ((DereferenceAliasesPolicy) obj).intValue;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int hashCode()
+ {
+ return intValue;
+ }
+
+
+
+ /**
+ * Returns the integer value of this alias dereferencing policy as
+ * defined in RFC 4511 section 4.5.1.
+ *
+ * @return The integer value of this alias dereferencing policy.
+ */
+ public int intValue()
+ {
+ return intValue;
+ }
+
+
+
+ /**
+ * Returns the string representation of this alias dereferencing
+ * policy.
+ *
+ * @return The string representation of this alias dereferencing
+ * policy.
+ */
+ public String toString()
+ {
+ return name;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/Entry.java b/sdk/src/org/opends/sdk/Entry.java
new file mode 100644
index 0000000..ed49121
--- /dev/null
+++ b/sdk/src/org/opends/sdk/Entry.java
@@ -0,0 +1,603 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.Collection;
+
+import org.opends.sdk.schema.ObjectClass;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * An entry, comprising of a distinguished name and zero or more
+ * attributes.
+ * <p>
+ * Some methods require a schema in order to decode their parameters
+ * (e.g. {@link #addAttribute(String, Object...)} and
+ * {@link #setName(String)}). In these cases the default schema is used
+ * unless an alternative schema is specified in the {@code Entry}
+ * constructor. The default schema is not used for any other purpose. In
+ * particular, an {@code Entry} will permit attributes to be added which
+ * have been decoded using a different schema.
+ * <p>
+ * Full LDAP modify semantics are provided via the {@link #addAttribute}, {@link #removeAttribute}, and {@link #replaceAttribute} methods.
+ * <p>
+ * Implementations should specify any constraints or special behavior.
+ * In particular, which methods are supported, and the order in which
+ * attributes are returned using the {@link #getAttributes()} method.
+ * <p>
+ * TODO: can we return collections/lists instead of iterables?
+ * <p>
+ * TODO: containsAttributeValue(String, Object)
+ */
+public interface Entry
+{
+
+ /**
+ * Adds all of the attribute values contained in {@code attribute} to
+ * this entry, merging with any existing attribute values (optional
+ * operation). If {@code attribute} is empty then this entry is left
+ * unchanged.
+ * <p>
+ * <b>NOTE:</b> This method implements LDAP Modify add semantics.
+ *
+ * @param attribute
+ * The attribute values to be added to this entry, merging
+ * with any existing attribute values.
+ * @param duplicateValues
+ * A collection into which duplicate values will be added, or
+ * {@code null} if duplicate values should not be saved.
+ * @return {@code true} if this entry changed as a result of this
+ * call.
+ * @throws UnsupportedOperationException
+ * If this entry does not permit attributes or their values
+ * to be added.
+ * @throws NullPointerException
+ * If {@code attribute} was {@code null}.
+ */
+ boolean addAttribute(Attribute attribute,
+ Collection<ByteString> duplicateValues)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Adds all of the attribute values contained in {@code attribute} to
+ * this entry, merging with any existing attribute values (optional
+ * operation). If {@code attribute} is empty then this entry is left
+ * unchanged.
+ * <p>
+ * If {@code attribute} is an instance of {@code Attribute} then it
+ * will be added to this entry as if {@link #addAttribute} was called.
+ * <p>
+ * If {@code attribute} is not an instance of {@code Attribute} then
+ * its attribute description will be decoded using the schema
+ * associated with this entry, and any attribute values which are not
+ * instances of {@code ByteString} will be converted using the
+ * {@link ByteString#valueOf(Object)} method.
+ * <p>
+ * <b>NOTE:</b> This method implements LDAP Modify add semantics.
+ *
+ * @param attribute
+ * The attribute values to be added to this entry merging
+ * with any existing attribute values.
+ * @return {@code true} if this entry changed as a result of this
+ * call.
+ * @throws UnsupportedOperationException
+ * If this entry does not permit attributes or their values
+ * to be added.
+ * @throws NullPointerException
+ * If {@code attribute} was {@code null}.
+ */
+ boolean addAttribute(Attribute attribute)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Adds all of the attribute values contained in {@code values} to
+ * this entry, merging with any existing attribute values (optional
+ * operation). If {@code values} is {@code null} or empty then this
+ * entry is left unchanged.
+ * <p>
+ * The attribute description will be decoded using the schema
+ * associated with this entry.
+ * <p>
+ * Any attribute values which are not instances of {@code ByteString}
+ * will be converted using the {@link ByteString#valueOf(Object)}
+ * method.
+ * <p>
+ * <b>NOTE:</b> This method implements LDAP Modify add semantics.
+ *
+ * @param attributeDescription
+ * The name of the attribute whose values are to be added.
+ * @param values
+ * The attribute values to be added to this entry, merging
+ * any existing attribute values.
+ * @return This entry.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code attributeDescription} could not be decoded
+ * using the schema associated with this entry.
+ * @throws UnsupportedOperationException
+ * If this entry does not permit attributes or their values
+ * to be added.
+ * @throws NullPointerException
+ * If {@code attribute} was {@code null}.
+ */
+ Entry addAttribute(String attributeDescription, Object... values)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Removes all the attributes from this entry (optional operation).
+ *
+ * @return This entry.
+ * @throws UnsupportedOperationException
+ * If this entry does not permit attributes to be removed.
+ */
+ Entry clearAttributes() throws UnsupportedOperationException;
+
+
+
+ /**
+ * Indicates whether or not this entry contains the named attribute.
+ *
+ * @param attributeDescription
+ * The name of the attribute.
+ * @return {@code true} if this entry contains the named attribute,
+ * otherwise {@code false}.
+ * @throws NullPointerException
+ * If {@code attributeDescription} was {@code null}.
+ */
+ boolean containsAttribute(AttributeDescription attributeDescription)
+ throws NullPointerException;
+
+
+
+ /**
+ * Indicates whether or not this entry contains the named attribute.
+ * <p>
+ * The attribute description will be decoded using the schema
+ * associated with this entry.
+ *
+ * @param attributeDescription
+ * The name of the attribute.
+ * @return {@code true} if this entry contains the named attribute,
+ * otherwise {@code false}.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code attributeDescription} could not be decoded
+ * using the schema associated with this entry.
+ * @throws NullPointerException
+ * If {@code attributeDescription} was {@code null}.
+ */
+ boolean containsAttribute(String attributeDescription)
+ throws LocalizedIllegalArgumentException, NullPointerException;
+
+
+
+ /**
+ * Indicates whether or not this entry contains the provided object
+ * class.
+ *
+ * @param objectClass
+ * The object class.
+ * @return {@code true} if this entry contains the object class,
+ * otherwise {@code false}.
+ * @throws NullPointerException
+ * If {@code objectClass} was {@code null}.
+ */
+ boolean containsObjectClass(ObjectClass objectClass)
+ throws NullPointerException;
+
+
+
+ /**
+ * Indicates whether or not this entry contains the named object
+ * class.
+ *
+ * @param objectClass
+ * The name of the object class.
+ * @return {@code true} if this entry contains the object class,
+ * otherwise {@code false}.
+ * @throws NullPointerException
+ * If {@code objectClass} was {@code null}.
+ */
+ boolean containsObjectClass(String objectClass)
+ throws NullPointerException;
+
+
+
+ /**
+ * Returns {@code true} if {@code object} is an entry which is equal
+ * to this entry. Two entries are considered equal if their
+ * distinguished names are equal, they both have the same number of
+ * attributes, and every attribute contained in the first entry is
+ * also contained in the second entry.
+ *
+ * @param object
+ * The object to be tested for equality with this entry.
+ * @return {@code true} if {@code object} is an entry which is equal
+ * to this entry, or {@code false} if not.
+ */
+ boolean equals(Object object);
+
+
+
+ /**
+ * Returns an {@code Iterable} containing all the attributes in this
+ * entry having an attribute description which is a sub-type of the
+ * provided attribute description. The returned {@code Iterable} may
+ * be used to remove attributes if permitted by this entry.
+ *
+ * @param attributeDescription
+ * The name of the attributes to be returned.
+ * @return An {@code Iterable} containing the matching attributes.
+ * @throws NullPointerException
+ * If {@code attributeDescription} was {@code null}.
+ */
+ Iterable<Attribute> findAttributes(
+ AttributeDescription attributeDescription)
+ throws NullPointerException;
+
+
+
+ /**
+ * Returns an {@code Iterable} containing all the attributes in this
+ * entry having an attribute description which is a sub-type of the
+ * provided attribute description. The returned {@code Iterable} may
+ * be used to remove attributes if permitted by this entry.
+ * <p>
+ * The attribute description will be decoded using the schema
+ * associated with this entry.
+ *
+ * @param attributeDescription
+ * The name of the attributes to be returned.
+ * @return An {@code Iterable} containing the matching attributes.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code attributeDescription} could not be decoded
+ * using the schema associated with this entry.
+ * @throws NullPointerException
+ * If {@code attributeDescription} was {@code null}.
+ */
+ Iterable<Attribute> findAttributes(String attributeDescription)
+ throws LocalizedIllegalArgumentException, NullPointerException;
+
+
+
+ /**
+ * Returns the named attribute contained in this entry, or {@code
+ * null} if it is not included with this entry.
+ *
+ * @param attributeDescription
+ * The name of the attribute to be returned.
+ * @return The named attribute, or {@code null} if it is not included
+ * with this entry.
+ * @throws NullPointerException
+ * If {@code attributeDescription} was {@code null}.
+ */
+ Attribute getAttribute(AttributeDescription attributeDescription)
+ throws NullPointerException;
+
+
+
+ /**
+ * Returns the named attribute contained in this entry, or {@code
+ * null} if it is not included with this entry.
+ * <p>
+ * The attribute description will be decoded using the schema
+ * associated with this entry.
+ *
+ * @param attributeDescription
+ * The name of the attribute to be returned.
+ * @return The named attribute, or {@code null} if it is not included
+ * with this entry.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code attributeDescription} could not be decoded
+ * using the schema associated with this entry.
+ * @throws NullPointerException
+ * If {@code attributeDescription} was {@code null}.
+ */
+ Attribute getAttribute(String attributeDescription)
+ throws LocalizedIllegalArgumentException, NullPointerException;
+
+
+
+ /**
+ * Returns the number of attributes in this entry.
+ *
+ * @return The number of attributes.
+ */
+ int getAttributeCount();
+
+
+
+ /**
+ * Returns an {@code Iterable} containing the attributes in this
+ * entry. The returned {@code Iterable} may be used to remove
+ * attributes if permitted by this entry.
+ *
+ * @return An {@code Iterable} containing the attributes.
+ */
+ Iterable<Attribute> getAttributes();
+
+
+
+ /**
+ * Returns the string representation of the distinguished name of this
+ * entry.
+ *
+ * @return The string representation of the distinguished name.
+ */
+ DN getName();
+
+
+
+ /**
+ * Returns an {@code Iterable} containing the names of the object
+ * classes in this entry. The returned {@code Iterable} may be used to
+ * remove object classes if permitted by this entry.
+ *
+ * @return An {@code Iterable} containing the object classes.
+ */
+ Iterable<String> getObjectClasses();
+
+
+
+ /**
+ * Returns the hash code for this entry. It will be calculated as the
+ * sum of the hash codes of the distinguished name and all of the
+ * attributes.
+ *
+ * @return The hash code for this entry.
+ */
+ int hashCode();
+
+
+
+ /**
+ * Removes all of the attribute values contained in {@code attribute}
+ * from this entry if it is present (optional operation). If {@code
+ * attribute} is empty then the entire attribute will be removed if it
+ * is present.
+ * <p>
+ * <b>NOTE:</b> This method implements LDAP Modify delete semantics.
+ *
+ * @param attribute
+ * The attribute values to be removed from this entry, which
+ * may be empty if the entire attribute is to be removed.
+ * @param missingValues
+ * A collection into which missing values will be added, or
+ * {@code null} if missing values should not be saved.
+ * @return {@code true} if this entry changed as a result of this
+ * call.
+ * @throws UnsupportedOperationException
+ * If this entry does not permit attributes or their values
+ * to be removed.
+ * @throws NullPointerException
+ * If {@code attribute} was {@code null}.
+ */
+ boolean removeAttribute(Attribute attribute,
+ Collection<ByteString> missingValues)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Removes the named attribute from this entry if it is present
+ * (optional operation). If this attribute does not contain the
+ * attribute, the call leaves this entry unchanged and returns {@code
+ * false}.
+ *
+ * @param attributeDescription
+ * The name of the attribute to be removed.
+ * @return {@code true} if this entry changed as a result of this
+ * call.
+ * @throws UnsupportedOperationException
+ * If this entry does not permit attributes to be removed.
+ * @throws NullPointerException
+ * If {@code attributeDescription} was {@code null}.
+ */
+ boolean removeAttribute(AttributeDescription attributeDescription)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Removes the named attribute from this entry if it is present
+ * (optional operation). If this attribute does not contain the
+ * attribute, the call leaves this entry unchanged.
+ * <p>
+ * The attribute description will be decoded using the schema
+ * associated with this entry.
+ *
+ * @param attributeDescription
+ * The name of the attribute to be removed.
+ * @return This entry.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code attributeDescription} could not be decoded
+ * using the schema associated with this entry.
+ * @throws UnsupportedOperationException
+ * If this entry does not permit attributes to be removed.
+ * @throws NullPointerException
+ * If {@code attributeDescription} was {@code null}.
+ */
+ Entry removeAttribute(String attributeDescription)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Removes all of the attribute values contained in {@code values}
+ * from the named attribute in this entry if it is present (optional
+ * operation). If {@code values} is {@code null} or empty then the
+ * entire attribute will be removed if it is present.
+ * <p>
+ * The attribute description will be decoded using the schema
+ * associated with this entry.
+ * <p>
+ * Any attribute values which are not instances of {@code ByteString}
+ * will be converted using the {@link ByteString#valueOf(Object)}
+ * method.
+ * <p>
+ * <b>NOTE:</b> This method implements LDAP Modify delete semantics.
+ *
+ * @param attributeDescription
+ * The name of the attribute whose values are to be removed.
+ * @param values
+ * The attribute values to be removed from this entry, which
+ * may be {@code null} or empty if the entire attribute is to
+ * be removed.
+ * @return This entry.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code attributeDescription} could not be decoded
+ * using the schema associated with this entry.
+ * @throws UnsupportedOperationException
+ * If this entry does not permit attributes or their values
+ * to be removed.
+ * @throws NullPointerException
+ * If {@code attributeDescription} was {@code null}.
+ */
+ Entry removeAttribute(String attributeDescription, Object... values)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Adds all of the attribute values contained in {@code attribute} to
+ * this entry, replacing any existing attribute values (optional
+ * operation). If {@code attribute} is empty then the entire attribute
+ * will be removed if it is present.
+ * <p>
+ * <b>NOTE:</b> This method implements LDAP Modify replace semantics.
+ *
+ * @param attribute
+ * The attribute values to be added to this entry, replacing
+ * any existing attribute values, and which may be empty if
+ * the entire attribute is to be removed.
+ * @return {@code true} if this entry changed as a result of this
+ * call.
+ * @throws UnsupportedOperationException
+ * If this entry does not permit attributes or their values
+ * to be replaced.
+ * @throws NullPointerException
+ * If {@code attribute} was {@code null}.
+ */
+ boolean replaceAttribute(Attribute attribute)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Adds all of the attribute values contained in {@code values} to
+ * this entry, replacing any existing attribute values (optional
+ * operation). If {@code values} is {@code null} or empty then the
+ * entire attribute will be removed if it is present.
+ * <p>
+ * The attribute description will be decoded using the schema
+ * associated with this entry.
+ * <p>
+ * Any attribute values which are not instances of {@code ByteString}
+ * will be converted using the {@link ByteString#valueOf(Object)}
+ * method.
+ * <p>
+ * <b>NOTE:</b> This method implements LDAP Modify replace semantics.
+ *
+ * @param attributeDescription
+ * The name of the attribute whose values are to be replaced.
+ * @param values
+ * The attribute values to be added to this entry, replacing
+ * any existing attribute values, and which may be {@code
+ * null} or empty if the entire attribute is to be removed.
+ * @return This entry.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code attributeDescription} could not be decoded
+ * using the schema associated with this entry.
+ * @throws UnsupportedOperationException
+ * If this entry does not permit attributes or their values
+ * to be replaced.
+ * @throws NullPointerException
+ * If {@code attribute} was {@code null}.
+ */
+ Entry replaceAttribute(String attributeDescription, Object... values)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the distinguished name of this entry (optional operation).
+ *
+ * @param dn
+ * The string representation of the distinguished name.
+ * @return This entry.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code dn} could not be decoded using the schema
+ * associated with this entry.
+ * @throws UnsupportedOperationException
+ * If this entry does not permit the distinguished name to
+ * be set.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ Entry setName(String dn) throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the distinguished name of this entry (optional operation).
+ *
+ * @param dn
+ * The distinguished name.
+ * @return This entry.
+ * @throws UnsupportedOperationException
+ * If this entry does not permit the distinguished name to
+ * be set.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ Entry setName(DN dn) throws UnsupportedOperationException,
+ NullPointerException;
+
+
+
+ /**
+ * Returns a string representation of this entry.
+ *
+ * @return The string representation of this entry.
+ */
+ String toString();
+}
diff --git a/sdk/src/org/opends/sdk/ErrorResultException.java b/sdk/src/org/opends/sdk/ErrorResultException.java
new file mode 100644
index 0000000..5a4d7dc
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ErrorResultException.java
@@ -0,0 +1,103 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.concurrent.ExecutionException;
+
+import org.opends.sdk.responses.Result;
+
+
+
+/**
+ * Thrown when the result code returned in a Result indicates that the
+ * Request was unsuccessful.
+ */
+@SuppressWarnings("serial")
+public class ErrorResultException extends ExecutionException
+{
+ private final Result result;
+
+
+
+ /**
+ * Wraps the provided result in an appropriate error result exception.
+ * The type of error result exception used depends on the underlying
+ * result code.
+ *
+ * @param result
+ * The result whose result code indicates a failure.
+ * @return The error result exception wrapping the provided result.
+ * @throws IllegalArgumentException
+ * If the provided result does not represent a failure.
+ * @throws NullPointerException
+ * If {@code result} was {@code null}.
+ */
+ public static ErrorResultException wrap(Result result)
+ throws IllegalArgumentException, NullPointerException
+ {
+ if (!result.getResultCode().isExceptional())
+ {
+ throw new IllegalArgumentException(
+ "Attempted to wrap a successful result: " + result);
+ }
+
+ // TODO: choose type of exception based on result code (e.g.
+ // referral).
+ return new ErrorResultException(result);
+ }
+
+
+
+ /**
+ * Creates a new error result exception using the provided result.
+ *
+ * @param result
+ * The error result.
+ */
+ ErrorResultException(Result result)
+ {
+ super(result.getResultCode() + ": " + result.getDiagnosticMessage());
+ this.result = result;
+ }
+
+
+
+ /**
+ * Returns the error result which caused this exception to be thrown.
+ * The type of result returned corresponds to the expected result type
+ * of the original request.
+ *
+ * @return The error result which caused this exception to be thrown.
+ */
+ public Result getResult()
+ {
+ return result;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ErrorResultIOException.java b/sdk/src/org/opends/sdk/ErrorResultIOException.java
new file mode 100644
index 0000000..a2db44a
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ErrorResultIOException.java
@@ -0,0 +1,75 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.io.IOException;
+
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * An {@code ErrorResultIOException} adapts an {@code
+ * ErrorResultException} to an {@code IOException}.
+ */
+@SuppressWarnings("serial")
+public final class ErrorResultIOException extends IOException
+{
+ private final ErrorResultException cause;
+
+
+
+ /**
+ * Creates a new error result IO exception with the provided cause.
+ *
+ * @param cause
+ * The cause which may be later retrieved by the
+ * {@link #getCause} method.
+ * @throws NullPointerException
+ * If {@code cause} was {@code null}.
+ */
+ public ErrorResultIOException(ErrorResultException cause)
+ throws NullPointerException
+ {
+ super(Validator.ensureNotNull(cause));
+
+ this.cause = cause;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ErrorResultException getCause()
+ {
+ return cause;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/Filter.java b/sdk/src/org/opends/sdk/Filter.java
new file mode 100644
index 0000000..6370007
--- /dev/null
+++ b/sdk/src/org/opends/sdk/Filter.java
@@ -0,0 +1,1978 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import static org.opends.messages.ProtocolMessages.*;
+import static org.opends.sdk.util.StaticUtils.byteToHex;
+import static org.opends.sdk.util.StaticUtils.getBytes;
+import static org.opends.sdk.util.StaticUtils.toLowerCase;
+
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.*;
+
+
+
+/**
+ * A search filter as defined in RFC 4511. In addition this class also
+ * provides support for the absolute true and absolute false filters as
+ * defined in RFC 4526.
+ * <p>
+ * This class provides many factory methods for creating common types of
+ * filter. Applications interact with a filter using
+ * {@link FilterVisitor} which is applied to a filter using the
+ * {@link #accept(FilterVisitor, Object)} method.
+ * <p>
+ * The RFC 4515 string representation of a filter can be generated using
+ * the {@link #toString} methods and parsed using the
+ * {@link #valueOf(String)} factory method.
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc4511">RFC 4511 -
+ * Lightweight Directory Access Protocol (LDAP): The Protocol </a>
+ * @see <a href="http://tools.ietf.org/html/rfc4515">RFC 4515 - String
+ * Representation of Search Filters </a>
+ * @see <a href="http://tools.ietf.org/html/rfc4526">RFC 4526 - Absolute
+ * True and False Filters </a>
+ */
+public final class Filter
+{
+ private static final class AndImpl extends Impl
+ {
+ private final List<Filter> subFilters;
+
+
+
+ public AndImpl(List<Filter> subFilters)
+ {
+ this.subFilters = subFilters;
+ }
+
+
+
+ @Override
+ public <R, P> R accept(FilterVisitor<R, P> v, P p)
+ {
+ return v.visitAndFilter(p, subFilters);
+ }
+
+ }
+
+
+
+ private static final class ApproxMatchImpl extends Impl
+ {
+
+ private final ByteSequence assertionValue;
+
+ private final String attributeDescription;
+
+
+
+ public ApproxMatchImpl(String attributeDescription,
+ ByteSequence assertionValue)
+ {
+ this.attributeDescription = attributeDescription;
+ this.assertionValue = assertionValue;
+ }
+
+
+
+ @Override
+ public <R, P> R accept(FilterVisitor<R, P> v, P p)
+ {
+ return v.visitApproxMatchFilter(p, attributeDescription,
+ assertionValue);
+ }
+
+ }
+
+
+
+ private static final class EqualityMatchImpl extends Impl
+ {
+
+ private final ByteSequence assertionValue;
+
+ private final String attributeDescription;
+
+
+
+ public EqualityMatchImpl(String attributeDescription,
+ ByteSequence assertionValue)
+ {
+ this.attributeDescription = attributeDescription;
+ this.assertionValue = assertionValue;
+ }
+
+
+
+ @Override
+ public <R, P> R accept(FilterVisitor<R, P> v, P p)
+ {
+ return v.visitEqualityMatchFilter(p, attributeDescription,
+ assertionValue);
+ }
+
+ }
+
+
+
+ private static final class ExtensibleMatchImpl extends Impl
+ {
+ private final String attributeDescription;
+
+ private final boolean dnAttributes;
+
+ private final String matchingRule;
+
+ private final ByteSequence matchValue;
+
+
+
+ public ExtensibleMatchImpl(String matchingRule,
+ String attributeDescription, ByteSequence matchValue,
+ boolean dnAttributes)
+ {
+ this.matchingRule = matchingRule;
+ this.attributeDescription = attributeDescription;
+ this.matchValue = matchValue;
+ this.dnAttributes = dnAttributes;
+ }
+
+
+
+ @Override
+ public <R, P> R accept(FilterVisitor<R, P> v, P p)
+ {
+ return v.visitExtensibleMatchFilter(p, matchingRule,
+ attributeDescription, matchValue, dnAttributes);
+ }
+
+ }
+
+
+
+ private static final class GreaterOrEqualImpl extends Impl
+ {
+
+ private final ByteSequence assertionValue;
+
+ private final String attributeDescription;
+
+
+
+ public GreaterOrEqualImpl(String attributeDescription,
+ ByteSequence assertionValue)
+ {
+ this.attributeDescription = attributeDescription;
+ this.assertionValue = assertionValue;
+ }
+
+
+
+ @Override
+ public <R, P> R accept(FilterVisitor<R, P> v, P p)
+ {
+ return v.visitGreaterOrEqualFilter(p, attributeDescription,
+ assertionValue);
+ }
+
+ }
+
+
+
+ private static abstract class Impl
+ {
+ protected Impl()
+ {
+ // Nothing to do.
+ }
+
+
+
+ public abstract <R, P> R accept(FilterVisitor<R, P> v, P p);
+ }
+
+
+
+ private static final class LessOrEqualImpl extends Impl
+ {
+
+ private final ByteSequence assertionValue;
+
+ private final String attributeDescription;
+
+
+
+ public LessOrEqualImpl(String attributeDescription,
+ ByteSequence assertionValue)
+ {
+ this.attributeDescription = attributeDescription;
+ this.assertionValue = assertionValue;
+ }
+
+
+
+ @Override
+ public <R, P> R accept(FilterVisitor<R, P> v, P p)
+ {
+ return v.visitLessOrEqualFilter(p, attributeDescription,
+ assertionValue);
+ }
+
+ }
+
+
+
+ private static final class NotImpl extends Impl
+ {
+ private final Filter subFilter;
+
+
+
+ public NotImpl(Filter subFilter)
+ {
+ this.subFilter = subFilter;
+ }
+
+
+
+ @Override
+ public <R, P> R accept(FilterVisitor<R, P> v, P p)
+ {
+ return v.visitNotFilter(p, subFilter);
+ }
+
+ }
+
+
+
+ private static final class OrImpl extends Impl
+ {
+ private final List<Filter> subFilters;
+
+
+
+ public OrImpl(List<Filter> subFilters)
+ {
+ this.subFilters = subFilters;
+ }
+
+
+
+ @Override
+ public <R, P> R accept(FilterVisitor<R, P> v, P p)
+ {
+ return v.visitOrFilter(p, subFilters);
+ }
+
+ }
+
+
+
+ private static final class PresentImpl extends Impl
+ {
+
+ private final String attributeDescription;
+
+
+
+ public PresentImpl(String attributeDescription)
+ {
+ this.attributeDescription = attributeDescription;
+ }
+
+
+
+ @Override
+ public <R, P> R accept(FilterVisitor<R, P> v, P p)
+ {
+ return v.visitPresentFilter(p, attributeDescription);
+ }
+
+ }
+
+
+
+ private static final class SubstringsImpl extends Impl
+ {
+
+ private final List<ByteSequence> anyStrings;
+
+ private final String attributeDescription;
+
+ private final ByteSequence finalString;
+
+ private final ByteSequence initialString;
+
+
+
+ public SubstringsImpl(String attributeDescription,
+ ByteSequence initialString, List<ByteSequence> anyStrings,
+ ByteSequence finalString)
+ {
+ this.attributeDescription = attributeDescription;
+ this.initialString = initialString;
+ this.anyStrings = anyStrings;
+ this.finalString = finalString;
+
+ }
+
+
+
+ @Override
+ public <R, P> R accept(FilterVisitor<R, P> v, P p)
+ {
+ return v.visitSubstringsFilter(p, attributeDescription,
+ initialString, anyStrings, finalString);
+ }
+
+ }
+
+
+
+ private static final class UnrecognizedImpl extends Impl
+ {
+
+ private final ByteSequence filterBytes;
+
+ private final byte filterTag;
+
+
+
+ public UnrecognizedImpl(byte filterTag, ByteSequence filterBytes)
+ {
+ this.filterTag = filterTag;
+ this.filterBytes = filterBytes;
+ }
+
+
+
+ @Override
+ public <R, P> R accept(FilterVisitor<R, P> v, P p)
+ {
+ return v.visitUnrecognizedFilter(p, filterTag, filterBytes);
+ }
+
+ }
+
+
+
+ // RFC 4526 - FALSE filter.
+ private static final Filter FALSE = new Filter(new OrImpl(Collections
+ .<Filter> emptyList()));
+
+ // Heavily used (objectClass=*) filter.
+ private static final Filter OBJECT_CLASS_PRESENT = new Filter(
+ new PresentImpl("objectClass"));
+
+ private static final FilterVisitor<StringBuilder, StringBuilder> TO_STRING_VISITOR = new FilterVisitor<StringBuilder, StringBuilder>()
+ {
+
+ public StringBuilder visitAndFilter(StringBuilder builder,
+ List<Filter> subFilters)
+ {
+ builder.append("(&");
+ for (Filter subFilter : subFilters)
+ {
+ subFilter.accept(this, builder);
+ }
+ builder.append(')');
+ return builder;
+ }
+
+
+
+ public StringBuilder visitApproxMatchFilter(StringBuilder builder,
+ String attributeDescription, ByteSequence assertionValue)
+ {
+ builder.append('(');
+ builder.append(attributeDescription);
+ builder.append("~=");
+ valueToFilterString(builder, assertionValue);
+ builder.append(')');
+ return builder;
+ }
+
+
+
+ public StringBuilder visitEqualityMatchFilter(
+ StringBuilder builder, String attributeDescription,
+ ByteSequence assertionValue)
+ {
+ builder.append('(');
+ builder.append(attributeDescription);
+ builder.append("=");
+ valueToFilterString(builder, assertionValue);
+ builder.append(')');
+ return builder;
+ }
+
+
+
+ public StringBuilder visitExtensibleMatchFilter(
+ StringBuilder builder, String matchingRule,
+ String attributeDescription, ByteSequence assertionValue,
+ boolean dnAttributes)
+ {
+ builder.append('(');
+
+ if (attributeDescription != null)
+ {
+ builder.append(attributeDescription);
+ }
+
+ if (dnAttributes)
+ {
+ builder.append(":dn");
+ }
+
+ if (matchingRule != null)
+ {
+ builder.append(':');
+ builder.append(matchingRule);
+ }
+
+ builder.append(":=");
+ valueToFilterString(builder, assertionValue);
+ builder.append(')');
+ return builder;
+ }
+
+
+
+ public StringBuilder visitGreaterOrEqualFilter(
+ StringBuilder builder, String attributeDescription,
+ ByteSequence assertionValue)
+ {
+ builder.append('(');
+ builder.append(attributeDescription);
+ builder.append(">=");
+ valueToFilterString(builder, assertionValue);
+ builder.append(')');
+ return builder;
+ }
+
+
+
+ public StringBuilder visitLessOrEqualFilter(StringBuilder builder,
+ String attributeDescription, ByteSequence assertionValue)
+ {
+ builder.append('(');
+ builder.append(attributeDescription);
+ builder.append("<=");
+ valueToFilterString(builder, assertionValue);
+ builder.append(')');
+ return builder;
+ }
+
+
+
+ public StringBuilder visitNotFilter(StringBuilder builder,
+ Filter subFilter)
+ {
+ builder.append("(|");
+ subFilter.accept(this, builder);
+ builder.append(')');
+ return builder;
+ }
+
+
+
+ public StringBuilder visitOrFilter(StringBuilder builder,
+ List<Filter> subFilters)
+ {
+ builder.append("(|");
+ for (Filter subFilter : subFilters)
+ {
+ subFilter.accept(this, builder);
+ }
+ builder.append(')');
+ return builder;
+ }
+
+
+
+ public StringBuilder visitPresentFilter(StringBuilder builder,
+ String attributeDescription)
+ {
+ builder.append('(');
+ builder.append(attributeDescription);
+ builder.append("=*)");
+ return builder;
+ }
+
+
+
+ public StringBuilder visitSubstringsFilter(StringBuilder builder,
+ String attributeDescription, ByteSequence initialSubstring,
+ List<ByteSequence> anySubstrings, ByteSequence finalSubstring)
+ {
+ builder.append('(');
+ builder.append(attributeDescription);
+ builder.append("=");
+ if (initialSubstring != null)
+ {
+ valueToFilterString(builder, initialSubstring);
+ }
+ for (ByteSequence anySubstring : anySubstrings)
+ {
+ builder.append('*');
+ valueToFilterString(builder, anySubstring);
+ }
+ builder.append('*');
+ if (finalSubstring != null)
+ {
+ valueToFilterString(builder, finalSubstring);
+ }
+ builder.append(')');
+ return builder;
+ }
+
+
+
+ public StringBuilder visitUnrecognizedFilter(StringBuilder builder,
+ byte filterTag, ByteSequence filterBytes)
+ {
+ // Fake up a representation.
+ builder.append('(');
+ builder.append(byteToHex(filterTag));
+ builder.append(':');
+ StaticUtils.toHex(filterBytes, builder);
+ builder.append(')');
+ return builder;
+ }
+ };
+
+ // RFC 4526 - TRUE filter.
+ private static final Filter TRUE = new Filter(new AndImpl(Collections
+ .<Filter> emptyList()));
+
+
+
+ /**
+ * Returns the {@code absolute false} filter as defined in RFC 4526
+ * which is comprised of an {@code or} filter containing zero
+ * components.
+ *
+ * @return The absolute false filter.
+ * @see <a href="http://tools.ietf.org/html/rfc4526">RFC 4526</a>
+ */
+ public static Filter getAbsoluteFalseFilter()
+ {
+ return FALSE;
+ }
+
+
+
+ /**
+ * Returns the {@code absolute true} filter as defined in RFC 4526
+ * which is comprised of an {@code and} filter containing zero
+ * components.
+ *
+ * @return The absolute true filter.
+ * @see <a href="http://tools.ietf.org/html/rfc4526">RFC 4526</a>
+ */
+ public static Filter getAbsoluteTrueFilter()
+ {
+ return TRUE;
+ }
+
+
+
+ /**
+ * Returns the {@code objectClass} presence filter {@code
+ * (objectClass=*)}.
+ * <p>
+ * A call to this method is equivalent to but more efficient than the
+ * following code:
+ *
+ * <pre>
+ * Filter.present("objectClass");
+ * </pre>
+ *
+ * @return The {@code objectClass} presence filter {@code
+ * (objectClass=*)}.
+ */
+ public static Filter getObjectClassPresentFilter()
+ {
+ return OBJECT_CLASS_PRESENT;
+ }
+
+
+
+ /**
+ * Creates a new {@code and} filter using the provided list of
+ * sub-filters.
+ * <p>
+ * Creating a new {@code and} filter with a {@code null} or empty list
+ * of sub-filters is equivalent to calling
+ * {@link #getAbsoluteTrueFilter()}.
+ *
+ * @param subFilters
+ * The list of sub-filters, may be empty or {@code null}.
+ * @return The newly created {@code and} filter.
+ */
+ public static Filter newAndFilter(Collection<Filter> subFilters)
+ {
+ if (subFilters == null || subFilters.isEmpty())
+ {
+ // RFC 4526 - TRUE filter.
+ return getAbsoluteTrueFilter();
+ }
+ else if (subFilters.size() == 1)
+ {
+ Filter subFilter = subFilters.iterator().next();
+ Validator.ensureNotNull(subFilter);
+ return new Filter(new AndImpl(Collections
+ .singletonList(subFilter)));
+ }
+ else
+ {
+ List<Filter> subFiltersList = new ArrayList<Filter>(subFilters
+ .size());
+ for (Filter subFilter : subFilters)
+ {
+ Validator.ensureNotNull(subFilter);
+ subFiltersList.add(subFilter);
+ }
+ return new Filter(new AndImpl(Collections
+ .unmodifiableList(subFiltersList)));
+ }
+ }
+
+
+
+ /**
+ * Creates a new {@code and} filter using the provided list of
+ * sub-filters.
+ * <p>
+ * Creating a new {@code and} filter with a {@code null} or empty list
+ * of sub-filters is equivalent to calling
+ * {@link #getAbsoluteTrueFilter()}.
+ *
+ * @param subFilters
+ * The list of sub-filters, may be empty or {@code null}.
+ * @return The newly created {@code and} filter.
+ */
+ public static Filter newAndFilter(Filter... subFilters)
+ {
+ if ((subFilters == null) || (subFilters.length == 0))
+ {
+ // RFC 4526 - TRUE filter.
+ return getAbsoluteTrueFilter();
+ }
+ else if (subFilters.length == 1)
+ {
+ Validator.ensureNotNull(subFilters[0]);
+ return new Filter(new AndImpl(Collections
+ .singletonList(subFilters[0])));
+ }
+ else
+ {
+ List<Filter> subFiltersList = new ArrayList<Filter>(
+ subFilters.length);
+ for (Filter subFilter : subFilters)
+ {
+ Validator.ensureNotNull(subFilter);
+ subFiltersList.add(subFilter);
+ }
+ return new Filter(new AndImpl(Collections
+ .unmodifiableList(subFiltersList)));
+ }
+ }
+
+
+
+ /**
+ * Creates a new {@code approximate match} filter using the provided
+ * attribute description and assertion value.
+ *
+ * @param attributeDescription
+ * The attribute description.
+ * @param assertionValue
+ * The assertion value.
+ * @return The newly created {@code approximate match} filter.
+ */
+ public static Filter newApproxMatchFilter(
+ String attributeDescription, ByteSequence assertionValue)
+ {
+ Validator.ensureNotNull(attributeDescription);
+ Validator.ensureNotNull(assertionValue);
+ return new Filter(new ApproxMatchImpl(attributeDescription,
+ assertionValue));
+ }
+
+
+
+ /**
+ * Creates a new {@code equality match} filter using the provided
+ * attribute description and assertion value.
+ *
+ * @param attributeDescription
+ * The attribute description.
+ * @param assertionValue
+ * The assertion value.
+ * @return The newly created {@code equality match} filter.
+ */
+ public static Filter newEqualityMatchFilter(
+ String attributeDescription, ByteSequence assertionValue)
+ {
+ Validator.ensureNotNull(attributeDescription);
+ Validator.ensureNotNull(assertionValue);
+ return new Filter(new EqualityMatchImpl(attributeDescription,
+ assertionValue));
+ }
+
+
+
+ /**
+ * Creates a new {@code extensible match} filter.
+ *
+ * @param matchingRule
+ * The matching rule name, may be {@code null} if {@code
+ * attributeDescription} is specified.
+ * @param attributeDescription
+ * The attribute description, may be {@code null} if {@code
+ * matchingRule} is specified.
+ * @param assertionValue
+ * The assertion value.
+ * @param dnAttributes
+ * Indicates whether DN matching should be performed.
+ * @return The newly created {@code extensible match} filter.
+ */
+ public static Filter newExtensibleMatchFilter(String matchingRule,
+ String attributeDescription, ByteSequence assertionValue,
+ boolean dnAttributes)
+ {
+ Validator.ensureTrue((matchingRule != null)
+ || (attributeDescription != null), "matchingRule and/or "
+ + "attributeDescription must not be null");
+ Validator.ensureNotNull(assertionValue);
+ return new Filter(new ExtensibleMatchImpl(matchingRule,
+ attributeDescription, assertionValue, dnAttributes));
+ }
+
+
+
+ /**
+ * Creates a new {@code greater or equal} filter using the provided
+ * attribute description and assertion value.
+ *
+ * @param attributeDescription
+ * The attribute description.
+ * @param assertionValue
+ * The assertion value.
+ * @return The newly created {@code greater or equal} filter.
+ */
+ public static Filter newGreaterOrEqualFilter(
+ String attributeDescription, ByteSequence assertionValue)
+ {
+ Validator.ensureNotNull(attributeDescription);
+ Validator.ensureNotNull(assertionValue);
+ return new Filter(new GreaterOrEqualImpl(attributeDescription,
+ assertionValue));
+ }
+
+
+
+ /**
+ * Creates a new {@code less or equal} filter using the provided
+ * attribute description and assertion value.
+ *
+ * @param attributeDescription
+ * The attribute description.
+ * @param assertionValue
+ * The assertion value.
+ * @return The newly created {@code less or equal} filter.
+ */
+ public static Filter newLessOrEqualFilter(
+ String attributeDescription, ByteSequence assertionValue)
+ {
+ Validator.ensureNotNull(attributeDescription);
+ Validator.ensureNotNull(assertionValue);
+ return new Filter(new LessOrEqualImpl(attributeDescription,
+ assertionValue));
+ }
+
+
+
+ /**
+ * Creates a new {@code not} filter using the provided sub-filter.
+ *
+ * @param subFilter
+ * The sub-filter.
+ * @return The newly created {@code not} filter.
+ */
+ public static Filter newNotFilter(Filter subFilter)
+ {
+ Validator.ensureNotNull(subFilter);
+ return new Filter(new NotImpl(subFilter));
+ }
+
+
+
+ /**
+ * Creates a new {@code or} filter using the provided list of
+ * sub-filters.
+ * <p>
+ * Creating a new {@code or} filter with a {@code null} or empty list
+ * of sub-filters is equivalent to calling
+ * {@link #getAbsoluteFalseFilter()}.
+ *
+ * @param subFilters
+ * The list of sub-filters, may be empty or {@code null}.
+ * @return The newly created {@code or} filter.
+ */
+ public static Filter newOrFilter(Collection<Filter> subFilters)
+ {
+ if (subFilters == null || subFilters.isEmpty())
+ {
+ // RFC 4526 - FALSE filter.
+ return getAbsoluteFalseFilter();
+ }
+ else if (subFilters.size() == 1)
+ {
+ Filter subFilter = subFilters.iterator().next();
+ Validator.ensureNotNull(subFilter);
+ return new Filter(
+ new OrImpl(Collections.singletonList(subFilter)));
+ }
+ else
+ {
+ List<Filter> subFiltersList = new ArrayList<Filter>(subFilters
+ .size());
+ for (Filter subFilter : subFilters)
+ {
+ Validator.ensureNotNull(subFilter);
+ subFiltersList.add(subFilter);
+ }
+ return new Filter(new OrImpl(Collections
+ .unmodifiableList(subFiltersList)));
+ }
+ }
+
+
+
+ /**
+ * Creates a new {@code or} filter using the provided list of
+ * sub-filters.
+ * <p>
+ * Creating a new {@code or} filter with a {@code null} or empty list
+ * of sub-filters is equivalent to calling
+ * {@link #getAbsoluteFalseFilter()}.
+ *
+ * @param subFilters
+ * The list of sub-filters, may be empty or {@code null}.
+ * @return The newly created {@code or} filter.
+ */
+ public static Filter newOrFilter(Filter... subFilters)
+ {
+ if ((subFilters == null) || (subFilters.length == 0))
+ {
+ // RFC 4526 - FALSE filter.
+ return getAbsoluteFalseFilter();
+ }
+ else if (subFilters.length == 1)
+ {
+ Validator.ensureNotNull(subFilters[0]);
+ return new Filter(new OrImpl(Collections
+ .singletonList(subFilters[0])));
+ }
+ else
+ {
+ List<Filter> subFiltersList = new ArrayList<Filter>(
+ subFilters.length);
+ for (Filter subFilter : subFilters)
+ {
+ Validator.ensureNotNull(subFilter);
+ subFiltersList.add(subFilter);
+ }
+ return new Filter(new OrImpl(Collections
+ .unmodifiableList(subFiltersList)));
+ }
+ }
+
+
+
+ /**
+ * Creates a new {@code present} filter using the provided attribute
+ * description.
+ *
+ * @param attributeDescription
+ * The attribute description.
+ * @return The newly created {@code present} filter.
+ */
+ public static Filter newPresentFilter(String attributeDescription)
+ {
+ Validator.ensureNotNull(attributeDescription);
+ if (toLowerCase(attributeDescription).equals("objectclass"))
+ {
+ return OBJECT_CLASS_PRESENT;
+ }
+ return new Filter(new PresentImpl(attributeDescription));
+ }
+
+
+
+ /**
+ * Creates a new {@code substrings} filter using the provided
+ * attribute description, {@code initial}, {@code final}, and {@code
+ * any} sub-strings.
+ *
+ * @param attributeDescription
+ * The attribute description.
+ * @param initialSubstring
+ * The initial sub-string, may be {@code null} if either
+ * {@code finalSubstring} or {@code anySubstrings} are
+ * specified.
+ * @param anySubstrings
+ * The final sub-string, may be {@code null} or empty if
+ * either {@code finalSubstring} or {@code initialSubstring}
+ * are specified.
+ * @param finalSubstring
+ * The final sub-string, may be {@code null}, may be {@code
+ * null} if either {@code initialSubstring} or {@code
+ * anySubstrings} are specified.
+ * @return The newly created {@code substrings} filter.
+ */
+ public static Filter newSubstringsFilter(String attributeDescription,
+ ByteSequence initialSubstring, ByteSequence[] anySubstrings,
+ ByteSequence finalSubstring)
+ {
+ Validator.ensureNotNull(attributeDescription);
+ Validator.ensureTrue((initialSubstring != null)
+ || (finalSubstring != null)
+ || ((anySubstrings != null) && (anySubstrings.length > 0)),
+ "at least one substring (initial, any or final)"
+ + " must be specified");
+
+ List<ByteSequence> anySubstringList;
+ if ((anySubstrings == null) || (anySubstrings.length == 0))
+ {
+ anySubstringList = Collections.emptyList();
+ }
+ else if (anySubstrings.length == 1)
+ {
+ Validator.ensureNotNull(anySubstrings[0]);
+ anySubstringList = Collections.singletonList(anySubstrings[0]);
+ }
+ else
+ {
+ anySubstringList = new ArrayList<ByteSequence>(
+ anySubstrings.length);
+ for (ByteSequence anySubstring : anySubstrings)
+ {
+ Validator.ensureNotNull(anySubstring);
+
+ anySubstringList.add(anySubstring);
+ }
+ anySubstringList = Collections.unmodifiableList(anySubstringList);
+ }
+
+ return new Filter(new SubstringsImpl(attributeDescription,
+ initialSubstring, anySubstringList, finalSubstring));
+ }
+
+
+
+ /**
+ * Creates a new {@code substrings} filter using the provided
+ * attribute description, {@code initial}, {@code final}, and {@code
+ * any} sub-strings.
+ *
+ * @param attributeDescription
+ * The attribute description.
+ * @param initialSubstring
+ * The initial sub-string, may be {@code null} if either
+ * {@code finalSubstring} or {@code anySubstrings} are
+ * specified.
+ * @param anySubstrings
+ * The final sub-string, may be {@code null} or empty if
+ * either {@code finalSubstring} or {@code initialSubstring}
+ * are specified.
+ * @param finalSubstring
+ * The final sub-string, may be {@code null}, may be {@code
+ * null} if either {@code initialSubstring} or {@code
+ * anySubstrings} are specified.
+ * @return The newly created {@code substrings} filter.
+ */
+ public static Filter newSubstringsFilter(String attributeDescription,
+ ByteSequence initialSubstring,
+ Collection<ByteSequence> anySubstrings,
+ ByteSequence finalSubstring)
+ {
+ Validator.ensureNotNull(attributeDescription);
+ Validator.ensureTrue((initialSubstring != null)
+ || (finalSubstring != null)
+ || ((anySubstrings != null) && (anySubstrings.size() > 0)),
+ "at least one substring (initial, any or final)"
+ + " must be specified");
+
+ List<ByteSequence> anySubstringList;
+ if ((anySubstrings == null) || (anySubstrings.size() == 0))
+ {
+ anySubstringList = Collections.emptyList();
+ }
+ else if (anySubstrings.size() == 1)
+ {
+ ByteSequence anySubstring = anySubstrings.iterator().next();
+ Validator.ensureNotNull(anySubstring);
+ anySubstringList = Collections.singletonList(anySubstring);
+ }
+ else
+ {
+ anySubstringList = new ArrayList<ByteSequence>(anySubstrings
+ .size());
+ for (ByteSequence anySubstring : anySubstrings)
+ {
+ Validator.ensureNotNull(anySubstring);
+
+ anySubstringList.add(anySubstring);
+ }
+ anySubstringList = Collections.unmodifiableList(anySubstringList);
+ }
+
+ return new Filter(new SubstringsImpl(attributeDescription,
+ initialSubstring, anySubstringList, finalSubstring));
+ }
+
+
+
+ /**
+ * Creates a new {@code unrecognized} filter using the provided ASN1
+ * filter tag and content. This type of filter should be used for
+ * filters which are not part of the standard filter definition.
+ *
+ * @param filterTag
+ * The ASN.1 tag.
+ * @param filterBytes
+ * The filter content.
+ * @return The newly created {@code unrecognized} filter.
+ */
+ public static Filter newUnrecognizedFilter(byte filterTag,
+ ByteSequence filterBytes)
+ {
+ Validator.ensureNotNull(filterBytes);
+ return new Filter(new UnrecognizedImpl(filterTag, filterBytes));
+ }
+
+
+
+ /**
+ * Parses the provided LDAP string representation of a filter as a
+ * {@code Filter}.
+ *
+ * @param string
+ * The LDAP string representation of a filter.
+ * @return The parsed {@code Filter}.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code string} is not a valid LDAP string
+ * representation of a filter.
+ */
+ public static Filter valueOf(String string)
+ throws LocalizedIllegalArgumentException
+ {
+ Validator.ensureNotNull(string);
+
+ // If the filter is enclosed in a pair of single quotes it
+ // is invalid (issue #1024).
+ if ((string.length() > 1) && string.startsWith("'")
+ && string.endsWith("'"))
+ {
+ Message message = ERR_LDAP_FILTER_ENCLOSED_IN_APOSTROPHES
+ .get(string);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ if (string.startsWith("("))
+ {
+ if (string.endsWith(")"))
+ {
+ return valueOf0(string, 1, string.length() - 1);
+ }
+ else
+ {
+ Message message = ERR_LDAP_FILTER_MISMATCHED_PARENTHESES.get(
+ string, 1, string.length());
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ }
+ else
+ {
+ // We tolerate the top level filter component not being surrounded
+ // by parentheses.
+ return valueOf0(string, 0, string.length());
+ }
+ }
+
+
+
+ private static Filter valueOf0(String string,
+ int beginIndex /* inclusive */, int endIndex /* exclusive */)
+ throws LocalizedIllegalArgumentException
+ {
+ if (beginIndex >= endIndex)
+ {
+ Message message = ERR_LDAP_FILTER_STRING_NULL.get();
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ int index = beginIndex;
+ char c = string.charAt(index);
+
+ if (c == '&')
+ {
+ List<Filter> subFilters = valueOfFilterList(string, index + 1,
+ endIndex);
+ if (subFilters.isEmpty())
+ {
+ return getAbsoluteTrueFilter();
+ }
+ else
+ {
+ return new Filter(new AndImpl(subFilters));
+ }
+ }
+ else if (c == '|')
+ {
+ List<Filter> subFilters = valueOfFilterList(string, index + 1,
+ endIndex);
+ if (subFilters.isEmpty())
+ {
+ return getAbsoluteFalseFilter();
+ }
+ else
+ {
+ return new Filter(new OrImpl(subFilters));
+ }
+ }
+ else if (c == '!')
+ {
+ if ((string.charAt(index + 1) != '(')
+ || (string.charAt(endIndex - 1) != ')'))
+ {
+ Message message = ERR_LDAP_FILTER_COMPOUND_MISSING_PARENTHESES
+ .get(string, index, endIndex - 1);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ Filter subFilter = valueOf0(string, index + 2, endIndex - 1);
+ return new Filter(new NotImpl(subFilter));
+ }
+ else
+ {
+ // It must be a simple filter. It must have an equal sign at some
+ // point, so find it.
+ int equalPos = -1;
+ for (int i = index; i < endIndex; i++)
+ {
+ if (string.charAt(i) == '=')
+ {
+ equalPos = i;
+ break;
+ }
+ }
+
+ // Look at the character immediately before the equal sign,
+ // because it may help determine the filter type.
+ String attributeDescription;
+ ByteSequence assertionValue;
+
+ switch (string.charAt(equalPos - 1))
+ {
+ case '~':
+ attributeDescription = valueOfAttributeDescription(string,
+ index, equalPos - 1);
+ assertionValue = valueOfAssertionValue(string, equalPos + 1,
+ endIndex);
+ return new Filter(new ApproxMatchImpl(attributeDescription,
+ assertionValue));
+ case '>':
+ attributeDescription = valueOfAttributeDescription(string,
+ index, equalPos - 1);
+ assertionValue = valueOfAssertionValue(string, equalPos + 1,
+ endIndex);
+ return new Filter(new GreaterOrEqualImpl(attributeDescription,
+ assertionValue));
+ case '<':
+ attributeDescription = valueOfAttributeDescription(string,
+ index, equalPos - 1);
+ assertionValue = valueOfAssertionValue(string, equalPos + 1,
+ endIndex);
+ return new Filter(new LessOrEqualImpl(attributeDescription,
+ assertionValue));
+ case ':':
+ return valueOfExtensibleFilter(string, index, equalPos,
+ endIndex);
+ default:
+ attributeDescription = valueOfAttributeDescription(string,
+ index, equalPos);
+ return valueOfGenericFilter(string, attributeDescription,
+ equalPos + 1, endIndex);
+ }
+ }
+ }
+
+
+
+ private static ByteSequence valueOfAssertionValue(String string,
+ int startIndex, int endIndex)
+ throws LocalizedIllegalArgumentException
+ {
+ boolean hasEscape = false;
+ byte[] valueBytes = getBytes(string.substring(startIndex, endIndex));
+ for (byte valueByte : valueBytes)
+ {
+ if (valueByte == 0x5C) // The backslash character
+ {
+ hasEscape = true;
+ break;
+ }
+ }
+
+ if (hasEscape)
+ {
+ ByteStringBuilder valueBuffer = new ByteStringBuilder(
+ valueBytes.length);
+ for (int i = 0; i < valueBytes.length; i++)
+ {
+ if (valueBytes[i] == 0x5C) // The backslash character
+ {
+ // The next two bytes must be the hex characters that comprise
+ // the binary value.
+ if ((i + 2) >= valueBytes.length)
+ {
+ Message message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
+ string, startIndex + i + 1);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ byte byteValue = 0;
+ switch (valueBytes[++i])
+ {
+ case 0x30: // '0'
+ break;
+ case 0x31: // '1'
+ byteValue = (byte) 0x10;
+ break;
+ case 0x32: // '2'
+ byteValue = (byte) 0x20;
+ break;
+ case 0x33: // '3'
+ byteValue = (byte) 0x30;
+ break;
+ case 0x34: // '4'
+ byteValue = (byte) 0x40;
+ break;
+ case 0x35: // '5'
+ byteValue = (byte) 0x50;
+ break;
+ case 0x36: // '6'
+ byteValue = (byte) 0x60;
+ break;
+ case 0x37: // '7'
+ byteValue = (byte) 0x70;
+ break;
+ case 0x38: // '8'
+ byteValue = (byte) 0x80;
+ break;
+ case 0x39: // '9'
+ byteValue = (byte) 0x90;
+ break;
+ case 0x41: // 'A'
+ case 0x61: // 'a'
+ byteValue = (byte) 0xA0;
+ break;
+ case 0x42: // 'B'
+ case 0x62: // 'b'
+ byteValue = (byte) 0xB0;
+ break;
+ case 0x43: // 'C'
+ case 0x63: // 'c'
+ byteValue = (byte) 0xC0;
+ break;
+ case 0x44: // 'D'
+ case 0x64: // 'd'
+ byteValue = (byte) 0xD0;
+ break;
+ case 0x45: // 'E'
+ case 0x65: // 'e'
+ byteValue = (byte) 0xE0;
+ break;
+ case 0x46: // 'F'
+ case 0x66: // 'f'
+ byteValue = (byte) 0xF0;
+ break;
+ default:
+ Message message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
+ string, startIndex + i + 1);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ switch (valueBytes[++i])
+ {
+ case 0x30: // '0'
+ break;
+ case 0x31: // '1'
+ byteValue |= (byte) 0x01;
+ break;
+ case 0x32: // '2'
+ byteValue |= (byte) 0x02;
+ break;
+ case 0x33: // '3'
+ byteValue |= (byte) 0x03;
+ break;
+ case 0x34: // '4'
+ byteValue |= (byte) 0x04;
+ break;
+ case 0x35: // '5'
+ byteValue |= (byte) 0x05;
+ break;
+ case 0x36: // '6'
+ byteValue |= (byte) 0x06;
+ break;
+ case 0x37: // '7'
+ byteValue |= (byte) 0x07;
+ break;
+ case 0x38: // '8'
+ byteValue |= (byte) 0x08;
+ break;
+ case 0x39: // '9'
+ byteValue |= (byte) 0x09;
+ break;
+ case 0x41: // 'A'
+ case 0x61: // 'a'
+ byteValue |= (byte) 0x0A;
+ break;
+ case 0x42: // 'B'
+ case 0x62: // 'b'
+ byteValue |= (byte) 0x0B;
+ break;
+ case 0x43: // 'C'
+ case 0x63: // 'c'
+ byteValue |= (byte) 0x0C;
+ break;
+ case 0x44: // 'D'
+ case 0x64: // 'd'
+ byteValue |= (byte) 0x0D;
+ break;
+ case 0x45: // 'E'
+ case 0x65: // 'e'
+ byteValue |= (byte) 0x0E;
+ break;
+ case 0x46: // 'F'
+ case 0x66: // 'f'
+ byteValue |= (byte) 0x0F;
+ break;
+ default:
+ Message message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
+ string, startIndex + i + 1);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ valueBuffer.append(byteValue);
+ }
+ else
+ {
+ valueBuffer.append(valueBytes[i]);
+ }
+ }
+
+ return valueBuffer.toByteString();
+ }
+ else
+ {
+ return ByteString.wrap(valueBytes);
+ }
+ }
+
+
+
+ private static String valueOfAttributeDescription(String string,
+ int startIndex, int endIndex)
+ throws LocalizedIllegalArgumentException
+ {
+ // The part of the filter string before the equal sign should be the
+ // attribute type. Make sure that the characters it contains are
+ // acceptable for attribute types, including those allowed by
+ // attribute name exceptions (ASCII letters and digits, the dash,
+ // and the underscore). We also need to allow attribute options,
+ // which includes the semicolon and the equal sign.
+ String attrType = string.substring(startIndex, endIndex);
+ for (int i = 0; i < attrType.length(); i++)
+ {
+ switch (attrType.charAt(i))
+ {
+ case '-':
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case ';':
+ case '=':
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ case 'G':
+ case 'H':
+ case 'I':
+ case 'J':
+ case 'K':
+ case 'L':
+ case 'M':
+ case 'N':
+ case 'O':
+ case 'P':
+ case 'Q':
+ case 'R':
+ case 'S':
+ case 'T':
+ case 'U':
+ case 'V':
+ case 'W':
+ case 'X':
+ case 'Y':
+ case 'Z':
+ case '_':
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ case 'g':
+ case 'h':
+ case 'i':
+ case 'j':
+ case 'k':
+ case 'l':
+ case 'm':
+ case 'n':
+ case 'o':
+ case 'p':
+ case 'q':
+ case 'r':
+ case 's':
+ case 't':
+ case 'u':
+ case 'v':
+ case 'w':
+ case 'x':
+ case 'y':
+ case 'z':
+ // These are all OK.
+ break;
+
+ case '.':
+ case '/':
+ case ':':
+ case '<':
+ case '>':
+ case '?':
+ case '@':
+ case '[':
+ case '\\':
+ case ']':
+ case '^':
+ case '`':
+ // These are not allowed, but they are explicitly called out
+ // because they are included in the range of values between '-'
+ // and 'z', and making sure all possible characters are included
+ // can help make the switch statement more efficient. We'll fall
+ // through to the default clause to reject them.
+ default:
+ Message message = ERR_LDAP_FILTER_INVALID_CHAR_IN_ATTR_TYPE
+ .get(attrType, String.valueOf(attrType.charAt(i)), i);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ }
+
+ return attrType;
+ }
+
+
+
+ private static Filter valueOfExtensibleFilter(String string,
+ int startIndex, int equalIndex, int endIndex)
+ throws LocalizedIllegalArgumentException
+ {
+ String attributeDescription = null;
+ boolean dnAttributes = false;
+ String matchingRule = null;
+
+ // Look at the first character. If it is a colon, then it must be
+ // followed by either the string "dn" or the matching rule ID. If it
+ // is not, then must be the attribute type.
+ String lowerLeftStr = toLowerCase(string.substring(startIndex,
+ equalIndex));
+ if (string.charAt(startIndex) == ':')
+ {
+ // See if it starts with ":dn". Otherwise, it much be the matching
+ // rule ID.
+ if (lowerLeftStr.startsWith(":dn:"))
+ {
+ dnAttributes = true;
+
+ if ((startIndex + 4) < (equalIndex - 1))
+ {
+ matchingRule = string.substring(startIndex + 4,
+ equalIndex - 1);
+ }
+ }
+ else
+ {
+ matchingRule = string.substring(startIndex + 1, equalIndex - 1);
+ }
+ }
+ else
+ {
+ int colonPos = string.indexOf(':', startIndex);
+ if (colonPos < 0)
+ {
+ Message message = ERR_LDAP_FILTER_EXTENSIBLE_MATCH_NO_COLON
+ .get(string, startIndex);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ attributeDescription = string.substring(startIndex, colonPos);
+
+ // If there is anything left, then it should be ":dn" and/or ":"
+ // followed by the matching rule ID.
+ if (colonPos < (equalIndex - 1))
+ {
+ if (lowerLeftStr.startsWith(":dn:", colonPos - startIndex))
+ {
+ dnAttributes = true;
+
+ if ((colonPos + 4) < (equalIndex - 1))
+ {
+ matchingRule = string.substring(colonPos + 4,
+ equalIndex - 1);
+ }
+ }
+ else
+ {
+ matchingRule = string.substring(colonPos + 1, equalIndex - 1);
+ }
+ }
+ }
+
+ // Parse out the attribute value.
+ ByteSequence matchValue = valueOfAssertionValue(string,
+ equalIndex + 1, endIndex);
+
+ // Make sure that the filter has at least one of an attribute
+ // description and/or a matching rule ID.
+ if ((attributeDescription == null) && (matchingRule == null))
+ {
+ Message message = ERR_LDAP_FILTER_EXTENSIBLE_MATCH_NO_AD_OR_MR
+ .get(string, startIndex);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ return new Filter(new ExtensibleMatchImpl(matchingRule,
+ attributeDescription, matchValue, dnAttributes));
+ }
+
+
+
+ private static List<Filter> valueOfFilterList(String string,
+ int startIndex, int endIndex)
+ throws LocalizedIllegalArgumentException
+ {
+ // If the end index is equal to the start index, then there are no
+ // components.
+ if (startIndex >= endIndex)
+ {
+ return Collections.emptyList();
+ }
+
+ // At least one sub-filter.
+ Filter firstFilter = null;
+ List<Filter> subFilters = null;
+
+ // The first and last characters must be parentheses. If not, then
+ // that's an error.
+ if ((string.charAt(startIndex) != '(')
+ || (string.charAt(endIndex - 1) != ')'))
+ {
+ Message message = ERR_LDAP_FILTER_COMPOUND_MISSING_PARENTHESES
+ .get(string, startIndex, endIndex);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // Iterate through the characters in the value. Whenever an open
+ // parenthesis is found, locate the corresponding close parenthesis
+ // by counting the number of intermediate open/close parentheses.
+ int pendingOpens = 0;
+ int openIndex = -1;
+ for (int i = startIndex; i < endIndex; i++)
+ {
+ char c = string.charAt(i);
+ if (c == '(')
+ {
+ if (openIndex < 0)
+ {
+ openIndex = i;
+ }
+ pendingOpens++;
+ }
+ else if (c == ')')
+ {
+ pendingOpens--;
+ if (pendingOpens == 0)
+ {
+ Filter subFilter = valueOf0(string, openIndex + 1, i);
+ if (subFilters != null)
+ {
+ subFilters.add(subFilter);
+ }
+ else if (firstFilter != null)
+ {
+ subFilters = new LinkedList<Filter>();
+ subFilters.add(firstFilter);
+ subFilters.add(subFilter);
+ firstFilter = null;
+ }
+ else
+ {
+ firstFilter = subFilter;
+ }
+ openIndex = -1;
+ }
+ else if (pendingOpens < 0)
+ {
+ Message message = ERR_LDAP_FILTER_NO_CORRESPONDING_OPEN_PARENTHESIS
+ .get(string, i);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ }
+ else if (pendingOpens <= 0)
+ {
+ Message message = ERR_LDAP_FILTER_COMPOUND_MISSING_PARENTHESES
+ .get(string, startIndex, endIndex);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ }
+
+ // At this point, we have parsed the entire set of filter
+ // components. The list of open parenthesis positions must be empty.
+ if (pendingOpens != 0)
+ {
+ Message message = ERR_LDAP_FILTER_NO_CORRESPONDING_CLOSE_PARENTHESIS
+ .get(string, openIndex);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ if (subFilters != null)
+ {
+ return Collections.unmodifiableList(subFilters);
+ }
+ else
+ {
+ return Collections.singletonList(firstFilter);
+ }
+ }
+
+
+
+ private static Filter valueOfGenericFilter(String string,
+ String attributeDescription, int startIndex, int endIndex)
+ throws LocalizedIllegalArgumentException
+ {
+ if (startIndex >= endIndex)
+ {
+ // Equality filter with empty assertion value.
+ return new Filter(new EqualityMatchImpl(attributeDescription,
+ ByteString.empty()));
+ }
+ else if ((endIndex - startIndex == 1)
+ && (string.charAt(startIndex) == '*'))
+ {
+ // Single asterisk is a present filter.
+ return newPresentFilter(attributeDescription);
+ }
+ else
+ {
+ // Either an equality or substring filter.
+ ByteSequence assertionValue = valueOfAssertionValue(string,
+ startIndex, endIndex);
+
+ ByteSequence initialString = null;
+ ByteSequence finalString = null;
+ LinkedList<ByteSequence> anyStrings = null;
+
+ int lastAsteriskIndex = -1;
+ int length = assertionValue.length();
+ for (int i = 0; i < length; i++)
+ {
+ if (assertionValue.byteAt(i) == '*')
+ {
+ if (lastAsteriskIndex == -1)
+ {
+ if (i > 0)
+ {
+ // Got an initial substring.
+ initialString = assertionValue.subSequence(0, i);
+ }
+ lastAsteriskIndex = i;
+ }
+ else
+ {
+ // Got an any substring.
+ if (anyStrings == null)
+ {
+ anyStrings = new LinkedList<ByteSequence>();
+ }
+
+ int s = lastAsteriskIndex + 1;
+ if (s == i)
+ {
+ // A zero length substring.
+ Message message = ERR_LDAP_FILTER_BAD_SUBSTRING.get(
+ string, string.subSequence(startIndex, endIndex));
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ anyStrings.add(assertionValue.subSequence(s, i));
+ lastAsteriskIndex = i;
+ }
+ }
+ }
+
+ if (lastAsteriskIndex >= 0 && lastAsteriskIndex < length - 1)
+ {
+ // Got a final substring.
+ finalString = assertionValue.subSequence(lastAsteriskIndex + 1,
+ length);
+ }
+
+ if ((initialString == null) && (anyStrings == null)
+ && (finalString == null))
+ {
+ return new Filter(new EqualityMatchImpl(attributeDescription,
+ assertionValue));
+ }
+ else
+ {
+ List<ByteSequence> tmp;
+
+ if (anyStrings == null)
+ {
+ tmp = Collections.emptyList();
+ }
+ else if (anyStrings.size() == 1)
+ {
+ tmp = Collections.singletonList(anyStrings.getFirst());
+ }
+ else
+ {
+ tmp = Collections.unmodifiableList(anyStrings);
+ }
+
+ return new Filter(new SubstringsImpl(attributeDescription,
+ initialString, tmp, finalString));
+ }
+ }
+ }
+
+
+
+ /**
+ * Appends a properly-cleaned version of the provided value to the
+ * given builder so that it can be safely used in string
+ * representations of this search filter. The formatting changes that
+ * may be performed will be in compliance with the specification in
+ * RFC 2254.
+ *
+ * @param builder
+ * The builder to which the "safe" version of the value will
+ * be appended.
+ * @param value
+ * The value to be appended to the builder.
+ */
+ private static void valueToFilterString(StringBuilder builder,
+ ByteSequence value)
+ {
+ // Get the binary representation of the value and iterate through
+ // it to see if there are any unsafe characters. If there are,
+ // then escape them and replace them with a two-digit hex
+ // equivalent.
+ builder.ensureCapacity(builder.length() + value.length());
+ for (int i = 0; i < value.length(); i++)
+ {
+ // TODO: this is a bit overkill - it will escape all non-ascii
+ // chars!
+ byte b = value.byteAt(i);
+ if (((b & 0x7F) != b) || // Not 7-bit clean
+ (b <= 0x1F) || // Below the printable character range
+ (b == 0x28) || // Open parenthesis
+ (b == 0x29) || // Close parenthesis
+ (b == 0x2A) || // Asterisk
+ (b == 0x5C) || // Backslash
+ (b == 0x7F)) // Delete character
+ {
+ builder.append('\\');
+ builder.append(byteToHex(b));
+ }
+ else
+ {
+ builder.append((char) b);
+ }
+ }
+ }
+
+
+
+ private final Impl pimpl;
+
+
+
+ private Filter(Impl pimpl)
+ {
+ this.pimpl = pimpl;
+ }
+
+
+
+ /**
+ * Applies a {@code FilterVisitor} to this {@code Filter}.
+ *
+ * @param <R>
+ * The return type of the visitor's methods.
+ * @param <P>
+ * The type of the additional parameters to the visitor's
+ * methods.
+ * @param v
+ * The filter visitor.
+ * @param p
+ * Optional additional visitor parameter.
+ * @return A result as specified by the visitor.
+ */
+ public <R, P> R accept(FilterVisitor<R, P> v, P p)
+ {
+ return pimpl.accept(v, p);
+ }
+
+
+
+ /**
+ * Returns a {@code Matcher} which can be used to compare this {@code
+ * Filter} against entries using the provided {@code Schema}.
+ *
+ * @param schema
+ * The schema which the {@code Matcher} should use for
+ * comparisons.
+ * @return The {@code Matcher}.
+ */
+ public Matcher matcher(Schema schema)
+ {
+ return new Matcher(this, schema);
+ }
+
+
+
+ /**
+ * Returns a {@code Matcher} which can be used to compare this {@code
+ * Filter} against entries using the default schema.
+ *
+ * @return The {@code Matcher}.
+ */
+ public Matcher matcher()
+ {
+ return new Matcher(this, Schema.getDefaultSchema());
+ }
+
+
+
+ /**
+ * Indicates whether this {@code Filter} matches the provided {@code
+ * Entry} using the schema associated with the entry.
+ * <p>
+ * Calling this method is equivalent to the following:
+ *
+ * <pre>
+ * boolean b = matcher(entry.getSchema()).matches(entry);
+ * </pre>
+ *
+ * @param entry
+ * The entry to be matched.
+ * @return {@code true} if this {@code Filter} matches the provided
+ * {@code Entry}.
+ */
+ public ConditionResult matches(Entry entry)
+ {
+ return matcher(Schema.getDefaultSchema()).matches(entry);
+ }
+
+
+
+ /**
+ * Returns a {@code String} whose contents is the LDAP string
+ * representation of this {@code Filter}.
+ *
+ * @return The LDAP string representation of this {@code Filter}.
+ */
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ return toString(builder).toString();
+ }
+
+
+
+ /**
+ * Appends the LDAP string representation of this {@code Filter} to
+ * the provided {@code StringBuilder}.
+ *
+ * @param builder
+ * The {@code StringBuilder} to which the LDAP string
+ * representation of this {@code Filter} should be appended.
+ * @return The updated {@code StringBuilder}.
+ */
+ public StringBuilder toString(StringBuilder builder)
+ {
+ return pimpl.accept(TO_STRING_VISITOR, builder);
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/FilterVisitor.java b/sdk/src/org/opends/sdk/FilterVisitor.java
new file mode 100644
index 0000000..a7d180e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/FilterVisitor.java
@@ -0,0 +1,238 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.List;
+
+import org.opends.sdk.util.ByteSequence;
+
+
+/**
+ * A visitor of {@code Filter}s, in the style of the visitor design
+ * pattern.
+ * <p>
+ * Classes implementing this interface can query filters in a type-safe
+ * manner. When a visitor is passed to a filter's accept method, the
+ * corresponding visit method most applicable to that filter is invoked.
+ *
+ * @param <R>
+ * The return type of this visitor's methods. Use
+ * {@link java.lang.Void} for visitors that do not need to
+ * return results.
+ * @param <P>
+ * The type of the additional parameter to this visitor's
+ * methods. Use {@link java.lang.Void} for visitors that do not
+ * need an additional parameter.
+ */
+public interface FilterVisitor<R, P>
+{
+
+ /**
+ * Visits an {@code and} filter.
+ * <p>
+ * <b>Implementation note</b>: for the purposes of matching an empty
+ * sub-filter list should always evaluate to {@code true} as per RFC
+ * 4526.
+ *
+ * @param p
+ * A visitor specified parameter.
+ * @param subFilters
+ * The unmodifiable list of sub-filters.
+ * @return Returns a visitor specified result.
+ */
+ R visitAndFilter(P p, List<Filter> subFilters);
+
+
+
+ /**
+ * Visits an {@code approximate match} filter.
+ *
+ * @param p
+ * A visitor specified parameter.
+ * @param attributeDescription
+ * The attribute description.
+ * @param assertionValue
+ * The assertion value.
+ * @return Returns a visitor specified result.
+ */
+ R visitApproxMatchFilter(P p, String attributeDescription,
+ ByteSequence assertionValue);
+
+
+
+ /**
+ * Visits an {@code equality match} filter.
+ *
+ * @param p
+ * A visitor specified parameter.
+ * @param attributeDescription
+ * The attribute description.
+ * @param assertionValue
+ * The assertion value.
+ * @return Returns a visitor specified result.
+ */
+ R visitEqualityMatchFilter(P p, String attributeDescription,
+ ByteSequence assertionValue);
+
+
+
+ /**
+ * Visits an {@code extensible} filter.
+ *
+ * @param p
+ * A visitor specified parameter.
+ * @param matchingRule
+ * The matching rule name, may be {@code null} if {@code
+ * attributeDescription} is specified.
+ * @param attributeDescription
+ * The attribute description, may be {@code null} if {@code
+ * matchingRule} is specified.
+ * @param assertionValue
+ * The assertion value.
+ * @param dnAttributes
+ * Indicates whether DN matching should be performed.
+ * @return Returns a visitor specified result.
+ */
+ R visitExtensibleMatchFilter(P p, String matchingRule,
+ String attributeDescription, ByteSequence assertionValue,
+ boolean dnAttributes);
+
+
+
+ /**
+ * Visits a {@code greater or equal} filter.
+ *
+ * @param p
+ * A visitor specified parameter.
+ * @param attributeDescription
+ * The attribute description.
+ * @param assertionValue
+ * The assertion value.
+ * @return Returns a visitor specified result.
+ */
+ R visitGreaterOrEqualFilter(P p, String attributeDescription,
+ ByteSequence assertionValue);
+
+
+
+ /**
+ * Visits a {@code less or equal} filter.
+ *
+ * @param p
+ * A visitor specified parameter.
+ * @param attributeDescription
+ * The attribute description.
+ * @param assertionValue
+ * The assertion value.
+ * @return Returns a visitor specified result.
+ */
+ R visitLessOrEqualFilter(P p, String attributeDescription,
+ ByteSequence assertionValue);
+
+
+
+ /**
+ * Visits a {@code not} filter.
+ *
+ * @param p
+ * A visitor specified parameter.
+ * @param subFilter
+ * The sub-filter.
+ * @return Returns a visitor specified result.
+ */
+ R visitNotFilter(P p, Filter subFilter);
+
+
+
+ /**
+ * Visits an {@code or} filter.
+ * <p>
+ * <b>Implementation note</b>: for the purposes of matching an empty
+ * sub-filter list should always evaluate to {@code false} as per RFC
+ * 4526.
+ *
+ * @param p
+ * A visitor specified parameter.
+ * @param subFilters
+ * The unmodifiable list of sub-filters.
+ * @return Returns a visitor specified result.
+ */
+ R visitOrFilter(P p, List<Filter> subFilters);
+
+
+
+ /**
+ * Visits a {@code present} filter.
+ *
+ * @param p
+ * A visitor specified parameter.
+ * @param attributeDescription
+ * The attribute description.
+ * @return Returns a visitor specified result.
+ */
+ R visitPresentFilter(P p, String attributeDescription);
+
+
+
+ /**
+ * Visits a {@code substrings} filter.
+ *
+ * @param p
+ * A visitor specified parameter.
+ * @param attributeDescription
+ * The attribute description.
+ * @param initialSubstring
+ * The initial sub-string, may be {@code null}.
+ * @param anySubstrings
+ * The unmodifiable list of any sub-strings, may be empty.
+ * @param finalSubstring
+ * The final sub-string, may be {@code null}.
+ * @return Returns a visitor specified result.
+ */
+ R visitSubstringsFilter(P p, String attributeDescription,
+ ByteSequence initialSubstring, List<ByteSequence> anySubstrings,
+ ByteSequence finalSubstring);
+
+
+
+ /**
+ * Visits an {@code unrecognized} filter.
+ *
+ * @param p
+ * A visitor specified parameter.
+ * @param filterTag
+ * The ASN.1 tag.
+ * @param filterBytes
+ * The filter content.
+ * @return Returns a visitor specified result.
+ */
+ R visitUnrecognizedFilter(P p, byte filterTag, ByteSequence filterBytes);
+
+}
\ No newline at end of file
diff --git a/sdk/src/org/opends/sdk/LinkedAttribute.java b/sdk/src/org/opends/sdk/LinkedAttribute.java
new file mode 100644
index 0000000..1b52b5b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/LinkedAttribute.java
@@ -0,0 +1,1063 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.*;
+
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * An implementation of the {@code Attribute} interface with predictable
+ * iteration order.
+ * <p>
+ * Internally, attribute values are stored in a linked list and it's
+ * this list which defines the iteration ordering, which is the order in
+ * which elements were inserted into the set (insertion-order). This
+ * ordering is particularly useful in LDAP where clients generally
+ * appreciate having things returned in the same order they were
+ * presented.
+ * <p>
+ * All operations are supported by this implementation.
+ */
+public final class LinkedAttribute extends AbstractAttribute
+{
+ private static abstract class Impl
+ {
+
+ abstract boolean add(LinkedAttribute attribute, ByteString value);
+
+
+
+ boolean addAll(LinkedAttribute attribute,
+ Collection<? extends ByteString> values,
+ Collection<? super ByteString> duplicateValues)
+ throws NullPointerException
+ {
+ // TODO: could optimize if values is a BasicAttribute.
+ ensureCapacity(attribute, values.size());
+ boolean modified = false;
+ for (ByteString value : values)
+ {
+ if (add(attribute, value))
+ {
+ modified = true;
+ }
+ else if (duplicateValues != null)
+ {
+ duplicateValues.add(value);
+ }
+ }
+ resize(attribute);
+ return modified;
+ }
+
+
+
+ abstract void clear(LinkedAttribute attribute);
+
+
+
+ abstract boolean contains(LinkedAttribute attribute,
+ ByteString value);
+
+
+
+ boolean containsAll(LinkedAttribute attribute, Collection<?> values)
+ {
+ // TODO: could optimize if objects is a BasicAttribute.
+ for (Object value : values)
+ {
+ if (!contains(attribute, ByteString.valueOf(value)))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+
+ abstract void ensureCapacity(LinkedAttribute attribute, int size);
+
+
+
+ abstract ByteString firstValue(LinkedAttribute attribute)
+ throws NoSuchElementException;
+
+
+
+ abstract Iterator<ByteString> iterator(LinkedAttribute attribute);
+
+
+
+ abstract boolean remove(LinkedAttribute attribute, ByteString value);
+
+
+
+ <T> boolean removeAll(LinkedAttribute attribute,
+ Collection<T> values, Collection<? super T> missingValues)
+ {
+ // TODO: could optimize if objects is a BasicAttribute.
+ boolean modified = false;
+ for (T value : values)
+ {
+ if (remove(attribute, ByteString.valueOf(value)))
+ {
+ modified = true;
+ }
+ else if (missingValues != null)
+ {
+ missingValues.add(value);
+ }
+ }
+ return modified;
+ }
+
+
+
+ abstract void resize(LinkedAttribute attribute);
+
+
+
+ abstract <T> boolean retainAll(LinkedAttribute attribute,
+ Collection<T> values, Collection<? super T> missingValues);
+
+
+
+ abstract int size(LinkedAttribute attribute);
+ }
+
+
+
+ private static final class MultiValueImpl extends Impl
+ {
+
+ boolean add(LinkedAttribute attribute, ByteString value)
+ {
+ ByteString normalizedValue = normalizeValue(attribute, value);
+ if (attribute.multipleValues.put(normalizedValue, value) == null)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+
+ void clear(LinkedAttribute attribute)
+ {
+ attribute.multipleValues = null;
+ attribute.pimpl = ZERO_VALUE_IMPL;
+ }
+
+
+
+ boolean contains(LinkedAttribute attribute, ByteString value)
+ {
+ return attribute.multipleValues.containsKey(normalizeValue(
+ attribute, value));
+ }
+
+
+
+ void ensureCapacity(LinkedAttribute attribute, int size)
+ {
+ // Nothing to do.
+ }
+
+
+
+ ByteString firstValue(LinkedAttribute attribute)
+ throws NoSuchElementException
+ {
+ return attribute.multipleValues.values().iterator().next();
+ }
+
+
+
+ Iterator<ByteString> iterator(final LinkedAttribute attribute)
+ {
+ return new Iterator<ByteString>()
+ {
+ private Impl expectedImpl = MULTI_VALUE_IMPL;
+
+ private Iterator<ByteString> iterator = attribute.multipleValues
+ .values().iterator();
+
+
+
+ public boolean hasNext()
+ {
+ return iterator.hasNext();
+ }
+
+
+
+ public ByteString next()
+ {
+ if (attribute.pimpl != expectedImpl)
+ {
+ throw new ConcurrentModificationException();
+ }
+ else
+ {
+ return iterator.next();
+ }
+ }
+
+
+
+ public void remove()
+ {
+ if (attribute.pimpl != expectedImpl)
+ {
+ throw new ConcurrentModificationException();
+ }
+ else
+ {
+ iterator.remove();
+
+ // Resize if we have removed the second to last value.
+ if (attribute.multipleValues != null
+ && attribute.multipleValues.size() == 1)
+ {
+ resize(attribute);
+ iterator = attribute.pimpl.iterator(attribute);
+ }
+
+ // Always update since we may change to single or zero value
+ // impl.
+ expectedImpl = attribute.pimpl;
+ }
+ }
+
+ };
+ }
+
+
+
+ boolean remove(LinkedAttribute attribute, ByteString value)
+ {
+ ByteString normalizedValue = normalizeValue(attribute, value);
+ if (attribute.multipleValues.remove(normalizedValue) != null)
+ {
+ resize(attribute);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+
+ void resize(LinkedAttribute attribute)
+ {
+ // May need to resize if initial size estimate was wrong (e.g. all
+ // values in added collection were the same).
+ switch (attribute.multipleValues.size())
+ {
+ case 0:
+ attribute.multipleValues = null;
+ attribute.pimpl = ZERO_VALUE_IMPL;
+ break;
+ case 1:
+ Map.Entry<ByteString, ByteString> e = attribute.multipleValues
+ .entrySet().iterator().next();
+ attribute.singleValue = e.getValue();
+ attribute.normalizedSingleValue = e.getKey();
+ attribute.multipleValues = null;
+ attribute.pimpl = SINGLE_VALUE_IMPL;
+ break;
+ default:
+ // Nothing to do.
+ break;
+ }
+ }
+
+
+
+ <T> boolean retainAll(LinkedAttribute attribute,
+ Collection<T> values, Collection<? super T> missingValues)
+ {
+ // TODO: could optimize if objects is a BasicAttribute.
+ if (values.isEmpty())
+ {
+ clear(attribute);
+ return true;
+ }
+
+ Map<ByteString, T> valuesToRetain = new HashMap<ByteString, T>(
+ values.size());
+ for (T value : values)
+ {
+ valuesToRetain.put(normalizeValue(attribute, ByteString
+ .valueOf(value)), value);
+ }
+
+ boolean modified = false;
+ Iterator<ByteString> iterator = attribute.multipleValues.keySet()
+ .iterator();
+ while (iterator.hasNext())
+ {
+ ByteString normalizedValue = iterator.next();
+ if (valuesToRetain.remove(normalizedValue) == null)
+ {
+ modified = true;
+ iterator.remove();
+ }
+ }
+
+ if (missingValues != null)
+ {
+ missingValues.addAll(valuesToRetain.values());
+ }
+
+ resize(attribute);
+
+ return modified;
+ }
+
+
+
+ int size(LinkedAttribute attribute)
+ {
+ return attribute.multipleValues.size();
+ }
+ }
+
+
+
+ private static final class SingleValueImpl extends Impl
+ {
+
+ boolean add(LinkedAttribute attribute, ByteString value)
+ {
+ ByteString normalizedValue = normalizeValue(attribute, value);
+ if (attribute.normalizedSingleValue().equals(normalizedValue))
+ {
+ return false;
+ }
+
+ attribute.multipleValues = new LinkedHashMap<ByteString, ByteString>(
+ 2);
+ attribute.multipleValues.put(attribute.normalizedSingleValue,
+ attribute.singleValue);
+ attribute.multipleValues.put(normalizedValue, value);
+ attribute.singleValue = null;
+ attribute.normalizedSingleValue = null;
+ attribute.pimpl = MULTI_VALUE_IMPL;
+
+ return true;
+ }
+
+
+
+ void clear(LinkedAttribute attribute)
+ {
+ attribute.singleValue = null;
+ attribute.normalizedSingleValue = null;
+ attribute.pimpl = ZERO_VALUE_IMPL;
+ }
+
+
+
+ boolean contains(LinkedAttribute attribute, ByteString value)
+ {
+ ByteString normalizedValue = normalizeValue(attribute, value);
+ return attribute.normalizedSingleValue().equals(normalizedValue);
+ }
+
+
+
+ void ensureCapacity(LinkedAttribute attribute, int size)
+ {
+ if (size == 0)
+ {
+ return;
+ }
+
+ attribute.multipleValues = new LinkedHashMap<ByteString, ByteString>(
+ 1 + size);
+ attribute.multipleValues.put(attribute.normalizedSingleValue,
+ attribute.singleValue);
+ attribute.singleValue = null;
+ attribute.normalizedSingleValue = null;
+ attribute.pimpl = MULTI_VALUE_IMPL;
+ }
+
+
+
+ ByteString firstValue(LinkedAttribute attribute)
+ throws NoSuchElementException
+ {
+ if (attribute.singleValue != null)
+ {
+ return attribute.singleValue;
+ }
+ else
+ {
+ throw new NoSuchElementException();
+ }
+ }
+
+
+
+ Iterator<ByteString> iterator(final LinkedAttribute attribute)
+ {
+ return new Iterator<ByteString>()
+ {
+ private Impl expectedImpl = SINGLE_VALUE_IMPL;
+
+ private boolean hasNext = true;
+
+
+
+ public boolean hasNext()
+ {
+ return hasNext;
+ }
+
+
+
+ public ByteString next()
+ {
+ if (attribute.pimpl != expectedImpl)
+ {
+ throw new ConcurrentModificationException();
+ }
+ else if (hasNext)
+ {
+ hasNext = false;
+ return attribute.singleValue;
+ }
+ else
+ {
+ throw new NoSuchElementException();
+ }
+ }
+
+
+
+ public void remove()
+ {
+ if (attribute.pimpl != expectedImpl)
+ {
+ throw new ConcurrentModificationException();
+ }
+ else if (hasNext || attribute.singleValue == null)
+ {
+ throw new IllegalStateException();
+ }
+ else
+ {
+ clear(attribute);
+ expectedImpl = attribute.pimpl;
+ }
+ }
+
+ };
+ }
+
+
+
+ boolean remove(LinkedAttribute attribute, ByteString value)
+ {
+ if (contains(attribute, value))
+ {
+ clear(attribute);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+
+ void resize(LinkedAttribute attribute)
+ {
+ // Nothing to do.
+ }
+
+
+
+ <T> boolean retainAll(LinkedAttribute attribute,
+ Collection<T> values, Collection<? super T> missingValues)
+ {
+ // TODO: could optimize if objects is a BasicAttribute.
+ if (values.isEmpty())
+ {
+ clear(attribute);
+ return true;
+ }
+
+ ByteString normalizedSingleValue = attribute
+ .normalizedSingleValue();
+ boolean retained = false;
+ for (T value : values)
+ {
+ ByteString normalizedValue = normalizeValue(attribute,
+ ByteString.valueOf(value));
+ if (normalizedSingleValue.equals(normalizedValue))
+ {
+ if (missingValues == null)
+ {
+ // We can stop now.
+ return false;
+ }
+ retained = true;
+ }
+ else if (missingValues != null)
+ {
+ missingValues.add(value);
+ }
+ }
+
+ if (!retained)
+ {
+ clear(attribute);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+
+ int size(LinkedAttribute attribute)
+ {
+ return 1;
+ }
+ }
+
+
+
+ private static final class ZeroValueImpl extends Impl
+ {
+
+ boolean add(LinkedAttribute attribute, ByteString value)
+ {
+ attribute.singleValue = value;
+ attribute.pimpl = SINGLE_VALUE_IMPL;
+ return true;
+ }
+
+
+
+ void clear(LinkedAttribute attribute)
+ {
+ // Nothing to do.
+ }
+
+
+
+ boolean contains(LinkedAttribute attribute, ByteString value)
+ {
+ return false;
+ }
+
+
+
+ boolean containsAll(LinkedAttribute attribute, Collection<?> values)
+ {
+ return values.isEmpty();
+ }
+
+
+
+ void ensureCapacity(LinkedAttribute attribute, int size)
+ {
+ if (size < 2)
+ {
+ return;
+ }
+
+ attribute.multipleValues = new LinkedHashMap<ByteString, ByteString>(
+ size);
+ attribute.pimpl = MULTI_VALUE_IMPL;
+ }
+
+
+
+ ByteString firstValue(LinkedAttribute attribute)
+ throws NoSuchElementException
+ {
+ throw new NoSuchElementException();
+ }
+
+
+
+ Iterator<ByteString> iterator(final LinkedAttribute attribute)
+ {
+ return new Iterator<ByteString>()
+ {
+ public boolean hasNext()
+ {
+ return false;
+ }
+
+
+
+ public ByteString next()
+ {
+ if (attribute.pimpl != ZERO_VALUE_IMPL)
+ {
+ throw new ConcurrentModificationException();
+ }
+ else
+ {
+ throw new NoSuchElementException();
+ }
+ }
+
+
+
+ public void remove()
+ {
+ if (attribute.pimpl != ZERO_VALUE_IMPL)
+ {
+ throw new ConcurrentModificationException();
+ }
+ else
+ {
+ throw new IllegalStateException();
+ }
+ }
+
+ };
+ }
+
+
+
+ boolean remove(LinkedAttribute attribute, ByteString value)
+ {
+ return false;
+ }
+
+
+
+ void resize(LinkedAttribute attribute)
+ {
+ // Nothing to do.
+ }
+
+
+
+ <T> boolean retainAll(LinkedAttribute attribute,
+ Collection<T> values, Collection<? super T> missingValues)
+ {
+ if (missingValues != null)
+ {
+ missingValues.addAll(values);
+ }
+ return false;
+ }
+
+
+
+ int size(LinkedAttribute attribute)
+ {
+ return 0;
+ }
+
+ }
+
+
+
+ private static final MultiValueImpl MULTI_VALUE_IMPL = new MultiValueImpl();
+
+ private static final SingleValueImpl SINGLE_VALUE_IMPL = new SingleValueImpl();
+
+ private static final ZeroValueImpl ZERO_VALUE_IMPL = new ZeroValueImpl();
+
+ private final AttributeDescription attributeDescription;
+
+ private Map<ByteString, ByteString> multipleValues = null;
+
+ private ByteString normalizedSingleValue = null;
+
+ private Impl pimpl = ZERO_VALUE_IMPL;
+
+ private ByteString singleValue = null;
+
+
+
+ /**
+ * Creates a new attribute having the same attribute description and
+ * attribute values as {@code attribute}.
+ *
+ * @param attribute
+ * The attribute to be copied.
+ * @throws NullPointerException
+ * If {@code attribute} was {@code null}.
+ */
+ public LinkedAttribute(Attribute attribute)
+ throws NullPointerException
+ {
+ this.attributeDescription = attribute.getAttributeDescription();
+
+ if (attribute instanceof LinkedAttribute)
+ {
+ LinkedAttribute other = (LinkedAttribute) attribute;
+ this.pimpl = other.pimpl;
+ this.singleValue = other.singleValue;
+ this.normalizedSingleValue = other.normalizedSingleValue;
+ if (other.multipleValues != null)
+ {
+ this.multipleValues = new LinkedHashMap<ByteString, ByteString>(
+ other.multipleValues);
+ }
+ }
+ else
+ {
+ addAll(attribute);
+ }
+ }
+
+
+
+ /**
+ * Creates a new attribute having the specified attribute description
+ * and no attribute values.
+ *
+ * @param attributeDescription
+ * The attribute description.
+ * @throws NullPointerException
+ * If {@code attributeDescription} was {@code null}.
+ */
+ public LinkedAttribute(AttributeDescription attributeDescription)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(attributeDescription);
+ this.attributeDescription = attributeDescription;
+ }
+
+
+
+ /**
+ * Creates a new attribute having the specified attribute description
+ * and no attribute values. The attribute description will be decoded
+ * using the default schema.
+ *
+ * @param attributeDescription
+ * The attribute description.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code attributeDescription} could not be decoded
+ * using the default schema.
+ * @throws NullPointerException
+ * If {@code attributeDescription} was {@code null}.
+ */
+ public LinkedAttribute(String attributeDescription)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ this(AttributeDescription.valueOf(attributeDescription));
+ }
+
+
+
+ /**
+ * Creates a new attribute having the specified attribute description
+ * and single attribute value.
+ *
+ * @param attributeDescription
+ * The attribute description.
+ * @param value
+ * The single attribute value.
+ * @throws NullPointerException
+ * If {@code attributeDescription} or {@code value} was
+ * {@code null}.
+ */
+ public LinkedAttribute(AttributeDescription attributeDescription,
+ ByteString value) throws NullPointerException
+ {
+ this(attributeDescription);
+ add(value);
+ }
+
+
+
+ /**
+ * Creates a new attribute having the specified attribute description
+ * and attribute values.
+ *
+ * @param attributeDescription
+ * The attribute description.
+ * @param values
+ * The attribute values.
+ * @throws NullPointerException
+ * If {@code attributeDescription} or {@code values} was
+ * {@code null}.
+ */
+ public LinkedAttribute(AttributeDescription attributeDescription,
+ ByteString... values) throws NullPointerException
+ {
+ this(attributeDescription);
+ addAll(Arrays.asList(values));
+ }
+
+
+
+ /**
+ * Creates a new attribute having the specified attribute description
+ * and attribute values.
+ *
+ * @param attributeDescription
+ * The attribute description.
+ * @param values
+ * The attribute values.
+ * @throws NullPointerException
+ * If {@code attributeDescription} or {@code values} was
+ * {@code null}.
+ */
+ public LinkedAttribute(AttributeDescription attributeDescription,
+ Collection<ByteString> values) throws NullPointerException
+ {
+ this(attributeDescription);
+ addAll(values);
+ }
+
+
+
+ /**
+ * Creates a new attribute having the specified attribute description
+ * and single attribute value. The attribute description will be
+ * decoded using the default schema.
+ * <p>
+ * If {@code value} is not an instance of {@code ByteString} then it
+ * will be converted using the {@link ByteString#valueOf(Object)}
+ * method.
+ *
+ * @param attributeDescription
+ * The attribute description.
+ * @param value
+ * The single attribute value.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code attributeDescription} could not be decoded
+ * using the default schema.
+ * @throws NullPointerException
+ * If {@code attributeDescription} or {@code value} was
+ * {@code null}.
+ */
+ public LinkedAttribute(String attributeDescription, Object value)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ this(attributeDescription);
+ add(ByteString.valueOf(value));
+ }
+
+
+
+ /**
+ * Creates a new attribute having the specified attribute description
+ * and attribute values. The attribute description will be decoded
+ * using the default schema.
+ * <p>
+ * Any attribute values which are not instances of {@code ByteString}
+ * will be converted using the {@link ByteString#valueOf(Object)}
+ * method.
+ *
+ * @param attributeDescription
+ * The attribute description.
+ * @param values
+ * The attribute values.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code attributeDescription} could not be decoded
+ * using the default schema.
+ * @throws NullPointerException
+ * If {@code attributeDescription} or {@code values} was
+ * {@code null}.
+ */
+ public LinkedAttribute(String attributeDescription, Object... values)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ this(attributeDescription);
+ for (Object value : values)
+ {
+ add(ByteString.valueOf(value));
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean add(ByteString value) throws NullPointerException
+ {
+ Validator.ensureNotNull(value);
+ return pimpl.add(this, value);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean addAll(Collection<? extends ByteString> values,
+ Collection<? super ByteString> duplicateValues)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(values);
+ return pimpl.addAll(this, values, duplicateValues);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void clear()
+ {
+ pimpl.clear(this);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean containsAll(Collection<?> values)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(values);
+ return pimpl.containsAll(this, values);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteString firstValue() throws NoSuchElementException
+ {
+ return pimpl.firstValue(this);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public AttributeDescription getAttributeDescription()
+ {
+ return attributeDescription;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterator<ByteString> iterator()
+ {
+ return pimpl.iterator(this);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <T> boolean removeAll(Collection<T> values,
+ Collection<? super T> missingValues) throws NullPointerException
+ {
+ Validator.ensureNotNull(values);
+ return pimpl.removeAll(this, values, missingValues);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <T> boolean retainAll(Collection<T> values,
+ Collection<? super T> missingValues) throws NullPointerException
+ {
+ Validator.ensureNotNull(values);
+ return pimpl.retainAll(this, values, missingValues);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int size()
+ {
+ return pimpl.size(this);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean contains(Object value) throws NullPointerException
+ {
+ Validator.ensureNotNull(value);
+ return pimpl.contains(this, ByteString.valueOf(value));
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean remove(Object value) throws NullPointerException
+ {
+ Validator.ensureNotNull(value);
+ return pimpl.remove(this, ByteString.valueOf(value));
+ }
+
+
+
+ // Lazily computes the normalized single value.
+ private ByteString normalizedSingleValue()
+ {
+ if (normalizedSingleValue == null)
+ {
+ normalizedSingleValue = normalizeValue(this, singleValue);
+ }
+ return normalizedSingleValue;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/Matcher.java b/sdk/src/org/opends/sdk/Matcher.java
new file mode 100644
index 0000000..7e7aace
--- /dev/null
+++ b/sdk/src/org/opends/sdk/Matcher.java
@@ -0,0 +1,871 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import static org.opends.sdk.util.StaticUtils.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+
+import org.opends.sdk.schema.MatchingRule;
+import org.opends.sdk.schema.MatchingRuleUse;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.schema.UnknownSchemaElementException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.StaticUtils;
+
+
+/**
+ * An interface for determining whether entries match a {@code Filter}.
+ */
+public final class Matcher
+{
+ private static class AndMatcherImpl extends MatcherImpl
+ {
+ private final List<MatcherImpl> subMatchers;
+
+
+
+ private AndMatcherImpl(List<MatcherImpl> subMatchers)
+ {
+ this.subMatchers = subMatchers;
+ }
+
+
+
+ @Override
+ public ConditionResult matches(Entry entry)
+ {
+ ConditionResult r = ConditionResult.TRUE;
+ for (final MatcherImpl m : subMatchers)
+ {
+ final ConditionResult p = m.matches(entry);
+ if (p == ConditionResult.FALSE)
+ {
+ return p;
+ }
+ r = ConditionResult.and(r, p);
+ }
+ return r;
+ }
+ }
+
+
+
+ private static class AssertionMatcherImpl extends MatcherImpl
+ {
+ private final Assertion assertion;
+ private final AttributeDescription attributeDescription;
+ private final boolean dnAttributes;
+ private final MatchingRule rule;
+ private final MatchingRuleUse ruleUse;
+
+
+
+ private AssertionMatcherImpl(
+ AttributeDescription attributeDescription, MatchingRule rule,
+ MatchingRuleUse ruleUse, Assertion assertion,
+ boolean dnAttributes)
+ {
+ this.attributeDescription = attributeDescription;
+ this.rule = rule;
+ this.ruleUse = ruleUse;
+ this.assertion = assertion;
+ this.dnAttributes = dnAttributes;
+ }
+
+
+
+ @Override
+ public ConditionResult matches(Entry entry)
+ {
+ ConditionResult r = ConditionResult.FALSE;
+ if (attributeDescription != null)
+ {
+ // If the matchingRule field is absent, the type field will be
+ // present and the default equality matching rule is used,
+ // and an equality match is performed for that type.
+
+ // If the type field is present and the matchingRule is present,
+ // the matchValue is compared against the specified attribute
+ // type and its subtypes.
+ final ConditionResult p = Matcher.matches(
+ entry.getAttribute(attributeDescription), rule, assertion);
+ if (p == ConditionResult.TRUE)
+ {
+ return p;
+ }
+ r = ConditionResult.or(r, p);
+ }
+ else
+ {
+ // If the type field is absent and the matchingRule is present,
+ // the matchValue is compared against all attributes in an entry
+ // that support that matchingRule.
+ for (final Attribute a : entry.getAttributes())
+ {
+ if (ruleUse.hasAttribute(a.getAttributeDescription()
+ .getAttributeType()))
+ {
+ final ConditionResult p = Matcher.matches(a, rule, assertion);
+ if (p == ConditionResult.TRUE)
+ {
+ return p;
+ }
+ r = ConditionResult.or(r, p);
+ }
+ }
+ }
+
+ if (dnAttributes)
+ {
+ // If the dnAttributes field is set to TRUE, the match is
+ // additionally applied against all the AttributeValueAssertions
+ // in an entry's distinguished name, and it evaluates to TRUE if
+ // there is at least one attribute or subtype in the
+ // distinguished name for which the filter item evaluates to
+ // TRUE.
+ final DN dn = entry.getName();
+ for (final RDN rdn : dn)
+ {
+ for (final RDN.AVA ava : rdn)
+ {
+ if (ruleUse.hasAttribute(ava.getAttributeType()))
+ {
+ final ConditionResult p =
+ Matcher.matches(ava.getAttributeValue(), rule, assertion);
+ if (p == ConditionResult.TRUE)
+ {
+ return p;
+ }
+ r = ConditionResult.or(r, p);
+ }
+ }
+ }
+ }
+ return r;
+ }
+ }
+
+
+
+ private static class FalseMatcherImpl extends MatcherImpl
+ {
+ @Override
+ public ConditionResult matches(Entry entry)
+ {
+ return ConditionResult.FALSE;
+ }
+ }
+
+
+
+ private static abstract class MatcherImpl
+ {
+ public abstract ConditionResult matches(Entry entry);
+ }
+
+
+
+ private static class NotMatcherImpl extends MatcherImpl
+ {
+ private final MatcherImpl subFilter;
+
+
+
+ private NotMatcherImpl(MatcherImpl subFilter)
+ {
+ this.subFilter = subFilter;
+ }
+
+
+
+ @Override
+ public ConditionResult matches(Entry entry)
+ {
+ return ConditionResult.not(subFilter.matches(entry));
+ }
+ }
+
+
+
+ private static class OrMatcherImpl extends MatcherImpl
+ {
+ private final List<MatcherImpl> subMatchers;
+
+
+
+ private OrMatcherImpl(List<MatcherImpl> subMatchers)
+ {
+ this.subMatchers = subMatchers;
+ }
+
+
+
+ @Override
+ public ConditionResult matches(Entry entry)
+ {
+ ConditionResult r = ConditionResult.FALSE;
+ for (final MatcherImpl m : subMatchers)
+ {
+ final ConditionResult p = m.matches(entry);
+ if (p == ConditionResult.TRUE)
+ {
+ return p;
+ }
+ r = ConditionResult.or(r, p);
+ }
+ return r;
+ }
+ }
+
+
+
+ private static class PresentMatcherImpl extends MatcherImpl
+ {
+ private final AttributeDescription attribute;
+
+
+
+ private PresentMatcherImpl(AttributeDescription attribute)
+ {
+ this.attribute = attribute;
+ }
+
+
+
+ @Override
+ public ConditionResult matches(Entry entry)
+ {
+ return entry.getAttribute(attribute) == null ? ConditionResult.FALSE
+ : ConditionResult.TRUE;
+ }
+ }
+
+
+
+ private static class TrueMatcherImpl extends MatcherImpl
+ {
+ @Override
+ public ConditionResult matches(Entry entry)
+ {
+ return ConditionResult.TRUE;
+ }
+ }
+
+
+
+ private static class UndefinedMatcherImpl extends MatcherImpl
+ {
+ @Override
+ public ConditionResult matches(Entry entry)
+ {
+ return ConditionResult.UNDEFINED;
+ }
+ }
+
+
+
+ /**
+ * A visitor which is used to transform a filter into a matcher.
+ */
+ private static final class Visitor implements
+ FilterVisitor<MatcherImpl, Schema>
+ {
+ public MatcherImpl visitAndFilter(Schema schema,
+ List<Filter> subFilters)
+ {
+ if (subFilters.isEmpty())
+ {
+ if(DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ DEBUG_LOG.finer("Empty add filter component. " +
+ "Will always return TRUE");
+ }
+ return TRUE;
+ }
+
+ final List<MatcherImpl> subMatchers =
+ new ArrayList<MatcherImpl>(subFilters.size());
+ for (final Filter f : subFilters)
+ {
+ subMatchers.add(f.accept(this, schema));
+ }
+ return new AndMatcherImpl(subMatchers);
+ }
+
+
+
+ public MatcherImpl visitApproxMatchFilter(Schema schema,
+ String attributeDescription, ByteSequence assertionValue)
+ {
+ AttributeDescription ad;
+ MatchingRule rule;
+ Assertion assertion;
+
+ try
+ {
+ ad = AttributeDescription.valueOf(attributeDescription, schema);
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ if(DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ DEBUG_LOG.warning(
+ "Attribute description " + attributeDescription +
+ " is not recognized: " + e.toString());
+ }
+ return UNDEFINED;
+ }
+
+ if ((rule = ad.getAttributeType().getApproximateMatchingRule()) == null)
+ {
+ if(DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ DEBUG_LOG.warning(
+ "The attribute type " + attributeDescription +
+ " does not define an approximate matching rule");
+ }
+ return UNDEFINED;
+ }
+
+ try
+ {
+ assertion = rule.getAssertion(assertionValue);
+ }
+ catch (final DecodeException de)
+ {
+ if(DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ DEBUG_LOG.warning(
+ "The assertion value " + assertionValue + " is invalid: " +
+ de.toString());
+ }
+ return UNDEFINED;
+ }
+ return new AssertionMatcherImpl(ad, rule, null, assertion, false);
+ }
+
+
+
+ public MatcherImpl visitEqualityMatchFilter(Schema schema,
+ String attributeDescription, ByteSequence assertionValue)
+ {
+ AttributeDescription ad;
+ MatchingRule rule;
+ Assertion assertion;
+
+ try
+ {
+ ad = AttributeDescription.valueOf(attributeDescription, schema);
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ if(DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ DEBUG_LOG.warning(
+ "Attribute description " + attributeDescription +
+ " is not recognized: " + e.toString());
+ }
+ return UNDEFINED;
+ }
+
+ if ((rule = ad.getAttributeType().getEqualityMatchingRule()) == null)
+ {
+ if(DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ DEBUG_LOG.warning(
+ "The attribute type " + attributeDescription +
+ " does not define an equality matching rule");
+ }
+ return UNDEFINED;
+ }
+
+ try
+ {
+ assertion = rule.getAssertion(assertionValue);
+ }
+ catch (final DecodeException de)
+ {
+ if(DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ DEBUG_LOG.warning(
+ "The assertion value " + assertionValue + " is invalid: " +
+ de.toString());
+ }
+ return UNDEFINED;
+ }
+ return new AssertionMatcherImpl(ad, rule, null, assertion, false);
+ }
+
+
+
+ public MatcherImpl visitExtensibleMatchFilter(Schema schema,
+ String matchingRule,
+ String attributeDescription,
+ ByteSequence assertionValue,
+ boolean dnAttributes)
+ {
+ AttributeDescription ad = null;
+ MatchingRule rule = null;
+ MatchingRuleUse ruleUse = null;
+ Assertion assertion;
+
+ if (matchingRule != null)
+ {
+ try
+ {
+ rule = schema.getMatchingRule(matchingRule);
+ }
+ catch(final UnknownSchemaElementException e)
+ {
+ if(DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ DEBUG_LOG.warning(
+ "Matching rule " + matchingRule + " is not recognized: " +
+ e.toString());
+ }
+ return UNDEFINED;
+ }
+ }
+
+ if (attributeDescription != null)
+ {
+ try
+ {
+ ad =
+ AttributeDescription
+ .valueOf(attributeDescription, schema);
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ if(DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ DEBUG_LOG.warning(
+ "Attribute description " + attributeDescription +
+ " is not recognized: " + e.toString());
+ }
+ return UNDEFINED;
+ }
+
+ if (rule == null)
+ {
+ if ((rule = ad.getAttributeType().getEqualityMatchingRule()) == null)
+ {
+ if(DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ DEBUG_LOG.warning(
+ "The attribute type " + attributeDescription +
+ " does not define an equality matching rule");
+ }
+ return UNDEFINED;
+ }
+ }
+ else
+ {
+ try
+ {
+ ruleUse = schema.getMatchingRuleUse(rule);
+ }
+ catch(final UnknownSchemaElementException e)
+ {
+ if(DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ DEBUG_LOG.warning("No matching rule use is defined for " +
+ "matching rule " + matchingRule);
+ return UNDEFINED;
+ }
+ }
+ if(!ruleUse.hasAttribute(ad.getAttributeType()))
+ {
+ if(DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ DEBUG_LOG.warning("The matching rule " + matchingRule +
+ " is not valid for attribute type " +
+ attributeDescription);
+ }
+ return UNDEFINED;
+ }
+ }
+ }
+ else
+ {
+ try
+ {
+ ruleUse = schema.getMatchingRuleUse(rule);
+ }
+ catch(final UnknownSchemaElementException e)
+ {
+ if(DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ DEBUG_LOG.warning("No matching rule use is defined for " +
+ "matching rule " + matchingRule);
+ }
+ return UNDEFINED;
+ }
+ }
+
+ try
+ {
+ assertion = rule.getAssertion(assertionValue);
+ }
+ catch (final DecodeException de)
+ {
+ if(DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ DEBUG_LOG.warning(
+ "The assertion value " + assertionValue + " is invalid: " +
+ de.toString());
+ }
+ return UNDEFINED;
+ }
+ return new AssertionMatcherImpl(ad, rule, ruleUse, assertion,
+ dnAttributes);
+ }
+
+
+
+ public MatcherImpl visitGreaterOrEqualFilter(Schema schema,
+ String attributeDescription,
+ ByteSequence assertionValue)
+ {
+ AttributeDescription ad;
+ MatchingRule rule;
+ Assertion assertion;
+
+ try
+ {
+ ad = AttributeDescription.valueOf(attributeDescription, schema);
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ if(DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ DEBUG_LOG.warning(
+ "Attribute description " + attributeDescription +
+ " is not recognized: " + e.toString());
+ }
+ return UNDEFINED;
+ }
+
+ if ((rule = ad.getAttributeType().getOrderingMatchingRule()) == null)
+ {
+ if(DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ DEBUG_LOG.warning(
+ "The attribute type " + attributeDescription +
+ " does not define an ordering matching rule");
+ }
+ return UNDEFINED;
+ }
+
+ try
+ {
+ assertion = rule.getGreaterOrEqualAssertion(assertionValue);
+ }
+ catch (final DecodeException de)
+ {
+ if(DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ DEBUG_LOG.warning(
+ "The assertion value " + assertionValue + " is invalid: " +
+ de.toString());
+ }
+ return UNDEFINED;
+ }
+ return new AssertionMatcherImpl(ad, rule, null, assertion, false);
+ }
+
+
+
+ public MatcherImpl visitLessOrEqualFilter(Schema schema,
+ String attributeDescription,
+ ByteSequence assertionValue)
+ {
+ AttributeDescription ad;
+ MatchingRule rule;
+ Assertion assertion;
+
+ try
+ {
+ ad = AttributeDescription.valueOf(attributeDescription, schema);
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ if(DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ DEBUG_LOG.warning(
+ "Attribute description " + attributeDescription +
+ " is not recognized: " + e.toString());
+ }
+ return UNDEFINED;
+ }
+
+ if ((rule = ad.getAttributeType().getOrderingMatchingRule()) == null)
+ {
+ if(DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ DEBUG_LOG.warning(
+ "The attribute type " + attributeDescription +
+ " does not define an ordering matching rule");
+ }
+ return UNDEFINED;
+ }
+
+ try
+ {
+ assertion = rule.getLessOrEqualAssertion(assertionValue);
+ }
+ catch (final DecodeException de)
+ {
+ if(DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ DEBUG_LOG.warning(
+ "The assertion value " + assertionValue + " is invalid: " +
+ de.toString());
+ }
+ return UNDEFINED;
+ }
+ return new AssertionMatcherImpl(ad, rule, null, assertion, false);
+ }
+
+
+
+ public MatcherImpl visitNotFilter(Schema schema, Filter subFilter)
+ {
+ final MatcherImpl subMatcher = subFilter.accept(this, schema);
+ return new NotMatcherImpl(subMatcher);
+ }
+
+
+
+ public MatcherImpl visitOrFilter(Schema schema,
+ List<Filter> subFilters)
+ {
+ if (subFilters.isEmpty())
+ {
+ if(DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ DEBUG_LOG.finer("Empty or filter component. " +
+ "Will always return FALSE");
+ }
+ return FALSE;
+ }
+
+ final List<MatcherImpl> subMatchers =
+ new ArrayList<MatcherImpl>(subFilters.size());
+ for (final Filter f : subFilters)
+ {
+ subMatchers.add(f.accept(this, schema));
+ }
+ return new OrMatcherImpl(subMatchers);
+ }
+
+
+
+ public MatcherImpl visitPresentFilter(Schema schema,
+ String attributeDescription)
+ {
+ AttributeDescription ad;
+ try
+ {
+ ad = AttributeDescription.valueOf(attributeDescription, schema);
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ if(DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ DEBUG_LOG.warning(
+ "Attribute description " + attributeDescription +
+ " is not recognized: " + e.toString());
+ }
+ return UNDEFINED;
+ }
+
+ return new PresentMatcherImpl(ad);
+ }
+
+
+
+ public MatcherImpl visitSubstringsFilter(Schema schema,
+ String attributeDescription,
+ ByteSequence initialSubstring,
+ List<ByteSequence> anySubstrings,
+ ByteSequence finalSubstring)
+ {
+ AttributeDescription ad;
+ MatchingRule rule;
+ Assertion assertion;
+
+ try
+ {
+ ad = AttributeDescription.valueOf(attributeDescription, schema);
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ if(DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ DEBUG_LOG.warning(
+ "Attribute description " + attributeDescription +
+ " is not recognized: " + e.toString());
+ }
+ return UNDEFINED;
+ }
+
+ if ((rule = ad.getAttributeType().getSubstringMatchingRule()) == null)
+ {
+ if(DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ DEBUG_LOG.warning(
+ "The attribute type " + attributeDescription +
+ " does not define an substring matching rule");
+ }
+ return UNDEFINED;
+ }
+
+ try
+ {
+ assertion =
+ rule.getAssertion(initialSubstring, anySubstrings,
+ finalSubstring);
+ }
+ catch (final DecodeException de)
+ {
+ if(DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ DEBUG_LOG.warning(
+ "The substring assertion values contain an invalid value: " +
+ de.toString());
+ }
+ return UNDEFINED;
+ }
+ return new AssertionMatcherImpl(ad, rule, null, assertion, false);
+ }
+
+
+
+ public MatcherImpl visitUnrecognizedFilter(Schema schema,
+ byte filterTag,
+ ByteSequence filterBytes)
+ {
+ if(DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ DEBUG_LOG.warning("The type of filtering requested with tag " +
+ StaticUtils.byteToHex(filterTag) +
+ " is not implemented");
+ }
+ return UNDEFINED;
+ }
+ }
+
+ private static final MatcherImpl FALSE = new FalseMatcherImpl();
+
+ private static final MatcherImpl TRUE = new TrueMatcherImpl();
+
+ private static final MatcherImpl UNDEFINED =
+ new UndefinedMatcherImpl();
+
+ private static final FilterVisitor<MatcherImpl, Schema> VISITOR =
+ new Visitor();
+
+
+
+ private static ConditionResult matches(Attribute a,
+ MatchingRule rule, Assertion assertion)
+ {
+
+ ConditionResult r = ConditionResult.FALSE;
+ if (a != null)
+ {
+ for (final ByteString v : a)
+ {
+ switch (matches(v, rule, assertion))
+ {
+ case TRUE:
+ return ConditionResult.TRUE;
+ case UNDEFINED:
+ r = ConditionResult.UNDEFINED;
+ }
+ }
+ }
+ return r;
+ }
+
+
+
+ private static ConditionResult matches(ByteString v,
+ MatchingRule rule, Assertion assertion)
+ {
+ try
+ {
+ final ByteString normalizedValue =
+ rule.normalizeAttributeValue(v);
+ return assertion.matches(normalizedValue);
+ }
+ catch (final DecodeException de)
+ {
+ if(DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ DEBUG_LOG.warning("The attribute value " + v.toString() + " is " +
+ "invalid for matching rule " + rule.getNameOrOID() +
+ ". Possible schema error? : " + de.toString());
+ }
+ return ConditionResult.UNDEFINED;
+ }
+ }
+
+ private final MatcherImpl impl;
+
+
+
+ Matcher(Filter filter, Schema schema)
+ {
+ this.impl = filter.accept(VISITOR, schema);
+ }
+
+
+
+ /**
+ * Indicates whether this filter {@code Matcher} matches the provided
+ * {@code Entry}.
+ *
+ * @param entry
+ * The entry to be matched.
+ * @return {@code true} if this filter {@code Matcher} matches the
+ * provided {@code Entry}.
+ */
+ public ConditionResult matches(Entry entry)
+ {
+ return impl.matches(entry);
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ModificationType.java b/sdk/src/org/opends/sdk/ModificationType.java
new file mode 100644
index 0000000..020fb17
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ModificationType.java
@@ -0,0 +1,212 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+
+
+/**
+ * A Modify operation change type as defined in RFC 4511 section 4.6 is
+ * used to specify the type of modification being performed on an
+ * attribute.
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc4511#section-4.6">RFC
+ * 4511 - Lightweight Directory Access Protocol (LDAP): The
+ * Protocol </a>
+ * @see <a href="http://tools.ietf.org/html/rfc4525">RFC 4525 -
+ * Lightweight Directory Access Protocol (LDAP) Modify-Increment
+ * Extension </a>
+ */
+public final class ModificationType
+{
+ private static final ModificationType[] ELEMENTS = new ModificationType[4];
+
+ private static final List<ModificationType> IMMUTABLE_ELEMENTS = Collections
+ .unmodifiableList(Arrays.asList(ELEMENTS));
+
+ /**
+ * Add the values listed in the modification to the attribute,
+ * creating the attribute if necessary.
+ */
+ public static final ModificationType ADD = register(0, "add");
+
+ /**
+ * Delete the values listed in the modification from the attribute. If
+ * no values are listed, or if all current values of the attribute are
+ * listed, the entire attribute is removed.
+ */
+ public static final ModificationType DELETE = register(1, "delete");
+
+ /**
+ * Replace all existing values of the attribute with the new values
+ * listed in the modification, creating the attribute if it did not
+ * already exist. A replace with no listed values will delete the
+ * entire attribute if it exists, and it is ignored if the attribute
+ * does not exist.
+ */
+ public static final ModificationType REPLACE = register(2, "replace");
+
+ /**
+ * Increment all existing values of the attribute by the amount
+ * specified in the modification value.
+ */
+ public static final ModificationType INCREMENT = register(3,
+ "increment");
+
+
+
+ /**
+ * Creates and registers a new modification change type with the
+ * application.
+ *
+ * @param intValue
+ * The integer value of the modification change type as
+ * defined in RFC 4511 section 4.6.
+ * @param name
+ * The name of the modification change type.
+ * @return The new modification change type.
+ */
+ private static ModificationType register(int intValue, String name)
+ {
+ ModificationType t = new ModificationType(intValue, name);
+ ELEMENTS[intValue] = t;
+ return t;
+ }
+
+
+
+ /**
+ * Returns the modification change type having the specified integer
+ * value as defined in RFC 4511 section 4.6.
+ *
+ * @param intValue
+ * The integer value of the modification change type.
+ * @return The modification change type, or {@code null} if there was
+ * no modification change type associated with {@code
+ * intValue}.
+ */
+ public static ModificationType valueOf(int intValue)
+ {
+ if (intValue < 0 || intValue >= ELEMENTS.length)
+ {
+ return null;
+ }
+ return ELEMENTS[intValue];
+ }
+
+
+
+ /**
+ * Returns an unmodifiable list containing the set of available
+ * modification change types indexed on their integer value as defined
+ * in RFC 4511 section 4.6.
+ *
+ * @return An unmodifiable list containing the set of available
+ * modification change types.
+ */
+ public static List<ModificationType> values()
+ {
+ return IMMUTABLE_ELEMENTS;
+ }
+
+
+
+ private final int intValue;
+
+ private final String name;
+
+
+
+ // Prevent direct instantiation.
+ private ModificationType(int intValue, String name)
+ {
+ this.intValue = intValue;
+ this.name = name;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ else if (obj instanceof ModificationType)
+ {
+ return this.intValue == ((ModificationType) obj).intValue;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int hashCode()
+ {
+ return intValue;
+ }
+
+
+
+ /**
+ * Returns the integer value of this modification change type as
+ * defined in RFC 4511 section 4.6.
+ *
+ * @return The integer value of this modification change type.
+ */
+ public int intValue()
+ {
+ return intValue;
+ }
+
+
+
+ /**
+ * Returns the string representation of this modification change type.
+ *
+ * @return The string representation of this modification change type.
+ */
+ public String toString()
+ {
+ return name;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/RDN.java b/sdk/src/org/opends/sdk/RDN.java
new file mode 100644
index 0000000..2831f9e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/RDN.java
@@ -0,0 +1,899 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.util.StaticUtils.*;
+
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.sdk.schema.*;
+import org.opends.sdk.util.*;
+
+
+
+/**
+ * A relative distinguished name (RDN) as defined in RFC 4512 section
+ * 2.3 is the name of an entry relative to its immediate superior. An
+ * RDN is composed of an unordered set of one or more attribute value
+ * assertions (AVA) consisting of an attribute description with zero
+ * options and an attribute value. These AVAs are chosen to match
+ * attribute values (each a distinguished value) of the entry.
+ * <p>
+ * An entry's relative distinguished name must be unique among all
+ * immediate subordinates of the entry's immediate superior (i.e. all
+ * siblings).
+ * <p>
+ * The following are examples of string representations of RDNs:
+ *
+ * <pre>
+ * uid=12345
+ * ou=Engineering
+ * cn=Kurt Zeilenga+L=Redwood Shores
+ * </pre>
+ *
+ * The last is an example of a multi-valued RDN; that is, an RDN
+ * composed of multiple AVAs.
+ * <p>
+ * TODO: need more constructors.
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc4512#section-2.3">RFC
+ * 4512 - Lightweight Directory Access Protocol (LDAP): Directory
+ * Information Models </a>
+ */
+public final class RDN implements Iterable<RDN.AVA>, Comparable<RDN>
+{
+ /**
+ * An attribute value assertion (AVA) as defined in RFC 4512 section
+ * 2.3 consists of an attribute description with zero options and an
+ * attribute value.
+ */
+ public static final class AVA implements Comparable<AVA>
+ {
+ private final AttributeType attributeType;
+
+ private final ByteString attributeValue;
+
+
+
+ /**
+ * Creates a new attribute value assertion (AVA) using the provided
+ * attribute type and value.
+ *
+ * @param attributeType
+ * The attribute type.
+ * @param attributeValue
+ * The attribute value.
+ * @throws NullPointerException
+ * If {@code attributeType} or {@code attributeValue} was
+ * {@code null}.
+ */
+ public AVA(AttributeType attributeType, ByteString attributeValue)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(attributeType, attributeValue);
+
+ this.attributeType = attributeType;
+ this.attributeValue = attributeValue;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int compareTo(AVA ava)
+ {
+ int result = attributeType.compareTo(ava.attributeType);
+
+ if (result == 0)
+ {
+ final ByteString nv1 = getNormalizeValue();
+ final ByteString nv2 = ava.getNormalizeValue();
+ result = nv1.compareTo(nv2);
+ }
+
+ return result;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ else if (obj instanceof AVA)
+ {
+ return compareTo((AVA) obj) == 0;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+
+ /**
+ * Returns the attribute type associated with this AVA.
+ *
+ * @return The attribute type associated with this AVA.
+ */
+ public AttributeType getAttributeType()
+ {
+ return attributeType;
+ }
+
+
+
+ /**
+ * Returns the attribute value associated with this AVA.
+ *
+ * @return The attribute value associated with this AVA.
+ */
+ public ByteString getAttributeValue()
+ {
+ return attributeValue;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int hashCode()
+ {
+ return attributeType.hashCode() * 31
+ + getNormalizeValue().hashCode();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String toString()
+ {
+ final StringBuilder builder = new StringBuilder();
+ return toString(builder).toString();
+ }
+
+
+
+ private ByteString getNormalizeValue()
+ {
+ final MatchingRule matchingRule = attributeType
+ .getEqualityMatchingRule();
+ if (matchingRule != null)
+ {
+ try
+ {
+ return matchingRule.normalizeAttributeValue(attributeValue);
+ }
+ catch (final DecodeException de)
+ {
+ // Ignore - we'll drop back to the user provided value.
+ }
+ }
+ return attributeValue;
+ }
+
+
+
+ private StringBuilder toNormalizedString(StringBuilder builder)
+ {
+ return toString(builder, true);
+ }
+
+
+
+ private StringBuilder toString(StringBuilder builder)
+ {
+ return toString(builder, false);
+ }
+
+
+
+ private StringBuilder toString(StringBuilder builder,
+ boolean normalize)
+ {
+ final ByteString value = normalize ? getNormalizeValue()
+ : attributeValue;
+
+ if (!attributeType.getNames().iterator().hasNext())
+ {
+ builder.append(attributeType.getOID());
+ builder.append("=#");
+ StaticUtils.toHex(value, builder);
+ }
+ else
+ {
+ final String name = attributeType.getNameOrOID();
+ if (normalize)
+ {
+ // Normalizing.
+ StaticUtils.toLowerCase(name, builder);
+ }
+ else
+ {
+ builder.append(name);
+ }
+
+ builder.append("=");
+
+ final Syntax syntax = attributeType.getSyntax();
+ if (!syntax.isHumanReadable())
+ {
+ builder.append("#");
+ StaticUtils.toHex(value, builder);
+ }
+ else
+ {
+ final String str = value.toString();
+ char c;
+ for (int si = 0; si < str.length(); si++)
+ {
+ c = str.charAt(si);
+ if (c == ' ' || c == '#' || c == '"' || c == '+'
+ || c == ',' || c == ';' || c == '<' || c == '='
+ || c == '>' || c == '\\' || c == '\u0000')
+ {
+ builder.append('\\');
+ }
+ builder.append(c);
+ }
+ }
+ }
+ return builder;
+ }
+ }
+
+
+
+ private static final char[] SPECIAL_CHARS = new char[] { '\"', '+',
+ ',', ';', '<', '>', ' ', '#', '=', '\\' };
+
+ private static final char[] DELIMITER_CHARS = new char[] { '+', ',',
+ ';' };
+
+ private static final char[] DQUOTE_CHAR = new char[] { '\"' };
+
+ private static final Comparator<AVA> ATV_COMPARATOR = new Comparator<AVA>()
+ {
+ public int compare(AVA o1, AVA o2)
+ {
+ return o1.getAttributeType().compareTo(o2.getAttributeType());
+ }
+ };
+
+
+
+ /**
+ * Parses the provided LDAP string representation of an RDN using the
+ * default schema.
+ *
+ * @param rdn
+ * The LDAP string representation of a RDN.
+ * @return The parsed RDN.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code rdn} is not a valid LDAP string representation
+ * of a RDN.
+ * @throws NullPointerException
+ * If {@code rdn} was {@code null}.
+ */
+ public static RDN valueOf(String rdn)
+ throws LocalizedIllegalArgumentException
+ {
+ return valueOf(rdn, Schema.getDefaultSchema());
+ }
+
+
+
+ /**
+ * Parses the provided LDAP string representation of a RDN using the
+ * provided schema.
+ *
+ * @param rdn
+ * The LDAP string representation of a RDN.
+ * @param schema
+ * The schema to use when parsing the RDN.
+ * @return The parsed RDN.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code rdn} is not a valid LDAP string representation
+ * of a RDN.
+ * @throws NullPointerException
+ * If {@code rdn} or {@code schema} was {@code null}.
+ */
+ public static RDN valueOf(String rdn, Schema schema)
+ throws LocalizedIllegalArgumentException
+ {
+ final SubstringReader reader = new SubstringReader(rdn);
+ try
+ {
+ return decode(rdn, reader, schema);
+ }
+ catch (final UnknownSchemaElementException e)
+ {
+ final Message message = ERR_RDN_TYPE_NOT_FOUND.get(rdn, e
+ .getMessageObject());
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ }
+
+
+
+ private static AVA readAttributeTypeAndValue(SubstringReader reader,
+ Schema schema) throws LocalizedIllegalArgumentException,
+ UnknownSchemaElementException
+ {
+ // Skip over any spaces at the beginning.
+ reader.skipWhitespaces();
+
+ final AttributeType attribute = readDNAttributeName(reader, schema);
+
+ // Make sure that we're not at the end of the DN string because
+ // that would be invalid.
+ if (reader.remaining() == 0)
+ {
+ final Message message = ERR_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME
+ .get(reader.getString(), attribute.getNameOrOID());
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // The next character must be an equal sign. If it is not, then
+ // that's an error.
+ char c;
+ if ((c = reader.read()) != '=')
+ {
+ final Message message = ERR_ATTR_SYNTAX_DN_NO_EQUAL.get(reader
+ .getString(), attribute.getNameOrOID(), c);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // Skip over any spaces after the equal sign.
+ reader.skipWhitespaces();
+
+ // Parse the value for this RDN component.
+ final ByteString value = readDNAttributeValue(reader);
+
+ return new AVA(attribute, value);
+ }
+
+
+
+ private static AttributeType readDNAttributeName(
+ SubstringReader reader, Schema schema)
+ throws LocalizedIllegalArgumentException,
+ UnknownSchemaElementException
+ {
+ int length = 1;
+ reader.mark();
+
+ // The next character must be either numeric (for an OID) or
+ // alphabetic (for
+ // an attribute description).
+ char c = reader.read();
+ if (isDigit(c))
+ {
+ boolean lastWasPeriod = false;
+ do
+ {
+ if (c == '.')
+ {
+ if (lastWasPeriod)
+ {
+ final Message message = ERR_ATTR_SYNTAX_OID_CONSECUTIVE_PERIODS
+ .get(reader.getString(), reader.pos() - 1);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ else
+ {
+ lastWasPeriod = true;
+ }
+ }
+ else if (!isDigit(c))
+ {
+ // This must have been an illegal character.
+ final Message message = ERR_ATTR_SYNTAX_OID_ILLEGAL_CHARACTER
+ .get(reader.getString(), reader.pos() - 1);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ else
+ {
+ lastWasPeriod = false;
+ }
+ length++;
+ } while ((c = reader.read()) != '=');
+ }
+ if (isAlpha(c))
+ {
+ // This must be an attribute description. In this case, we will
+ // only
+ // accept alphabetic characters, numeric digits, and the hyphen.
+ while ((c = reader.read()) != '=')
+ {
+ if (length == 0 && !isAlpha(c))
+ {
+ // This is an illegal character.
+ final Message message = ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR
+ .get(reader.getString(), c, reader.pos() - 1);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ if (!isAlpha(c) && !isDigit(c) && c != '-')
+ {
+ // This is an illegal character.
+ final Message message = ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR
+ .get(reader.getString(), c, reader.pos() - 1);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ length++;
+ }
+ }
+ else
+ {
+ final Message message = ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR.get(
+ reader.getString(), c, reader.pos() - 1);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ reader.reset();
+
+ // Return the position of the first non-space character after the
+ // token.
+
+ return schema.getAttributeType(reader.read(length));
+ }
+
+
+
+ private static ByteString readDNAttributeValue(SubstringReader reader)
+ throws LocalizedIllegalArgumentException
+ {
+ // All leading spaces have already been stripped so we can start
+ // reading the value. However, it may be empty so check for that.
+ if (reader.remaining() == 0)
+ {
+ return ByteString.empty();
+ }
+
+ reader.mark();
+
+ // Look at the first character. If it is an octothorpe (#), then
+ // that means that the value should be a hex string.
+ char c = reader.read();
+ int length = 0;
+ if (c == '#')
+ {
+ // The first two characters must be hex characters.
+ reader.mark();
+ if (reader.remaining() < 2)
+ {
+ final Message message = ERR_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT
+ .get(reader.getString());
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ for (int i = 0; i < 2; i++)
+ {
+ c = reader.read();
+ if (isHexDigit(c))
+ {
+ length++;
+ }
+ else
+ {
+ final Message message = ERR_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT
+ .get(reader.getString(), c);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ }
+
+ // The rest of the value must be a multiple of two hex
+ // characters. The end of the value may be designated by the
+ // end of the DN, a comma or semicolon, or a space.
+ while (reader.remaining() > 0)
+ {
+ c = reader.read();
+ if (isHexDigit(c))
+ {
+ length++;
+
+ if (reader.remaining() > 0)
+ {
+ c = reader.read();
+ if (isHexDigit(c))
+ {
+ length++;
+ }
+ else
+ {
+ final Message message = ERR_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT
+ .get(reader.getString(), c);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ }
+ else
+ {
+ final Message message = ERR_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT
+ .get(reader.getString());
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ }
+ else if ((c == ' ') || (c == ',') || (c == ';'))
+ {
+ // This denotes the end of the value.
+ break;
+ }
+ else
+ {
+ final Message message = ERR_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT
+ .get(reader.getString(), c);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ }
+
+ // At this point, we should have a valid hex string. Convert it
+ // to a byte array and set that as the value of the provided
+ // octet string.
+ try
+ {
+ reader.reset();
+ return ByteString
+ .wrap(hexStringToByteArray(reader.read(length)));
+ }
+ catch (final Exception e)
+ {
+ final Message message = ERR_ATTR_SYNTAX_DN_ATTR_VALUE_DECODE_FAILURE
+ .get(reader.getString(), String.valueOf(e));
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ }
+
+ // If the first character is a quotation mark, then the value
+ // should continue until the corresponding closing quotation mark.
+ else if (c == '"')
+ {
+ try
+ {
+ return StaticUtils.evaluateEscapes(reader, DQUOTE_CHAR, false);
+ }
+ catch (final DecodeException e)
+ {
+ throw new LocalizedIllegalArgumentException(e
+ .getMessageObject());
+ }
+ }
+
+ // Otherwise, use general parsing to find the end of the value.
+ else
+ {
+ reader.reset();
+ ByteString bytes;
+ try
+ {
+ bytes = StaticUtils.evaluateEscapes(reader, SPECIAL_CHARS,
+ DELIMITER_CHARS, true);
+ }
+ catch (final DecodeException e)
+ {
+ throw new LocalizedIllegalArgumentException(e
+ .getMessageObject());
+ }
+ if (bytes.length() == 0)
+ {
+ // We don't allow an empty attribute value.
+ final Message message = ERR_ATTR_SYNTAX_DN_INVALID_REQUIRES_ESCAPE_CHAR
+ .get(reader.getString(), reader.pos());
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ return bytes;
+ }
+ }
+
+
+
+ // FIXME: ensure that the decoded RDN does not contain multiple AVAs
+ // with the same type.
+ static RDN decode(String rdnString, SubstringReader reader,
+ Schema schema) throws LocalizedIllegalArgumentException,
+ UnknownSchemaElementException
+ {
+ final AVA firstAVA = readAttributeTypeAndValue(reader, schema);
+
+ // Skip over any spaces that might be after the attribute value.
+ reader.skipWhitespaces();
+
+ reader.mark();
+ if (reader.remaining() > 0 && reader.read() == '+')
+ {
+ final List<AVA> avas = new ArrayList<AVA>();
+ avas.add(firstAVA);
+
+ do
+ {
+ avas.add(readAttributeTypeAndValue(reader, schema));
+
+ // Skip over any spaces that might be after the attribute value.
+ reader.skipWhitespaces();
+
+ reader.mark();
+ } while (reader.read() == '+');
+
+ reader.reset();
+ return new RDN(avas.toArray(new AVA[avas.size()]), rdnString);
+ }
+ else
+ {
+ reader.reset();
+ return new RDN(new AVA[] { firstAVA }, rdnString);
+ }
+ }
+
+
+
+ // In original order.
+ private final AVA[] avas;
+
+ // We need to store the original string value if provided in order to
+ // preserve the original whitespace.
+ private String stringValue;
+
+
+
+ private RDN(AVA[] avas, String stringValue)
+ {
+ this.avas = avas;
+ this.stringValue = stringValue;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int compareTo(RDN rdn)
+ {
+ final int sz1 = avas.length;
+ final int sz2 = rdn.avas.length;
+
+ if (sz1 != sz2)
+ {
+ return sz1 - sz2;
+ }
+
+ if (sz1 == 1)
+ {
+ return avas[0].compareTo(rdn.avas[0]);
+ }
+
+ // Need to sort the AVAs before comparing.
+ final AVA[] a1 = new AVA[sz1];
+ System.arraycopy(avas, 0, a1, 0, sz1);
+ Arrays.sort(a1, ATV_COMPARATOR);
+
+ final AVA[] a2 = new AVA[sz1];
+ System.arraycopy(rdn.avas, 0, a2, 0, sz1);
+ Arrays.sort(a2, ATV_COMPARATOR);
+
+ for (int i = 0; i < sz1; i++)
+ {
+ final int result = a1[i].compareTo(a2[i]);
+ if (result != 0)
+ {
+ return result;
+ }
+ }
+
+ return 0;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ else if (obj instanceof RDN)
+ {
+ return compareTo((RDN) obj) == 0;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+
+ /**
+ * Returns the attribute value contained in this RDN which is
+ * associated with the provided attribute type, or {@code null} if
+ * this RDN does not include such an attribute value.
+ *
+ * @param attributeType
+ * The attribute type.
+ * @return The attribute value.
+ */
+ public ByteString getAttributeValue(AttributeType attributeType)
+ {
+ for (final AVA ava : avas)
+ {
+ if (ava.getAttributeType().equals(attributeType))
+ {
+ return ava.getAttributeValue();
+ }
+ }
+ return null;
+ }
+
+
+
+ /**
+ * Returns the first AVA contained in this RDN.
+ *
+ * @return The first AVA contained in this RDN.
+ */
+ public AVA getFirstAVA()
+ {
+ return avas[0];
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int hashCode()
+ {
+ // Avoid an algorithm that requires the AVAs to be sorted.
+ int hash = 0;
+ for (int i = 0; i < avas.length; i++)
+ {
+ hash += avas[i].hashCode();
+ }
+ return hash;
+ }
+
+
+
+ /**
+ * Returns {@code true} if this RDN contains more than one AVA.
+ *
+ * @return {@code true} if this RDN contains more than one AVA,
+ * otherwise {@code false}.
+ */
+ public boolean isMultiValued()
+ {
+ return avas.length > 1;
+ }
+
+
+
+ /**
+ * Returns an iterator of the AVAs contained in this RDN. The AVAs
+ * will be returned in the user provided order.
+ * <p>
+ * Attempts to remove AVAs using an iterator's {@code remove()} method
+ * are not permitted and will result in an {@code
+ * UnsupportedOperationException} being thrown.
+ *
+ * @return An iterator of the AVAs contained in this RDN.
+ */
+ public Iterator<AVA> iterator()
+ {
+ return Iterators.arrayIterator(avas);
+ }
+
+
+
+ /**
+ * Returns the number of AVAs in this RDN.
+ *
+ * @return The number of AVAs in this RDN.
+ */
+ public int size()
+ {
+ return avas.length;
+ }
+
+
+
+ /**
+ * Returns the RFC 4514 string representation of this RDN.
+ *
+ * @return The RFC 4514 string representation of this RDN.
+ * @see <a href="http://tools.ietf.org/html/rfc4514">RFC 4514 -
+ * Lightweight Directory Access Protocol (LDAP): String
+ * Representation of Distinguished Names </a>
+ */
+ public String toString()
+ {
+ // We don't care about potential race conditions here.
+ if (stringValue == null)
+ {
+ final StringBuilder builder = new StringBuilder();
+ avas[0].toString(builder);
+ for (int i = 1; i < avas.length; i++)
+ {
+ builder.append(',');
+ avas[i].toString(builder);
+ }
+ stringValue = builder.toString();
+ }
+ return stringValue;
+ }
+
+
+
+ StringBuilder toNormalizedString(StringBuilder builder)
+ {
+ final int sz = avas.length;
+ if (sz == 1)
+ {
+ return avas[0].toNormalizedString(builder);
+ }
+ else
+ {
+ // Need to sort the AVAs before comparing.
+ final AVA[] a = new AVA[sz];
+ System.arraycopy(avas, 0, a, 0, sz);
+ Arrays.sort(a, ATV_COMPARATOR);
+
+ a[0].toString(builder);
+ for (int i = 1; i < sz; i++)
+ {
+ builder.append(',');
+ a[i].toNormalizedString(builder);
+ }
+
+ return builder;
+ }
+ }
+
+
+
+ StringBuilder toString(StringBuilder builder)
+ {
+ return builder.append(toString());
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ResultCode.java b/sdk/src/org/opends/sdk/ResultCode.java
new file mode 100644
index 0000000..c8d7cb0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ResultCode.java
@@ -0,0 +1,764 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import static org.opends.messages.CoreMessages.*;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.opends.messages.Message;
+
+
+
+/**
+ * An operation result code as defined in RFC 4511 section 4.1.9 is used
+ * to indicate the final status of an operation. If a server detects
+ * multiple errors for an operation, only one result code is returned.
+ * The server should return the result code that best indicates the
+ * nature of the error encountered. Servers may return substituted
+ * result codes to prevent unauthorized disclosures.
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc4511#section-4.1.9">RFC
+ * 4511 - Lightweight Directory Access Protocol (LDAP): The
+ * Protocol </a>
+ */
+public final class ResultCode
+{
+ private static final ResultCode[] ELEMENTS = new ResultCode[16655];
+
+ private static final List<ResultCode> IMMUTABLE_ELEMENTS = Collections
+ .unmodifiableList(Arrays.asList(ELEMENTS));
+
+ /**
+ * The result code that indicates that the operation completed
+ * successfully.
+ */
+ public static final ResultCode SUCCESS = registerSuccessResultCode(0,
+ INFO_RESULT_SUCCESS.get());
+
+ /**
+ * The result code that indicates that an internal error prevented the
+ * operation from being processed properly.
+ */
+ public static final ResultCode OPERATIONS_ERROR = registerErrorResultCode(
+ 1, INFO_RESULT_OPERATIONS_ERROR.get());
+
+ /**
+ * The result code that indicates that the client sent a malformed or
+ * illegal request to the server.
+ */
+ public static final ResultCode PROTOCOL_ERROR = registerErrorResultCode(
+ 2, INFO_RESULT_PROTOCOL_ERROR.get());
+
+ /**
+ * The result code that indicates that a time limit was exceeded while
+ * attempting to process the request.
+ */
+ public static final ResultCode TIME_LIMIT_EXCEEDED = registerErrorResultCode(
+ 3, INFO_RESULT_TIME_LIMIT_EXCEEDED.get());
+
+ /**
+ * The result code that indicates that a size limit was exceeded while
+ * attempting to process the request.
+ */
+ public static final ResultCode SIZE_LIMIT_EXCEEDED = registerErrorResultCode(
+ 4, INFO_RESULT_SIZE_LIMIT_EXCEEDED.get());
+
+ /**
+ * The result code that indicates that the attribute value assertion
+ * included in a compare request did not match the targeted entry.
+ */
+ public static final ResultCode COMPARE_FALSE = registerSuccessResultCode(
+ 5, INFO_RESULT_COMPARE_FALSE.get());
+
+ /**
+ * The result code that indicates that the attribute value assertion
+ * included in a compare request did match the targeted entry.
+ */
+ public static final ResultCode COMPARE_TRUE = registerSuccessResultCode(
+ 6, INFO_RESULT_COMPARE_TRUE.get());
+
+ /**
+ * The result code that indicates that the requested authentication
+ * attempt failed because it referenced an invalid SASL mechanism.
+ */
+ public static final ResultCode AUTH_METHOD_NOT_SUPPORTED = registerErrorResultCode(
+ 7, INFO_RESULT_AUTH_METHOD_NOT_SUPPORTED.get());
+
+ /**
+ * The result code that indicates that the requested operation could
+ * not be processed because it requires that the client has completed
+ * a strong form of authentication.
+ */
+ public static final ResultCode STRONG_AUTH_REQUIRED = registerErrorResultCode(
+ 8, INFO_RESULT_STRONG_AUTH_REQUIRED.get());
+
+ /**
+ * The result code that indicates that a referral was encountered.
+ * <p>
+ * Strictly speaking this result code should not be exceptional since
+ * it is considered as a "success" response. However, referrals should
+ * occur rarely in practice and, when they do occur, should not be
+ * ignored since the application may believe that a request has
+ * succeeded when, in fact, nothing was done.
+ */
+ public static final ResultCode REFERRAL = registerErrorResultCode(10,
+ INFO_RESULT_REFERRAL.get());
+
+ /**
+ * The result code that indicates that processing on the requested
+ * operation could not continue because an administrative limit was
+ * exceeded.
+ */
+ public static final ResultCode ADMIN_LIMIT_EXCEEDED = registerErrorResultCode(
+ 11, INFO_RESULT_ADMIN_LIMIT_EXCEEDED.get());
+
+ /**
+ * The result code that indicates that the requested operation failed
+ * because it included a critical extension that is unsupported or
+ * inappropriate for that request.
+ */
+ public static final ResultCode UNAVAILABLE_CRITICAL_EXTENSION = registerErrorResultCode(
+ 12, INFO_RESULT_UNAVAILABLE_CRITICAL_EXTENSION.get());
+
+ /**
+ * The result code that indicates that the requested operation could
+ * not be processed because it requires confidentiality for the
+ * communication between the client and the server.
+ */
+ public static final ResultCode CONFIDENTIALITY_REQUIRED = registerErrorResultCode(
+ 13, INFO_RESULT_CONFIDENTIALITY_REQUIRED.get());
+
+ /**
+ * The result code that should be used for intermediate responses in
+ * multi-stage SASL bind operations.
+ */
+ public static final ResultCode SASL_BIND_IN_PROGRESS = registerSuccessResultCode(
+ 14, INFO_RESULT_SASL_BIND_IN_PROGRESS.get());
+
+ /**
+ * The result code that indicates that the requested operation failed
+ * because it targeted an attribute or attribute value that did not
+ * exist in the specified entry.
+ */
+ public static final ResultCode NO_SUCH_ATTRIBUTE = registerErrorResultCode(
+ 16, INFO_RESULT_NO_SUCH_ATTRIBUTE.get());
+
+ /**
+ * The result code that indicates that the requested operation failed
+ * because it referenced an attribute that is not defined in the
+ * server schema.
+ */
+ public static final ResultCode UNDEFINED_ATTRIBUTE_TYPE = registerErrorResultCode(
+ 17, INFO_RESULT_UNDEFINED_ATTRIBUTE_TYPE.get());
+
+ /**
+ * The result code that indicates that the requested operation failed
+ * because it attempted to perform an inappropriate type of matching
+ * against an attribute.
+ */
+ public static final ResultCode INAPPROPRIATE_MATCHING = registerErrorResultCode(
+ 18, INFO_RESULT_INAPPROPRIATE_MATCHING.get());
+
+ /**
+ * The result code that indicates that the requested operation failed
+ * because it would have violated some constraint defined in the
+ * server.
+ */
+ public static final ResultCode CONSTRAINT_VIOLATION = registerErrorResultCode(
+ 19, INFO_RESULT_CONSTRAINT_VIOLATION.get());
+
+ /**
+ * The result code that indicates that the requested operation failed
+ * because it would have resulted in a conflict with an existing
+ * attribute or attribute value in the target entry.
+ */
+ public static final ResultCode ATTRIBUTE_OR_VALUE_EXISTS = registerErrorResultCode(
+ 20, INFO_RESULT_ATTRIBUTE_OR_VALUE_EXISTS.get());
+
+ /**
+ * The result code that indicates that the requested operation failed
+ * because it violated the syntax for a specified attribute.
+ */
+ public static final ResultCode INVALID_ATTRIBUTE_SYNTAX = registerErrorResultCode(
+ 21, INFO_RESULT_INVALID_ATTRIBUTE_SYNTAX.get());
+
+ /**
+ * The result code that indicates that the requested operation failed
+ * because it referenced an entry that does not exist.
+ */
+ public static final ResultCode NO_SUCH_OBJECT = registerErrorResultCode(
+ 32, INFO_RESULT_NO_SUCH_OBJECT.get());
+
+ /**
+ * The result code that indicates that the requested operation failed
+ * because it attempted to perform an illegal operation on an alias.
+ */
+ public static final ResultCode ALIAS_PROBLEM = registerErrorResultCode(
+ 33, INFO_RESULT_ALIAS_PROBLEM.get());
+
+ /**
+ * The result code that indicates that the requested operation failed
+ * because it would have resulted in an entry with an invalid or
+ * malformed DN.
+ */
+ public static final ResultCode INVALID_DN_SYNTAX = registerErrorResultCode(
+ 34, INFO_RESULT_INVALID_DN_SYNTAX.get());
+
+ /**
+ * The result code that indicates that a problem was encountered while
+ * attempting to dereference an alias for a search operation.
+ */
+ public static final ResultCode ALIAS_DEREFERENCING_PROBLEM = registerErrorResultCode(
+ 36, INFO_RESULT_ALIAS_DEREFERENCING_PROBLEM.get());
+
+ /**
+ * The result code that indicates that an authentication attempt
+ * failed because the requested type of authentication was not
+ * appropriate for the targeted entry.
+ */
+ public static final ResultCode INAPPROPRIATE_AUTHENTICATION = registerErrorResultCode(
+ 48, INFO_RESULT_INAPPROPRIATE_AUTHENTICATION.get());
+
+ /**
+ * The result code that indicates that an authentication attempt
+ * failed because the user did not provide a valid set of credentials.
+ */
+ public static final ResultCode INVALID_CREDENTIALS = registerErrorResultCode(
+ 49, INFO_RESULT_INVALID_CREDENTIALS.get());
+
+ /**
+ * The result code that indicates that the client does not have
+ * sufficient permission to perform the requested operation.
+ */
+ public static final ResultCode INSUFFICIENT_ACCESS_RIGHTS = registerErrorResultCode(
+ 50, INFO_RESULT_INSUFFICIENT_ACCESS_RIGHTS.get());
+
+ /**
+ * The result code that indicates that the server is too busy to
+ * process the requested operation.
+ */
+ public static final ResultCode BUSY = registerErrorResultCode(51,
+ INFO_RESULT_BUSY.get());
+
+ /**
+ * The result code that indicates that either the entire server or one
+ * or more required resources were not available for use in processing
+ * the request.
+ */
+ public static final ResultCode UNAVAILABLE = registerErrorResultCode(
+ 52, INFO_RESULT_UNAVAILABLE.get());
+
+ /**
+ * The result code that indicates that the server is unwilling to
+ * perform the requested operation.
+ */
+ public static final ResultCode UNWILLING_TO_PERFORM = registerErrorResultCode(
+ 53, INFO_RESULT_UNWILLING_TO_PERFORM.get());
+
+ /**
+ * The result code that indicates that a referral or chaining loop was
+ * detected while processing the request.
+ */
+ public static final ResultCode LOOP_DETECT = registerErrorResultCode(
+ 54, INFO_RESULT_LOOP_DETECT.get());
+
+ /**
+ * The result code that indicates that a search request included a VLV
+ * request control without a server-side sort control.
+ */
+ public static final ResultCode SORT_CONTROL_MISSING = registerErrorResultCode(
+ 60, INFO_RESULT_SORT_CONTROL_MISSING.get());
+
+ /**
+ * The result code that indicates that a search request included a VLV
+ * request control with an invalid offset.
+ */
+ public static final ResultCode OFFSET_RANGE_ERROR = registerErrorResultCode(
+ 61, INFO_RESULT_OFFSET_RANGE_ERROR.get());
+
+ /**
+ * The result code that indicates that the requested operation failed
+ * because it would have violated the server's naming configuration.
+ */
+ public static final ResultCode NAMING_VIOLATION = registerErrorResultCode(
+ 64, INFO_RESULT_NAMING_VIOLATION.get());
+
+ /**
+ * The result code that indicates that the requested operation failed
+ * because it would have resulted in an entry that violated the server
+ * schema.
+ */
+ public static final ResultCode OBJECTCLASS_VIOLATION = registerErrorResultCode(
+ 65, INFO_RESULT_OBJECTCLASS_VIOLATION.get());
+
+ /**
+ * The result code that indicates that the requested operation is not
+ * allowed for non-leaf entries.
+ */
+ public static final ResultCode NOT_ALLOWED_ON_NONLEAF = registerErrorResultCode(
+ 66, INFO_RESULT_NOT_ALLOWED_ON_NONLEAF.get());
+
+ /**
+ * The result code that indicates that the requested operation is not
+ * allowed on an RDN attribute.
+ */
+ public static final ResultCode NOT_ALLOWED_ON_RDN = registerErrorResultCode(
+ 67, INFO_RESULT_NOT_ALLOWED_ON_RDN.get());
+
+ /**
+ * The result code that indicates that the requested operation failed
+ * because it would have resulted in an entry that conflicts with an
+ * entry that already exists.
+ */
+ public static final ResultCode ENTRY_ALREADY_EXISTS = registerErrorResultCode(
+ 68, INFO_RESULT_ENTRY_ALREADY_EXISTS.get());
+
+ /**
+ * The result code that indicates that the operation could not be
+ * processed because it would have modified the objectclasses
+ * associated with an entry in an illegal manner.
+ */
+ public static final ResultCode OBJECTCLASS_MODS_PROHIBITED = registerErrorResultCode(
+ 69, INFO_RESULT_OBJECTCLASS_MODS_PROHIBITED.get());
+
+ /**
+ * The result code that indicates that the operation could not be
+ * processed because it would impact multiple DSAs or other
+ * repositories.
+ */
+ public static final ResultCode AFFECTS_MULTIPLE_DSAS = registerErrorResultCode(
+ 71, INFO_RESULT_AFFECTS_MULTIPLE_DSAS.get());
+
+ /**
+ * The result code that indicates that the operation could not be
+ * processed because there was an error while processing the virtual
+ * list view control.
+ */
+ public static final ResultCode VIRTUAL_LIST_VIEW_ERROR = registerErrorResultCode(
+ 76, INFO_RESULT_VIRTUAL_LIST_VIEW_ERROR.get());
+
+ /**
+ * The result code that should be used if no other result code is
+ * appropriate.
+ */
+ public static final ResultCode OTHER = registerErrorResultCode(80,
+ INFO_RESULT_OTHER.get());
+
+ /**
+ * The client-side result code that indicates that a
+ * previously-established connection to the server was lost. This is
+ * for client-side use only and should never be transferred over
+ * protocol.
+ */
+ public static final ResultCode CLIENT_SIDE_SERVER_DOWN = registerErrorResultCode(
+ 81, INFO_RESULT_CLIENT_SIDE_SERVER_DOWN.get());
+
+ /**
+ * The client-side result code that indicates that a local error
+ * occurred that had nothing to do with interaction with the server.
+ * This is for client-side use only and should never be transferred
+ * over protocol.
+ */
+ public static final ResultCode CLIENT_SIDE_LOCAL_ERROR = registerErrorResultCode(
+ 82, INFO_RESULT_CLIENT_SIDE_LOCAL_ERROR.get());
+
+ /**
+ * The client-side result code that indicates that an error occurred
+ * while encoding a request to send to the server. This is for
+ * client-side use only and should never be transferred over protocol.
+ */
+ public static final ResultCode CLIENT_SIDE_ENCODING_ERROR = registerErrorResultCode(
+ 83, INFO_RESULT_CLIENT_SIDE_ENCODING_ERROR.get());
+
+ /**
+ * The client-side result code that indicates that an error occurred
+ * while decoding a response from the server. This is for client-side
+ * use only and should never be transferred over protocol.
+ */
+ public static final ResultCode CLIENT_SIDE_DECODING_ERROR = registerErrorResultCode(
+ 84, INFO_RESULT_CLIENT_SIDE_DECODING_ERROR.get());
+
+ /**
+ * The client-side result code that indicates that the client did not
+ * receive an expected response in a timely manner. This is for
+ * client-side use only and should never be transferred over protocol.
+ */
+ public static final ResultCode CLIENT_SIDE_TIMEOUT = registerErrorResultCode(
+ 85, INFO_RESULT_CLIENT_SIDE_TIMEOUT.get());
+
+ /**
+ * The client-side result code that indicates that the user requested
+ * an unknown or unsupported authentication mechanism. This is for
+ * client-side use only and should never be transferred over protocol.
+ */
+ public static final ResultCode CLIENT_SIDE_AUTH_UNKNOWN = registerErrorResultCode(
+ 86, INFO_RESULT_CLIENT_SIDE_AUTH_UNKNOWN.get());
+
+ /**
+ * The client-side result code that indicates that the filter provided
+ * by the user was malformed and could not be parsed. This is for
+ * client-side use only and should never be transferred over protocol.
+ */
+ public static final ResultCode CLIENT_SIDE_FILTER_ERROR = registerErrorResultCode(
+ 87, INFO_RESULT_CLIENT_SIDE_FILTER_ERROR.get());
+
+ /**
+ * The client-side result code that indicates that the user cancelled
+ * an operation. This is for client-side use only and should never be
+ * transferred over protocol.
+ */
+ public static final ResultCode CLIENT_SIDE_USER_CANCELLED = registerErrorResultCode(
+ 88, INFO_RESULT_CLIENT_SIDE_USER_CANCELLED.get());
+
+ /**
+ * The client-side result code that indicates that there was a problem
+ * with one or more of the parameters provided by the user. This is
+ * for client-side use only and should never be transferred over
+ * protocol.
+ */
+ public static final ResultCode CLIENT_SIDE_PARAM_ERROR = registerErrorResultCode(
+ 89, INFO_RESULT_CLIENT_SIDE_PARAM_ERROR.get());
+
+ /**
+ * The client-side result code that indicates that the client
+ * application was not able to allocate enough memory for the
+ * requested operation. This is for client-side use only and should
+ * never be transferred over protocol.
+ */
+ public static final ResultCode CLIENT_SIDE_NO_MEMORY = registerErrorResultCode(
+ 90, INFO_RESULT_CLIENT_SIDE_NO_MEMORY.get());
+
+ /**
+ * The client-side result code that indicates that the client was not
+ * able to establish a connection to the server. This is for
+ * client-side use only and should never be transferred over protocol.
+ */
+ public static final ResultCode CLIENT_SIDE_CONNECT_ERROR = registerErrorResultCode(
+ 91, INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get());
+
+ /**
+ * The client-side result code that indicates that the user requested
+ * an operation that is not supported. This is for client-side use
+ * only and should never be transferred over protocol.
+ */
+ public static final ResultCode CLIENT_SIDE_NOT_SUPPORTED = registerErrorResultCode(
+ 92, INFO_RESULT_CLIENT_SIDE_NOT_SUPPORTED.get());
+
+ /**
+ * The client-side result code that indicates that the client expected
+ * a control to be present in the response from the server but it was
+ * not included. This is for client-side use only and should never be
+ * transferred over protocol.
+ */
+ public static final ResultCode CLIENT_SIDE_CONTROL_NOT_FOUND = registerErrorResultCode(
+ 93, INFO_RESULT_CLIENT_SIDE_CONTROL_NOT_FOUND.get());
+
+ /**
+ * The client-side result code that indicates that the server did not
+ * return any results for a search operation that was expected to
+ * match at least one entry. This is for client-side use only and
+ * should never be transferred over protocol.
+ */
+ public static final ResultCode CLIENT_SIDE_NO_RESULTS_RETURNED = registerErrorResultCode(
+ 94, INFO_RESULT_CLIENT_SIDE_NO_RESULTS_RETURNED.get());
+
+ /**
+ * The client-side result code that indicates that the server has
+ * returned more matching entries for a search operation than have
+ * been processed so far. This is for client-side use only and should
+ * never be transferred over protocol.
+ */
+ public static final ResultCode CLIENT_SIDE_MORE_RESULTS_TO_RETURN = registerErrorResultCode(
+ 95, INFO_RESULT_CLIENT_SIDE_MORE_RESULTS_TO_RETURN.get());
+
+ /**
+ * The client-side result code that indicates that the client detected
+ * a referral loop caused by servers referencing each other in a
+ * circular manner. This is for client-side use only and should never
+ * be transferred over protocol.
+ */
+ public static final ResultCode CLIENT_SIDE_CLIENT_LOOP = registerErrorResultCode(
+ 96, INFO_RESULT_CLIENT_SIDE_CLIENT_LOOP.get());
+
+ /**
+ * The client-side result code that indicates that the client reached
+ * the maximum number of hops allowed when attempting to follow a
+ * referral (i.e., following one referral resulted in another referral
+ * which resulted in another referral and so on). This is for
+ * client-side use only and should never be transferred over protocol.
+ */
+ public static final ResultCode CLIENT_SIDE_REFERRAL_LIMIT_EXCEEDED = registerErrorResultCode(
+ 97, INFO_RESULT_CLIENT_SIDE_REFERRAL_LIMIT_EXCEEDED.get());
+
+ /**
+ * The result code that indicates that a cancel request was
+ * successful, or that the specified operation was canceled.
+ */
+ public static final ResultCode CANCELED = registerErrorResultCode(
+ 118, INFO_RESULT_CANCELED.get());
+
+ /**
+ * The result code that indicates that a cancel request was
+ * unsuccessful because the targeted operation did not exist or had
+ * already completed.
+ */
+ public static final ResultCode NO_SUCH_OPERATION = registerErrorResultCode(
+ 119, INFO_RESULT_NO_SUCH_OPERATION.get());
+
+ /**
+ * The result code that indicates that a cancel request was
+ * unsuccessful because processing on the targeted operation had
+ * already reached a point at which it could not be canceled.
+ */
+ public static final ResultCode TOO_LATE = registerErrorResultCode(
+ 120, INFO_RESULT_TOO_LATE.get());
+
+ /**
+ * The result code that indicates that a cancel request was
+ * unsuccessful because the targeted operation was one that could not
+ * be canceled.
+ */
+ public static final ResultCode CANNOT_CANCEL = registerErrorResultCode(
+ 121, INFO_RESULT_CANNOT_CANCEL.get());
+
+ /**
+ * The result code that indicates that the filter contained in an
+ * assertion control failed to match the target entry.
+ */
+ public static final ResultCode ASSERTION_FAILED = registerErrorResultCode(
+ 122, INFO_RESULT_ASSERTION_FAILED.get());
+
+ /**
+ * The result code that should be used if the server will not allow
+ * the client to use the requested authorization.
+ */
+ public static final ResultCode AUTHORIZATION_DENIED = registerErrorResultCode(
+ 123, INFO_RESULT_AUTHORIZATION_DENIED.get());
+
+ /**
+ * The result code that should be used if the server did not actually
+ * complete processing on the associated operation because the request
+ * included the LDAP No-Op control.
+ */
+ public static final ResultCode NO_OPERATION = registerErrorResultCode(
+ 16654, INFO_RESULT_NO_OPERATION.get());
+
+
+
+ /**
+ * Creates and registers a new error result code with the application.
+ *
+ * @param intValue
+ * The integer value of the error result code as defined in
+ * RFC 4511 section 4.1.9.
+ * @param name
+ * The name of the error result code.
+ * @return The new error result code.
+ */
+ private static ResultCode registerErrorResultCode(int intValue,
+ Message name)
+ {
+ ResultCode t = new ResultCode(intValue, name, true);
+ ELEMENTS[intValue] = t;
+ return t;
+ }
+
+
+
+ /**
+ * Creates and registers a new success result code with the
+ * application.
+ *
+ * @param intValue
+ * The integer value of the success result code as defined in
+ * RFC 4511 section 4.1.9.
+ * @param name
+ * The name of the success result code.
+ * @return The new success result code.
+ */
+ private static ResultCode registerSuccessResultCode(int intValue,
+ Message name)
+ {
+ ResultCode t = new ResultCode(intValue, name, false);
+ ELEMENTS[intValue] = t;
+ return t;
+ }
+
+
+
+ /**
+ * Returns the result code having the specified integer value as
+ * defined in RFC 4511 section 4.1.9. If there is no result code
+ * associated with the specified integer value then a temporary result
+ * code is automatically created in order to handle cases where
+ * unexpected result codes are returned from the server.
+ *
+ * @param intValue
+ * The integer value of the result code.
+ * @return The result code.
+ */
+ public static ResultCode valueOf(int intValue)
+ {
+ ResultCode resultCode = null;
+
+ if (intValue >= 0 || intValue < ELEMENTS.length)
+ {
+ resultCode = ELEMENTS[intValue];
+ }
+
+ if (resultCode == null)
+ {
+ resultCode = new ResultCode(intValue, Message.raw("undefined("
+ + intValue + ")"), true);
+ }
+
+ return resultCode;
+ }
+
+
+
+ /**
+ * Returns an unmodifiable list containing the set of available result
+ * codes indexed on their integer value as defined in RFC 4511 section
+ * 4.1.9.
+ *
+ * @return An unmodifiable list containing the set of available result
+ * codes.
+ */
+ public static List<ResultCode> values()
+ {
+ return IMMUTABLE_ELEMENTS;
+ }
+
+
+
+ private final int intValue;
+
+ private final Message name;
+
+ private final boolean exceptional;
+
+
+
+ // Prevent direct instantiation.
+ private ResultCode(int intValue, Message name, boolean exceptional)
+ {
+ this.intValue = intValue;
+ this.name = name;
+ this.exceptional = exceptional;
+ }
+
+
+
+ /**
+ * Indicates whether or not this result code represents an error
+ * result. In order to make it easier for application to detect
+ * referrals, the {@code REFERRAL} result code is treated as an error
+ * result (the LDAP RFCs treat referrals as a success response).
+ *
+ * @return {@code true} if this result code represents an error
+ * result, otherwise {@code false}.
+ */
+ public boolean isExceptional()
+ {
+ return exceptional;
+ }
+
+
+
+ /**
+ * Returns the short human-readable name of this result code.
+ *
+ * @return The short human-readable name of this result code.
+ */
+ public Message getName()
+ {
+ return name;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ else if (obj instanceof ResultCode)
+ {
+ return this.intValue == ((ResultCode) obj).intValue;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int hashCode()
+ {
+ return intValue;
+ }
+
+
+
+ /**
+ * Returns the integer value of this result code.
+ *
+ * @return The integer value of this result code.
+ */
+ public int intValue()
+ {
+ return intValue;
+ }
+
+
+
+ /**
+ * Returns the string representation of this result code.
+ *
+ * @return The string representation of this result code.
+ */
+ public String toString()
+ {
+ return name.toString();
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/ResultFuture.java b/sdk/src/org/opends/sdk/ResultFuture.java
new file mode 100644
index 0000000..63a8a09
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ResultFuture.java
@@ -0,0 +1,153 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.opends.sdk.responses.Result;
+
+
+
+/**
+ * A handle which can be used to retrieve the Result of an asynchronous
+ * Request.
+ *
+ * @param <S>
+ * The type of result returned by this future.
+ */
+public interface ResultFuture<S extends Result> extends Future<S>
+{
+ /**
+ * Attempts to cancel the request. This attempt will fail if the
+ * request has already completed or has already been cancelled. If
+ * successful, then cancellation results in an abandon or cancel
+ * request (if configured) being sent to the server.
+ * <p>
+ * After this method returns, subsequent calls to {@link #isDone} will
+ * always return {@code true}. Subsequent calls to
+ * {@link #isCancelled} will always return {@code true} if this method
+ * returned {@code true}.
+ *
+ * @param mayInterruptIfRunning
+ * {@code true} if the thread executing executing the
+ * response handler should be interrupted; otherwise,
+ * in-progress response handlers are allowed to complete.
+ * @return {@code false} if the request could not be cancelled,
+ * typically because it has already completed normally;
+ * {@code true} otherwise.
+ */
+ boolean cancel(boolean mayInterruptIfRunning);
+
+
+
+ /**
+ * Waits if necessary for the request to complete, and then returns
+ * the result if the request succeeded. If the request failed (i.e. a
+ * non-successful result code was obtained) then the result is thrown
+ * as an {@link ErrorResultException}.
+ *
+ * @return The result, but only if the result code indicates that the
+ * request succeeded.
+ * @throws CancellationException
+ * If the request was cancelled using a call to
+ * {@link #cancel}.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ */
+ S get() throws InterruptedException, ErrorResultException;
+
+
+
+ /**
+ * Waits if necessary for at most the given time for the request to
+ * complete, and then returns the result if the request succeeded. If
+ * the request failed (i.e. a non-successful result code was obtained)
+ * then the result is thrown as an {@link ErrorResultException}.
+ *
+ * @param timeout
+ * The maximum time to wait.
+ * @param unit
+ * The time unit of the timeout argument.
+ * @return The result, but only if the result code indicates that the
+ * request succeeded.
+ * @throws CancellationException
+ * If the request was cancelled using a call to
+ * {@link #cancel}.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws TimeoutException
+ * If the wait timed out.
+ */
+ S get(long timeout, TimeUnit unit) throws InterruptedException,
+ TimeoutException, ErrorResultException;
+
+
+
+ /**
+ * Returns the message ID of the request.
+ *
+ * @return The message ID.
+ */
+ int getMessageID();
+
+
+
+ /**
+ * Returns {@code true} if the request was cancelled before it
+ * completed normally.
+ *
+ * @return {@code true} if the request was cancelled before it
+ * completed normally, otherwise {@code false}.
+ */
+ boolean isCancelled();
+
+
+
+ /**
+ * Returns {@code true} if the request has completed.
+ * <p>
+ * Completion may be due to normal termination, an exception, or
+ * cancellation. In all of these cases, this method will return
+ * {@code true}.
+ *
+ * @return {@code true} if the request has completed, otherwise
+ * {@code false}.
+ */
+ boolean isDone();
+}
diff --git a/sdk/src/org/opends/sdk/ResultHandler.java b/sdk/src/org/opends/sdk/ResultHandler.java
new file mode 100644
index 0000000..8433352
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ResultHandler.java
@@ -0,0 +1,81 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import org.opends.sdk.responses.Result;
+
+
+
+/**
+ * A completion handler for consuming the result of an asynchronous
+ * operation.
+ * <p>
+ * {@link Connection} objects allow a result completion handler to be
+ * specified when sending operation requests to a Directory Server. The
+ * {@link #handleResult} method is invoked when the operation completes
+ * successfully. The {@link #handleErrorResult} method is invoked if the
+ * operation fails.
+ * <p>
+ * Implementations of these methods should complete in a timely manner
+ * so as to avoid keeping the invoking thread from dispatching to other
+ * completion handlers.
+ *
+ * @param <S>
+ * The type of result handled by this result handler.
+ * @param <P>
+ * The type of the additional parameter to this handler's
+ * methods. Use {@link java.lang.Void} for visitors that do not
+ * need an additional parameter.
+ */
+public interface ResultHandler<S extends Result, P>
+{
+ /**
+ * Invoked when the asynchronous operation has failed.
+ *
+ * @param p
+ * A handler specified parameter.
+ * @param error
+ * The error result exception indicating why the asynchronous
+ * operation has failed.
+ */
+ void handleErrorResult(P p, ErrorResultException error);
+
+
+
+ /**
+ * Invoked when the asynchronous operation has completed successfully.
+ *
+ * @param p
+ * A handler specified parameter.
+ * @param result
+ * The result of the asynchronous operation.
+ */
+ void handleResult(P p, S result);
+}
diff --git a/sdk/src/org/opends/sdk/RootDSE.java b/sdk/src/org/opends/sdk/RootDSE.java
new file mode 100644
index 0000000..697d3d6
--- /dev/null
+++ b/sdk/src/org/opends/sdk/RootDSE.java
@@ -0,0 +1,463 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.opends.sdk.responses.SearchResultEntry;
+import org.opends.sdk.schema.SchemaNotFoundException;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Functions;
+import org.opends.sdk.util.Iterables;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Root DSE Entry.
+ */
+public class RootDSE extends AbstractEntry
+{
+ private static final AttributeDescription ATTR_ALT_SERVER = AttributeDescription
+ .valueOf("altServer");
+
+ private static final AttributeDescription ATTR_NAMING_CONTEXTS = AttributeDescription
+ .valueOf("namingContexts");
+
+ private static final AttributeDescription ATTR_SUPPORTED_CONTROL = AttributeDescription
+ .valueOf("supportedControl");
+
+ private static final AttributeDescription ATTR_SUPPORTED_EXTENSION = AttributeDescription
+ .valueOf("supportedExtension");
+
+ private static final AttributeDescription ATTR_SUPPORTED_FEATURE = AttributeDescription
+ .valueOf("supportedFeatures");
+
+ private static final AttributeDescription ATTR_SUPPORTED_LDAP_VERSION = AttributeDescription
+ .valueOf("supportedLDAPVersion");
+
+ private static final AttributeDescription ATTR_SUPPORTED_SASL_MECHANISMS = AttributeDescription
+ .valueOf("supportedSASLMechanisms");
+
+ private static final AttributeDescription ATTR_SUPPORTED_AUTH_PASSWORD_SCHEMES = AttributeDescription
+ .valueOf("supportedAuthPasswordSchemes");
+
+ private static final AttributeDescription ATTR_VENDOR_NAME = AttributeDescription
+ .valueOf("vendorName");
+
+ private static final AttributeDescription ATTR_VENDOR_VERSION = AttributeDescription
+ .valueOf("vendorVersion");
+
+ private static String[] ROOTDSE_ATTRS = new String[] {
+ ATTR_ALT_SERVER.toString(), ATTR_NAMING_CONTEXTS.toString(),
+ ATTR_SUPPORTED_CONTROL.toString(),
+ ATTR_SUPPORTED_EXTENSION.toString(),
+ ATTR_SUPPORTED_FEATURE.toString(),
+ ATTR_SUPPORTED_LDAP_VERSION.toString(),
+ ATTR_SUPPORTED_SASL_MECHANISMS.toString(),
+ ATTR_VENDOR_NAME.toString(), ATTR_VENDOR_VERSION.toString(),
+ ATTR_SUPPORTED_AUTH_PASSWORD_SCHEMES.toString(), "*" };
+
+ private final Entry entry;
+
+ private final Iterable<String> altServers;
+
+ private final Iterable<DN> namingContexts;
+
+ private final Iterable<String> supportedControls;
+
+ private final Iterable<String> supportedExtensions;
+
+ private final Iterable<String> supportedFeatures;
+
+ private final Iterable<Integer> supportedLDAPVerions;
+
+ private final Iterable<String> supportedSASLMechanisms;
+
+ private final Iterable<String> supportedAuthPasswordSchemes;
+
+ private final String vendorName;
+
+ private final String vendorVersion;
+
+
+
+ private RootDSE(Entry entry) throws IllegalArgumentException
+ {
+ this.entry = Types.unmodifiableEntry(entry);
+
+ Attribute attr = getAttribute(ATTR_ALT_SERVER);
+ if (attr == null)
+ {
+ altServers = Collections.emptyList();
+ }
+ else
+ {
+ altServers = Iterables.unmodifiable(Iterables.transform(attr,
+ Functions.valueToString()));
+ }
+
+ attr = getAttribute(ATTR_NAMING_CONTEXTS);
+ if (attr == null)
+ {
+ namingContexts = Collections.emptyList();
+ }
+ else
+ {
+ namingContexts = Iterables.unmodifiable(Iterables.transform(attr,
+ Functions.valueToDN()));
+ }
+
+ attr = getAttribute(ATTR_SUPPORTED_CONTROL);
+ if (attr == null)
+ {
+ supportedControls = Collections.emptyList();
+ }
+ else
+ {
+ supportedControls = Iterables.unmodifiable(Iterables.transform(
+ attr, Functions.valueToString()));
+ }
+
+ attr = getAttribute(ATTR_SUPPORTED_EXTENSION);
+ if (attr == null)
+ {
+ supportedExtensions = Collections.emptyList();
+ }
+ else
+ {
+ supportedExtensions = Iterables.unmodifiable(Iterables.transform(
+ attr, Functions.valueToString()));
+ }
+
+ attr = getAttribute(ATTR_SUPPORTED_FEATURE);
+ if (attr == null)
+ {
+ supportedFeatures = Collections.emptyList();
+ }
+ else
+ {
+ supportedFeatures = Iterables.unmodifiable(Iterables.transform(
+ attr, Functions.valueToString()));
+ }
+
+ attr = getAttribute(ATTR_SUPPORTED_LDAP_VERSION);
+ if (attr == null)
+ {
+ supportedLDAPVerions = Collections.emptyList();
+ }
+ else
+ {
+ supportedLDAPVerions = Iterables.unmodifiable(Iterables
+ .transform(attr, Functions.valueToInteger()));
+ }
+
+ attr = getAttribute(ATTR_SUPPORTED_SASL_MECHANISMS);
+ if (attr == null)
+ {
+ supportedSASLMechanisms = Collections.emptyList();
+ }
+ else
+ {
+ supportedSASLMechanisms = Iterables.unmodifiable(Iterables
+ .transform(attr, Functions.valueToString()));
+ }
+
+ attr = getAttribute(ATTR_SUPPORTED_AUTH_PASSWORD_SCHEMES);
+ if (attr == null)
+ {
+ supportedAuthPasswordSchemes = Collections.emptyList();
+ }
+ else
+ {
+ supportedAuthPasswordSchemes = Iterables.unmodifiable(Iterables
+ .transform(attr, Functions.valueToString()));
+ }
+
+ attr = getAttribute(ATTR_VENDOR_NAME);
+ vendorName = attr == null ? "" : attr.firstValueAsString();
+
+ attr = getAttribute(ATTR_VENDOR_VERSION);
+ vendorVersion = attr == null ? "" : attr.firstValueAsString();
+ }
+
+
+
+ public static RootDSE getRootDSE(Connection connection)
+ throws ErrorResultException, InterruptedException,
+ DecodeException, SchemaNotFoundException
+ {
+ SearchResultEntry result = connection.readEntry(DN.rootDN(),
+ ROOTDSE_ATTRS);
+ return new RootDSE(result);
+ }
+
+
+
+ public Iterable<String> getAltServers()
+ {
+ return altServers;
+ }
+
+
+
+ public Iterable<DN> getNamingContexts()
+ {
+ return namingContexts;
+ }
+
+
+
+ public Iterable<String> getSupportedControls()
+ {
+ return supportedControls;
+ }
+
+
+
+ public boolean supportsControl(String oid)
+ {
+ Validator.ensureNotNull(oid);
+ for (String supported : supportedControls)
+ {
+ if (supported.equals(oid))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+
+ public Iterable<String> getSupportedExtendedOperations()
+ {
+ return supportedExtensions;
+ }
+
+
+
+ public boolean supportsExtendedOperation(String oid)
+ {
+ Validator.ensureNotNull(oid);
+ for (String supported : supportedExtensions)
+ {
+ if (supported.equals(oid))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+
+ public Iterable<String> getSupportedFeatures()
+ {
+ return supportedFeatures;
+ }
+
+
+
+ public boolean supportsFeature(String oid)
+ {
+ Validator.ensureNotNull(oid);
+ for (String supported : supportedFeatures)
+ {
+ if (supported.equals(oid))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+
+ public Iterable<Integer> getSupportedLDAPVersions()
+ {
+ return supportedLDAPVerions;
+ }
+
+
+
+ public boolean supportsLDAPVersion(int version)
+ {
+ for (int supported : supportedLDAPVerions)
+ {
+ if (supported == version)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+
+ public Iterable<String> getSupportedSASLMechanismNames()
+ {
+ return supportedSASLMechanisms;
+ }
+
+
+
+ public boolean supportsSASLMechanism(String name)
+ {
+ Validator.ensureNotNull(name);
+ for (String supported : supportedSASLMechanisms)
+ {
+ if (supported.equals(name))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+
+ public Iterable<String> getSupportedAuthPasswordSchemes()
+ {
+ return supportedSASLMechanisms;
+ }
+
+
+
+ public boolean supportsAuthPasswordScheme(String name)
+ {
+ Validator.ensureNotNull(name);
+ for (String supported : supportedAuthPasswordSchemes)
+ {
+ if (supported.equals(name))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+
+ public String getVendorName()
+ {
+ return vendorName;
+ }
+
+
+
+ public String getVendorVersion()
+ {
+ return vendorVersion;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean addAttribute(Attribute attribute,
+ Collection<ByteString> duplicateValues)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ public Entry clearAttributes() throws UnsupportedOperationException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ public boolean containsAttribute(
+ AttributeDescription attributeDescription)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(attributeDescription);
+
+ return entry.containsAttribute(attributeDescription);
+ }
+
+
+
+ public Attribute getAttribute(
+ AttributeDescription attributeDescription)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(attributeDescription);
+
+ return entry.getAttribute(attributeDescription);
+ }
+
+
+
+ public int getAttributeCount()
+ {
+ return entry.getAttributeCount();
+ }
+
+
+
+ public Iterable<Attribute> getAttributes()
+ {
+ return entry.getAttributes();
+ }
+
+
+
+ public DN getName()
+ {
+ return DN.rootDN();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean removeAttribute(Attribute attribute,
+ Collection<ByteString> missingValues)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ public Entry setName(DN dn) throws UnsupportedOperationException,
+ NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/SearchResultHandler.java b/sdk/src/org/opends/sdk/SearchResultHandler.java
new file mode 100644
index 0000000..653e8c6
--- /dev/null
+++ b/sdk/src/org/opends/sdk/SearchResultHandler.java
@@ -0,0 +1,82 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import org.opends.sdk.responses.SearchResultEntry;
+import org.opends.sdk.responses.SearchResultReference;
+
+
+
+/**
+ * A completion handler for consuming the results of an asynchronous
+ * Search operation.
+ * <p>
+ * {@link Connection} objects allow a search result completion handler
+ * to be specified when sending Search operation requests to a Directory
+ * Server. The {@link #handleEntry} method is invoked each time a Search
+ * Result Entry is returned from the Directory Server. The
+ * {@link #handleReference} method is invoked for each Search Result
+ * Reference returned from the Directory Server.
+ * <p>
+ * Implementations of these methods should complete in a timely manner
+ * so as to avoid keeping the invoking thread from dispatching to other
+ * completion handlers.
+ *
+ * @param <P>
+ * The type of the additional parameter to this handler's
+ * methods. Use {@link java.lang.Void} for visitors that do not
+ * need an additional parameter.
+ */
+public interface SearchResultHandler<P>
+{
+ /**
+ * Invoked each time a search result entry is returned from an
+ * asynchronous search operation.
+ *
+ * @param p
+ * A handler specified parameter.
+ * @param entry
+ * The search result entry.
+ */
+ void handleEntry(P p, SearchResultEntry entry);
+
+
+
+ /**
+ * Invoked each time a search result reference is returned from an
+ * asynchronous search operation.
+ *
+ * @param p
+ * A handler specified parameter.
+ * @param reference
+ * The search result reference.
+ */
+ void handleReference(P p, SearchResultReference reference);
+}
diff --git a/sdk/src/org/opends/sdk/SearchScope.java b/sdk/src/org/opends/sdk/SearchScope.java
new file mode 100644
index 0000000..7f02192
--- /dev/null
+++ b/sdk/src/org/opends/sdk/SearchScope.java
@@ -0,0 +1,207 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+
+
+/**
+ * A Search operation search scope as defined in RFC 4511 section
+ * 4.5.1.2 is used to specify the scope of a Search operation.
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc4511#section-4.5.1.2">RFC
+ * 4511 - Lightweight Directory Access Protocol (LDAP): The
+ * Protocol </a>
+ * @see <a
+ * href="http://tools.ietf.org/html/draft-sermersheim-ldap-subordinate-scope">
+ * draft-sermersheim-ldap-subordinate-scope - Subordinate Subtree
+ * Search Scope for LDAP </a>
+ */
+public final class SearchScope
+{
+ private static final SearchScope[] ELEMENTS = new SearchScope[4];
+
+ private static final List<SearchScope> IMMUTABLE_ELEMENTS = Collections
+ .unmodifiableList(Arrays.asList(ELEMENTS));
+
+ /**
+ * The scope is constrained to the search base entry.
+ */
+ public static final SearchScope BASE_OBJECT = register(0, "base");
+
+ /**
+ * The scope is constrained to the immediate subordinates of the
+ * search base entry.
+ */
+ public static final SearchScope SINGLE_LEVEL = register(1, "one");
+
+ /**
+ * The scope is constrained to the search base entry and to all its
+ * subordinates.
+ */
+ public static final SearchScope WHOLE_SUBTREE = register(2, "sub");
+
+ /**
+ * The scope is constrained to all the subordinates of the search base
+ * entry, but does not include the search base entry itself (as
+ * wholeSubtree does).
+ */
+ public static final SearchScope SUBORDINATES = register(3,
+ "subordinates");
+
+
+
+ /**
+ * Creates and registers a new search scope with the application.
+ *
+ * @param intValue
+ * The integer value of the search scope as defined in RFC
+ * 4511 section 4.5.1.2.
+ * @param name
+ * The name of the search scope as defined in RFC 4516.
+ * @return The new search scope.
+ */
+ private static SearchScope register(int intValue, String name)
+ {
+ SearchScope t = new SearchScope(intValue, name);
+ ELEMENTS[intValue] = t;
+ return t;
+ }
+
+
+
+ /**
+ * Returns the search scope having the specified integer value as
+ * defined in RFC 4511 section 4.5.1.2.
+ *
+ * @param intValue
+ * The integer value of the search scope.
+ * @return The search scope, or {@code null} if there was no search
+ * scope associated with {@code intValue}.
+ */
+ public static SearchScope valueOf(int intValue)
+ {
+ if (intValue < 0 || intValue >= ELEMENTS.length)
+ {
+ return null;
+ }
+ return ELEMENTS[intValue];
+ }
+
+
+
+ /**
+ * Returns an unmodifiable list containing the set of available search
+ * scopes indexed on their integer value as defined in RFC 4511
+ * section 4.5.1.2.
+ *
+ * @return An unmodifiable list containing the set of available search
+ * scopes.
+ */
+ public static List<SearchScope> values()
+ {
+ return IMMUTABLE_ELEMENTS;
+ }
+
+
+
+ private final int intValue;
+
+ private final String name;
+
+
+
+ // Prevent direct instantiation.
+ private SearchScope(int intValue, String name)
+ {
+ this.intValue = intValue;
+ this.name = name;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ else if (obj instanceof SearchScope)
+ {
+ return this.intValue == ((SearchScope) obj).intValue;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int hashCode()
+ {
+ return intValue;
+ }
+
+
+
+ /**
+ * Returns the integer value of this search scope as defined in RFC
+ * 4511 section 4.5.1.2.
+ *
+ * @return The integer value of this search scope.
+ */
+ public int intValue()
+ {
+ return intValue;
+ }
+
+
+
+ /**
+ * Returns the string representation of this search scope as defined
+ * in RFC 4516.
+ *
+ * @return The string representation of this search scope.
+ */
+ public String toString()
+ {
+ return name;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/SortedEntry.java b/sdk/src/org/opends/sdk/SortedEntry.java
new file mode 100644
index 0000000..5f48d0f
--- /dev/null
+++ b/sdk/src/org/opends/sdk/SortedEntry.java
@@ -0,0 +1,295 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.Collection;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.opends.sdk.requests.Requests;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * An implementation of the {@code Entry} interface which uses a {@code
+ * SortedMap} for storing attributes. Attributes are returned in
+ * ascending order of attribute description, with {@code objectClass}
+ * first, then all user attributes, and finally any operational
+ * attributes. All operations are supported by this implementation.
+ */
+public final class SortedEntry extends AbstractEntry
+{
+ private final SortedMap<AttributeDescription, Attribute> attributes = new TreeMap<AttributeDescription, Attribute>();
+
+ private DN name;
+
+
+
+ /**
+ * Creates an empty sorted entry and an empty (root) distinguished
+ * name.
+ */
+ public SortedEntry()
+ {
+ this(DN.rootDN());
+ }
+
+
+
+ /**
+ * Creates an empty sorted entry using the provided distinguished
+ * name.
+ *
+ * @param name
+ * The distinguished name of this entry.
+ * @throws NullPointerException
+ * If {@code name} was {@code null}.
+ */
+ public SortedEntry(DN name) throws NullPointerException
+ {
+ Validator.ensureNotNull(name);
+ this.name = name;
+ }
+
+
+
+ /**
+ * Creates an empty sorted entry using the provided distinguished name
+ * decoded using the default schema.
+ *
+ * @param name
+ * The distinguished name of this entry.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code name} could not be decoded using the default
+ * schema.
+ * @throws NullPointerException
+ * If {@code name} was {@code null}.
+ */
+ public SortedEntry(String name)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ this(DN.valueOf(name));
+ }
+
+
+
+ /**
+ * Creates a sorted entry having the same distinguished name,
+ * attributes, and object classes of the provided entry.
+ *
+ * @param entry
+ * The entry to be copied.
+ * @throws NullPointerException
+ * If {@code entry} was {@code null}.
+ */
+ public SortedEntry(Entry entry)
+ {
+ Validator.ensureNotNull(entry);
+
+ this.name = entry.getName();
+ for (Attribute attribute : entry.getAttributes())
+ {
+ addAttribute(attribute);
+ }
+ }
+
+
+
+ /**
+ * Creates a new sorted entry using the provided lines of LDIF decoded
+ * using the default schema.
+ *
+ * @param ldifLines
+ * Lines of LDIF containing the an LDIF add change record or
+ * an LDIF entry record.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code ldifLines} was empty, or contained invalid
+ * LDIF, or could not be decoded using the default schema.
+ * @throws NullPointerException
+ * If {@code ldifLines} was {@code null} .
+ */
+ public SortedEntry(String... ldifLines)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ this(Requests.newAddRequest(ldifLines));
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean addAttribute(Attribute attribute,
+ Collection<ByteString> duplicateValues)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(attribute);
+
+ if (!attribute.isEmpty())
+ {
+ AttributeDescription attributeDescription = attribute
+ .getAttributeDescription();
+ Attribute oldAttribute = attributes.get(attributeDescription);
+ if (oldAttribute != null)
+ {
+ return oldAttribute.addAll(attribute, duplicateValues);
+ }
+ else
+ {
+ attributes.put(attributeDescription, attribute);
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Entry clearAttributes()
+ {
+ attributes.clear();
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean containsAttribute(
+ AttributeDescription attributeDescription)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(attributeDescription);
+
+ return attributes.containsKey(attributeDescription);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Attribute getAttribute(
+ AttributeDescription attributeDescription)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(attributeDescription);
+
+ return attributes.get(attributeDescription);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int getAttributeCount()
+ {
+ return attributes.size();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterable<Attribute> getAttributes()
+ {
+ return attributes.values();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public DN getName()
+ {
+ return name;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean removeAttribute(Attribute attribute,
+ Collection<ByteString> missingValues) throws NullPointerException
+ {
+ Validator.ensureNotNull(attribute);
+
+ AttributeDescription attributeDescription = attribute
+ .getAttributeDescription();
+
+ if (attribute.isEmpty())
+ {
+ return attributes.remove(attributeDescription) != null;
+ }
+ else
+ {
+ Attribute oldAttribute = attributes.get(attributeDescription);
+ if (oldAttribute != null)
+ {
+ boolean modified = oldAttribute.removeAll(attribute,
+ missingValues);
+ if (oldAttribute.isEmpty())
+ {
+ attributes.remove(attributeDescription);
+ return true;
+ }
+ return modified;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Entry setName(DN dn) throws NullPointerException
+ {
+ Validator.ensureNotNull(dn);
+ this.name = dn;
+ return this;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/SynchronousConnection.java b/sdk/src/org/opends/sdk/SynchronousConnection.java
new file mode 100644
index 0000000..b648053
--- /dev/null
+++ b/sdk/src/org/opends/sdk/SynchronousConnection.java
@@ -0,0 +1,261 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import org.opends.sdk.requests.*;
+import org.opends.sdk.responses.BindResult;
+import org.opends.sdk.responses.CompareResult;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * A {@code SynchronousConnection} adapts an {@code
+ * AsynchronousConnection} into a synchronous {@code Connection}.
+ */
+public class SynchronousConnection extends AbstractConnection
+{
+ private final AsynchronousConnection connection;
+
+
+
+ /**
+ * Creates a new abstract connection which will route all synchronous
+ * requests to the provided asynchronous connection.
+ *
+ * @param connection
+ * The asynchronous connection to be used.
+ * @throws NullPointerException
+ * If {@code connection} was {@code null}.
+ */
+ public SynchronousConnection(AsynchronousConnection connection)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(connection);
+ this.connection = connection;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Result add(AddRequest request) throws ErrorResultException,
+ InterruptedException, UnsupportedOperationException,
+ IllegalStateException, NullPointerException
+ {
+ ResultFuture<Result> future = connection.add(request, null, null);
+ try
+ {
+ return future.get();
+ }
+ finally
+ {
+ // Cancel the request if it hasn't completed.
+ future.cancel(false);
+ }
+ }
+
+
+
+ public void addConnectionEventListener(
+ ConnectionEventListener listener) throws IllegalStateException,
+ NullPointerException
+ {
+ connection.addConnectionEventListener(listener);
+ }
+
+
+
+ public BindResult bind(BindRequest request)
+ throws ErrorResultException, InterruptedException,
+ UnsupportedOperationException, IllegalStateException,
+ NullPointerException
+ {
+ ResultFuture<BindResult> future = connection.bind(request, null,
+ null);
+ try
+ {
+ return future.get();
+ }
+ finally
+ {
+ // Cancel the request if it hasn't completed.
+ future.cancel(false);
+ }
+ }
+
+
+
+ public void close()
+ {
+ connection.close();
+ }
+
+
+
+ public void close(UnbindRequest request) throws NullPointerException
+ {
+ connection.close(request);
+ }
+
+
+
+ public CompareResult compare(CompareRequest request)
+ throws ErrorResultException, InterruptedException,
+ UnsupportedOperationException, IllegalStateException,
+ NullPointerException
+ {
+ ResultFuture<CompareResult> future = connection.compare(request,
+ null, null);
+ try
+ {
+ return future.get();
+ }
+ finally
+ {
+ // Cancel the request if it hasn't completed.
+ future.cancel(false);
+ }
+ }
+
+
+
+ public Result delete(DeleteRequest request)
+ throws ErrorResultException, InterruptedException,
+ UnsupportedOperationException, IllegalStateException,
+ NullPointerException
+ {
+ ResultFuture<Result> future = connection
+ .delete(request, null, null);
+ try
+ {
+ return future.get();
+ }
+ finally
+ {
+ // Cancel the request if it hasn't completed.
+ future.cancel(false);
+ }
+ }
+
+
+
+ public <R extends Result> R extendedRequest(ExtendedRequest<R> request)
+ throws ErrorResultException, InterruptedException,
+ UnsupportedOperationException, IllegalStateException,
+ NullPointerException
+ {
+ ResultFuture<R> future = connection.extendedRequest(request, null,
+ null);
+ try
+ {
+ return future.get();
+ }
+ finally
+ {
+ // Cancel the request if it hasn't completed.
+ future.cancel(false);
+ }
+ }
+
+
+
+ public Result modify(ModifyRequest request)
+ throws ErrorResultException, InterruptedException,
+ UnsupportedOperationException, IllegalStateException,
+ NullPointerException
+ {
+ ResultFuture<Result> future = connection
+ .modify(request, null, null);
+ try
+ {
+ return future.get();
+ }
+ finally
+ {
+ // Cancel the request if it hasn't completed.
+ future.cancel(false);
+ }
+ }
+
+
+
+ public Result modifyDN(ModifyDNRequest request)
+ throws ErrorResultException, InterruptedException,
+ UnsupportedOperationException, IllegalStateException,
+ NullPointerException
+ {
+ ResultFuture<Result> future = connection.modifyDN(request, null,
+ null);
+ try
+ {
+ return future.get();
+ }
+ finally
+ {
+ // Cancel the request if it hasn't completed.
+ future.cancel(false);
+ }
+ }
+
+
+
+ public void removeConnectionEventListener(
+ ConnectionEventListener listener) throws NullPointerException
+ {
+ connection.removeConnectionEventListener(listener);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <P> Result search(SearchRequest request,
+ SearchResultHandler<P> handler, P p) throws ErrorResultException,
+ InterruptedException, UnsupportedOperationException,
+ IllegalStateException, NullPointerException
+ {
+ ResultFuture<Result> future = connection.search(request, null,
+ handler, p);
+ try
+ {
+ return future.get();
+ }
+ finally
+ {
+ // Cancel the request if it hasn't completed.
+ future.cancel(false);
+ }
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/Types.java b/sdk/src/org/opends/sdk/Types.java
new file mode 100644
index 0000000..b040dcf
--- /dev/null
+++ b/sdk/src/org/opends/sdk/Types.java
@@ -0,0 +1,985 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.opends.sdk.schema.AttributeType;
+import org.opends.sdk.schema.ObjectClass;
+import org.opends.sdk.util.*;
+
+
+
+/**
+ * This class contains methods for creating and manipulating attributes,
+ * entries, and other types of object.
+ */
+public final class Types
+{
+
+ /**
+ * Empty attribute.
+ */
+ private static final class EmptyAttribute extends AbstractAttribute
+ {
+
+ private final AttributeDescription attributeDescription;
+
+
+
+ private EmptyAttribute(AttributeDescription attributeDescription)
+ {
+ this.attributeDescription = attributeDescription;
+ }
+
+
+
+ public boolean add(ByteString value)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ public void clear() throws UnsupportedOperationException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ public AttributeDescription getAttributeDescription()
+ {
+ return attributeDescription;
+ }
+
+
+
+ public boolean isEmpty()
+ {
+ return true;
+ }
+
+
+
+ public Iterator<ByteString> iterator()
+ {
+ return Iterators.empty();
+ }
+
+
+
+ public int size()
+ {
+ return 0;
+ }
+
+
+
+ public boolean contains(Object value) throws NullPointerException
+ {
+ return false;
+ }
+
+
+
+ public boolean remove(Object value)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ }
+
+
+
+ /**
+ * Renamed attribute.
+ */
+ private static final class RenamedAttribute implements Attribute
+ {
+
+ private final Attribute attribute;
+
+ private final AttributeDescription attributeDescription;
+
+
+
+ private RenamedAttribute(Attribute attribute,
+ AttributeDescription attributeDescription)
+ {
+ this.attribute = attribute;
+ this.attributeDescription = attributeDescription;
+ }
+
+
+
+ public boolean add(ByteString value)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ return attribute.add(value);
+ }
+
+
+
+ public boolean add(Object firstValue, Object... remainingValues)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ return attribute.add(firstValue, remainingValues);
+ }
+
+
+
+ public boolean addAll(Collection<? extends ByteString> values)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ return attribute.addAll(values);
+ }
+
+
+
+ public boolean addAll(Collection<? extends ByteString> values,
+ Collection<? super ByteString> duplicateValues)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ return attribute.addAll(values, duplicateValues);
+ }
+
+
+
+ public void clear() throws UnsupportedOperationException
+ {
+ attribute.clear();
+ }
+
+
+
+ public boolean contains(Object value) throws NullPointerException
+ {
+ return attribute.contains(value);
+ }
+
+
+
+ public boolean containsAll(Collection<?> values)
+ throws NullPointerException
+ {
+ return attribute.containsAll(values);
+ }
+
+
+
+ public boolean equals(Object object)
+ {
+ return AbstractAttribute.equals(this, object);
+ }
+
+
+
+ public ByteString firstValue() throws NoSuchElementException
+ {
+ return attribute.firstValue();
+ }
+
+
+
+ public <T> T firstValueAsObject(
+ Function<? super ByteString, T, Void> type)
+ throws NoSuchElementException
+ {
+ return attribute.firstValueAsObject(type);
+ }
+
+
+
+ public <T, P> T firstValueAsObject(
+ Function<? super ByteString, T, P> type, P p)
+ throws NoSuchElementException
+ {
+ return attribute.firstValueAsObject(type, p);
+ }
+
+
+
+ public String firstValueAsString() throws NoSuchElementException
+ {
+ return attribute.firstValueAsString();
+ }
+
+
+
+ public AttributeDescription getAttributeDescription()
+ {
+ return attributeDescription;
+ }
+
+
+
+ public String getAttributeDescriptionAsString()
+ {
+ return attributeDescription.toString();
+ }
+
+
+
+ public int hashCode()
+ {
+ return AbstractAttribute.hashCode(this);
+ }
+
+
+
+ public boolean isEmpty()
+ {
+ return attribute.isEmpty();
+ }
+
+
+
+ public Iterator<ByteString> iterator()
+ {
+ return attribute.iterator();
+ }
+
+
+
+ public boolean remove(Object value)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ return attribute.remove(value);
+ }
+
+
+
+ public boolean removeAll(Collection<?> values)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ return attribute.removeAll(values);
+ }
+
+
+
+ public <T> boolean removeAll(Collection<T> values,
+ Collection<? super T> missingValues)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ return attribute.removeAll(values, missingValues);
+ }
+
+
+
+ public boolean retainAll(Collection<?> values)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ return attribute.retainAll(values);
+ }
+
+
+
+ public <T> boolean retainAll(Collection<T> values,
+ Collection<? super T> missingValues)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ return attribute.retainAll(values, missingValues);
+ }
+
+
+
+ public int size()
+ {
+ return attribute.size();
+ }
+
+
+
+ public ByteString[] toArray()
+ {
+ return attribute.toArray();
+ }
+
+
+
+ public <T> T[] toArray(T[] array) throws ArrayStoreException,
+ NullPointerException
+ {
+ return attribute.toArray(array);
+ }
+
+
+
+ public String toString()
+ {
+ return AbstractAttribute.toString(this);
+ }
+
+ }
+
+
+
+ /**
+ * Unmodifiable attribute.
+ */
+ private static final class UnmodifiableAttribute implements Attribute
+ {
+
+ private final Attribute attribute;
+
+
+
+ private UnmodifiableAttribute(Attribute attribute)
+ {
+ this.attribute = attribute;
+ }
+
+
+
+ public boolean add(ByteString value)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ public boolean add(Object firstValue, Object... remainingValues)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ public boolean addAll(Collection<? extends ByteString> values)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ public boolean addAll(Collection<? extends ByteString> values,
+ Collection<? super ByteString> duplicateValues)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ public void clear() throws UnsupportedOperationException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ public boolean contains(Object value) throws NullPointerException
+ {
+ return attribute.contains(value);
+ }
+
+
+
+ public boolean containsAll(Collection<?> values)
+ throws NullPointerException
+ {
+ return attribute.containsAll(values);
+ }
+
+
+
+ public boolean equals(Object object)
+ {
+ return (object == this || attribute.equals(object));
+ }
+
+
+
+ public ByteString firstValue() throws NoSuchElementException
+ {
+ return attribute.firstValue();
+ }
+
+
+
+ public <T> T firstValueAsObject(
+ Function<? super ByteString, T, Void> type)
+ throws NoSuchElementException
+ {
+ return attribute.firstValueAsObject(type);
+ }
+
+
+
+ public <T, P> T firstValueAsObject(
+ Function<? super ByteString, T, P> type, P p)
+ throws NoSuchElementException
+ {
+ return attribute.firstValueAsObject(type, p);
+ }
+
+
+
+ public String firstValueAsString() throws NoSuchElementException
+ {
+ return attribute.firstValueAsString();
+ }
+
+
+
+ public AttributeDescription getAttributeDescription()
+ {
+ return attribute.getAttributeDescription();
+ }
+
+
+
+ public String getAttributeDescriptionAsString()
+ {
+ return attribute.getAttributeDescriptionAsString();
+ }
+
+
+
+ public int hashCode()
+ {
+ return attribute.hashCode();
+ }
+
+
+
+ public boolean isEmpty()
+ {
+ return attribute.isEmpty();
+ }
+
+
+
+ public Iterator<ByteString> iterator()
+ {
+ return Iterators.unmodifiable(attribute.iterator());
+ }
+
+
+
+ public boolean remove(Object value)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ public boolean removeAll(Collection<?> values)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ public <T> boolean removeAll(Collection<T> values,
+ Collection<? super T> missingValues)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ public boolean retainAll(Collection<?> values)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ public <T> boolean retainAll(Collection<T> values,
+ Collection<? super T> missingValues)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ public int size()
+ {
+ return attribute.size();
+ }
+
+
+
+ public ByteString[] toArray()
+ {
+ return attribute.toArray();
+ }
+
+
+
+ public <T> T[] toArray(T[] array) throws ArrayStoreException,
+ NullPointerException
+ {
+ return attribute.toArray(array);
+ }
+
+
+
+ public String toString()
+ {
+ return attribute.toString();
+ }
+
+ }
+
+
+
+ private static final class UnmodifiableEntry implements Entry
+ {
+ private final Entry entry;
+
+
+
+ private UnmodifiableEntry(Entry entry)
+ {
+ this.entry = entry;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean addAttribute(Attribute attribute,
+ Collection<ByteString> duplicateValues)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean addAttribute(Attribute attribute)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Entry addAttribute(String attributeDescription,
+ Object... values) throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ public Entry clearAttributes() throws UnsupportedOperationException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ public boolean containsAttribute(
+ AttributeDescription attributeDescription)
+ {
+ return entry.containsAttribute(attributeDescription);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean containsAttribute(String attributeDescription)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ return entry.containsAttribute(attributeDescription);
+ }
+
+
+
+ public boolean containsObjectClass(ObjectClass objectClass)
+ {
+ return entry.containsObjectClass(objectClass);
+ }
+
+
+
+ public boolean containsObjectClass(String objectClass)
+ {
+ return entry.containsObjectClass(objectClass);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean equals(Object object)
+ {
+ return (object == this || entry.equals(object));
+ }
+
+
+
+ public Iterable<Attribute> findAttributes(
+ AttributeDescription attributeDescription)
+ {
+ return Iterables.unmodifiable(Iterables.transform(entry
+ .findAttributes(attributeDescription),
+ UNMODIFIABLE_ATTRIBUTE_FUNCTION));
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterable<Attribute> findAttributes(
+ String attributeDescription)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ return Iterables.unmodifiable(Iterables.transform(entry
+ .findAttributes(attributeDescription),
+ UNMODIFIABLE_ATTRIBUTE_FUNCTION));
+ }
+
+
+
+ public Attribute getAttribute(
+ AttributeDescription attributeDescription)
+ {
+ Attribute attribute = entry.getAttribute(attributeDescription);
+ if (attribute != null)
+ {
+ return unmodifiableAttribute(attribute);
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Attribute getAttribute(String attributeDescription)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ Attribute attribute = entry.getAttribute(attributeDescription);
+ if (attribute != null)
+ {
+ return unmodifiableAttribute(attribute);
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+
+
+ public int getAttributeCount()
+ {
+ return entry.getAttributeCount();
+ }
+
+
+
+ public Iterable<Attribute> getAttributes()
+ {
+ return Iterables.unmodifiable(Iterables.transform(entry
+ .getAttributes(), UNMODIFIABLE_ATTRIBUTE_FUNCTION));
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public DN getName()
+ {
+ return entry.getName();
+ }
+
+
+
+ public Iterable<String> getObjectClasses()
+ {
+ return Iterables.unmodifiable(entry.getObjectClasses());
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int hashCode()
+ {
+ return entry.hashCode();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean removeAttribute(Attribute attribute,
+ Collection<ByteString> missingValues)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ public boolean removeAttribute(
+ AttributeDescription attributeDescription)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Entry removeAttribute(String attributeDescription)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Entry removeAttribute(String attributeDescription,
+ Object... values) throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean replaceAttribute(Attribute attribute)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Entry replaceAttribute(String attributeDescription,
+ Object... values) throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Entry setName(String dn)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ public Entry setName(DN dn) throws UnsupportedOperationException,
+ NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String toString()
+ {
+ return entry.toString();
+ }
+
+ }
+
+
+
+ private static final Function<Attribute, Attribute, Void> UNMODIFIABLE_ATTRIBUTE_FUNCTION = new Function<Attribute, Attribute, Void>()
+ {
+
+ public Attribute apply(Attribute value, Void p)
+ {
+ return unmodifiableAttribute(value);
+ }
+
+ };
+
+
+
+ /**
+ * Returns a read-only empty attribute having the specified attribute
+ * description.
+ *
+ * @param attributeDescription
+ * The attribute description.
+ * @return The empty attribute.
+ * @throws NullPointerException
+ * If {@code attributeDescription} was {@code null}.
+ */
+ public static final Attribute emptyAttribute(
+ AttributeDescription attributeDescription)
+ throws NullPointerException
+ {
+ return new EmptyAttribute(attributeDescription);
+ }
+
+
+
+ /**
+ * Returns a view of {@code attribute} having a different attribute
+ * description. All operations on the returned attribute
+ * "pass-through" to the underlying attribute.
+ *
+ * @param attribute
+ * The attribute to be renamed.
+ * @param attributeDescription
+ * The new attribute description for {@code attribute}, which
+ * must be compatible with {@code attribute}'s attribute
+ * description.
+ * @return A renamed view of {@code attribute}.
+ * @throws IllegalArgumentException
+ * If {@code attributeDescription} does not have the same
+ * attribute type as {@code attribute}'s attribute
+ * description.
+ * @throws NullPointerException
+ * If {@code attribute} or {@code attributeDescription} was
+ * {@code null}.
+ */
+ public static final Attribute renameAttribute(Attribute attribute,
+ AttributeDescription attributeDescription)
+ throws IllegalArgumentException, NullPointerException
+ {
+ AttributeType oldType = attribute.getAttributeDescription()
+ .getAttributeType();
+ AttributeType newType = attributeDescription.getAttributeType();
+
+ // We could relax a bit by ensuring that they are both compatible
+ // (e.g. one sub-type of another, or same equality matching rule,
+ // etc).
+ Validator.ensureTrue(oldType.equals(newType),
+ "Old and new attribute type are not the same");
+
+ return new RenamedAttribute(attribute, attributeDescription);
+ }
+
+
+
+ /**
+ * Returns a read-only view of {@code attribute}. Query operations on
+ * the returned attribute "read-through" to the underlying attribute,
+ * and attempts to modify the returned attribute either directly or
+ * indirectly via an iterator result in an {@code
+ * UnsupportedOperationException}.
+ *
+ * @param attribute
+ * The attribute for which a read-only view is to be
+ * returned.
+ * @return A read-only view of {@code attribute}.
+ * @throws NullPointerException
+ * If {@code attribute} was {@code null}.
+ */
+ public static final Attribute unmodifiableAttribute(
+ Attribute attribute) throws NullPointerException
+ {
+ return new UnmodifiableAttribute(attribute);
+ }
+
+
+
+ /**
+ * Returns a read-only view of {@code entry} and its attributes. Query
+ * operations on the returned entry and its attributes"read-through"
+ * to the underlying entry or attribute, and attempts to modify the
+ * returned entry and its attributes either directly or indirectly via
+ * an iterator result in an {@code UnsupportedOperationException}.
+ *
+ * @param entry
+ * The entry for which a read-only view is to be returned.
+ * @return A read-only view of {@code entry}.
+ * @throws NullPointerException
+ * If {@code entry} was {@code null}.
+ */
+ public static final Entry unmodifiableEntry(Entry entry)
+ throws NullPointerException
+ {
+ return new UnmodifiableEntry(entry);
+ }
+
+
+
+ // Prevent instantiation.
+ private Types()
+ {
+ // Nothing to do.
+ }
+}
diff --git a/sdk/src/org/opends/sdk/asn1/ASN1.java b/sdk/src/org/opends/sdk/asn1/ASN1.java
new file mode 100644
index 0000000..c263d88
--- /dev/null
+++ b/sdk/src/org/opends/sdk/asn1/ASN1.java
@@ -0,0 +1,221 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2008 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.asn1;
+
+
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.opends.sdk.util.*;
+
+
+/**
+ * This class contains various static factory methods for creating ASN.1
+ * readers and writers.
+ *
+ * @see ASN1Reader
+ * @see ASN1Writer
+ */
+public final class ASN1
+{
+
+ /**
+ * Returns an ASN.1 reader whose source is the provided byte array and
+ * having an unlimited maximum BER element size.
+ *
+ * @param array
+ * The byte array to use.
+ * @return The new ASN.1 reader.
+ */
+ public static ASN1Reader getReader(byte[] array)
+ {
+ return getReader(array, 0);
+ }
+
+
+
+ /**
+ * Returns an ASN.1 reader whose source is the provided byte array and
+ * having a user defined maximum BER element size.
+ *
+ * @param array
+ * The byte array to use.
+ * @param maxElementSize
+ * The maximum BER element size, or {@code 0} to indicate
+ * that there is no limit.
+ * @return The new ASN.1 reader.
+ */
+ public static ASN1Reader getReader(byte[] array, int maxElementSize)
+ {
+ return getReader(ByteString.wrap(array), maxElementSize);
+ }
+
+
+
+ /**
+ * Returns an ASN.1 reader whose source is the provided byte sequence
+ * and having an unlimited maximum BER element size.
+ *
+ * @param sequence
+ * The byte sequence to use.
+ * @return The new ASN.1 reader.
+ */
+ public static ASN1Reader getReader(ByteSequence sequence)
+ {
+ return getReader(sequence, 0);
+ }
+
+
+
+ /**
+ * Returns an ASN.1 reader whose source is the provided byte sequence
+ * and having a user defined maximum BER element size.
+ *
+ * @param sequence
+ * The byte sequence to use.
+ * @param maxElementSize
+ * The maximum BER element size, or {@code 0} to indicate
+ * that there is no limit.
+ * @return The new ASN.1 reader.
+ */
+ public static ASN1Reader getReader(ByteSequence sequence,
+ int maxElementSize)
+ {
+ return new ASN1ByteSequenceReader(sequence.asReader(),
+ maxElementSize);
+ }
+
+
+
+ /**
+ * Returns an ASN.1 reader whose source is the provided byte sequence
+ * reader and having an unlimited maximum BER element size.
+ *
+ * @param reader
+ * The byte sequence reader to use.
+ * @return The new ASN.1 reader.
+ */
+ public static ASN1Reader getReader(ByteSequenceReader reader)
+ {
+ return getReader(reader, 0);
+ }
+
+
+
+ /**
+ * Returns an ASN.1 reader whose source is the provided byte sequence
+ * reader and having a user defined maximum BER element size.
+ *
+ * @param reader
+ * The byte sequence reader to use.
+ * @param maxElementSize
+ * The maximum BER element size, or {@code 0} to indicate
+ * that there is no limit.
+ * @return The new ASN.1 reader.
+ */
+ public static ASN1Reader getReader(ByteSequenceReader reader,
+ int maxElementSize)
+ {
+ return new ASN1ByteSequenceReader(reader, maxElementSize);
+ }
+
+
+
+ /**
+ * Returns an ASN.1 reader whose source is the provided input stream
+ * and having an unlimited maximum BER element size.
+ *
+ * @param stream
+ * The input stream to use.
+ * @return The new ASN.1 reader.
+ */
+ public static ASN1Reader getReader(InputStream stream)
+ {
+ return getReader(stream, 0);
+ }
+
+
+
+ /**
+ * Returns an ASN.1 reader whose source is the provided input stream
+ * and having a user defined maximum BER element size.
+ *
+ * @param stream
+ * The input stream to use.
+ * @param maxElementSize
+ * The maximum BER element size, or {@code 0} to indicate
+ * that there is no limit.
+ * @return The new ASN.1 reader.
+ */
+ public static ASN1Reader getReader(InputStream stream,
+ int maxElementSize)
+ {
+ return new ASN1InputStreamReader(stream, maxElementSize);
+ }
+
+
+
+ /**
+ * Returns an ASN.1 writer whose destination is the provided byte
+ * string builder.
+ *
+ * @param builder
+ * The byte string builder to use.
+ * @return The new ASN.1 writer.
+ */
+ public static ASN1Writer getWriter(ByteStringBuilder builder)
+ {
+ ByteSequenceOutputStream outputStream =
+ new ByteSequenceOutputStream(builder);
+ return getWriter(outputStream);
+ }
+
+
+
+ /**
+ * Returns an ASN.1 writer whose destination is the provided output
+ * stream.
+ *
+ * @param stream
+ * The output stream to use.
+ * @return The new ASN.1 writer.
+ */
+ public static ASN1Writer getWriter(OutputStream stream)
+ {
+ return new ASN1OutputStreamWriter(stream);
+ }
+
+
+
+ // Prevent instantiation.
+ private ASN1()
+ {
+ // Nothing to do.
+ }
+}
diff --git a/sdk/src/org/opends/sdk/asn1/ASN1ByteSequenceReader.java b/sdk/src/org/opends/sdk/asn1/ASN1ByteSequenceReader.java
new file mode 100644
index 0000000..8e3ec17
--- /dev/null
+++ b/sdk/src/org/opends/sdk/asn1/ASN1ByteSequenceReader.java
@@ -0,0 +1,563 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2008 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.asn1;
+
+
+
+import static org.opends.messages.ProtocolMessages.*;
+import static org.opends.sdk.asn1.ASN1Constants.ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE;
+import static org.opends.sdk.asn1.ASN1Constants.ELEMENT_READ_STATE_NEED_TYPE;
+import static org.opends.sdk.asn1.ASN1Constants.ELEMENT_READ_STATE_NEED_VALUE_BYTES;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.logging.Level;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequenceReader;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * An ASN.1 reader that reads from a {@link ByteSequenceReader}.
+ */
+final class ASN1ByteSequenceReader extends AbstractASN1Reader implements
+ ASN1Reader
+{
+
+ private int state = ELEMENT_READ_STATE_NEED_TYPE;
+
+ private byte peekType = 0;
+
+ private int peekLength = -1;
+
+ private final int maxElementSize;
+
+ private ByteSequenceReader reader;
+
+ private final LinkedList<ByteSequenceReader> readerStack;
+
+
+
+ /**
+ * Creates a new ASN1 reader whose source is the provided byte
+ * sequence reader and having a user defined maximum BER element size.
+ *
+ * @param reader
+ * The byte sequence reader to be read.
+ * @param maxElementSize
+ * The maximum BER element size, or <code>0</code> to
+ * indicate that there is no limit.
+ */
+ ASN1ByteSequenceReader(ByteSequenceReader reader, int maxElementSize)
+ {
+ this.reader = reader;
+ this.readerStack = new LinkedList<ByteSequenceReader>();
+ this.maxElementSize = maxElementSize;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void close() throws IOException
+ {
+ readerStack.clear();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean elementAvailable() throws IOException
+ {
+ if ((state == ELEMENT_READ_STATE_NEED_TYPE)
+ && !needTypeState(false))
+ {
+ return false;
+ }
+ if ((state == ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE)
+ && !needFirstLengthByteState(false))
+ {
+ return false;
+ }
+
+ return peekLength <= reader.remaining();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean hasNextElement() throws IOException
+ {
+ return (state != ELEMENT_READ_STATE_NEED_TYPE)
+ || needTypeState(false);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int peekLength() throws IOException
+ {
+ peekType();
+
+ if (state == ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE)
+ {
+ needFirstLengthByteState(true);
+ }
+
+ return peekLength;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public byte peekType() throws IOException
+ {
+ if (state == ELEMENT_READ_STATE_NEED_TYPE)
+ {
+ // Read just the type.
+ if (reader.remaining() <= 0)
+ {
+ Message message = ERR_ASN1_TRUCATED_TYPE_BYTE.get();
+ throw DecodeException.fatalError(message);
+ }
+ int type = reader.get();
+
+ peekType = (byte) type;
+ state = ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE;
+ }
+
+ return peekType;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean readBoolean() throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ if (peekLength != 1)
+ {
+ Message message = ERR_ASN1_BOOLEAN_INVALID_LENGTH.get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+
+ if (reader.remaining() < peekLength)
+ {
+ Message message = ERR_ASN1_BOOLEAN_TRUNCATED_VALUE
+ .get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+ int readByte = reader.get();
+
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ return readByte != 0x00;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void readEndSequence() throws IOException,
+ IllegalStateException
+ {
+ if (readerStack.isEmpty())
+ {
+ Message message = ERR_ASN1_SEQUENCE_READ_NOT_STARTED.get();
+ throw new IllegalStateException(message.toString());
+ }
+
+ if ((reader.remaining() > 0)
+ && StaticUtils.DEBUG_LOG.isLoggable(Level.FINE))
+ {
+ StaticUtils.DEBUG_LOG.fine("Ignoring " + reader.remaining()
+ + " unused trailing bytes in " + "ASN.1 SEQUENCE");
+ }
+
+ reader = readerStack.removeFirst();
+
+ // Reset the state
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void readEndSet() throws IOException
+ {
+ // From an implementation point of view, a set is equivalent to a
+ // sequence.
+ readEndSequence();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int readEnumerated() throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ if ((peekLength < 1) || (peekLength > 4))
+ {
+ Message message = ERR_ASN1_INTEGER_INVALID_LENGTH.get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+
+ // From an implementation point of view, an enumerated value is
+ // equivalent to an integer.
+ return (int) readInteger();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public long readInteger() throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ if ((peekLength < 1) || (peekLength > 8))
+ {
+ Message message = ERR_ASN1_INTEGER_INVALID_LENGTH.get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+
+ if (reader.remaining() < peekLength)
+ {
+ Message message = ERR_ASN1_INTEGER_TRUNCATED_VALUE
+ .get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+ if (peekLength > 4)
+ {
+ long longValue = 0;
+ for (int i = 0; i < peekLength; i++)
+ {
+ int readByte = reader.get();
+ if ((i == 0) && (readByte < 0))
+ {
+ longValue = 0xFFFFFFFFFFFFFFFFL;
+ }
+ longValue = (longValue << 8) | (readByte & 0xFF);
+ }
+
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ return longValue;
+ }
+ else
+ {
+ int intValue = 0;
+ for (int i = 0; i < peekLength; i++)
+ {
+ int readByte = reader.get();
+ if ((i == 0) && (readByte < 0))
+ {
+ intValue = 0xFFFFFFFF;
+ }
+ intValue = (intValue << 8) | (readByte & 0xFF);
+ }
+
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ return intValue;
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void readNull() throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ // Make sure that the decoded length is exactly zero byte.
+ if (peekLength != 0)
+ {
+ Message message = ERR_ASN1_NULL_INVALID_LENGTH.get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteString readOctetString() throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ if (reader.remaining() < peekLength)
+ {
+ Message message = ERR_ASN1_OCTET_STRING_TRUNCATED_VALUE
+ .get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ return reader.getByteString(peekLength);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteStringBuilder readOctetString(ByteStringBuilder builder)
+ throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ // Copy the value.
+ if (reader.remaining() < peekLength)
+ {
+ Message message = ERR_ASN1_OCTET_STRING_TRUNCATED_VALUE
+ .get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+ builder.append(reader, peekLength);
+
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ return builder;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String readOctetStringAsString() throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ if (reader.remaining() < peekLength)
+ {
+ Message message = ERR_ASN1_OCTET_STRING_TRUNCATED_VALUE
+ .get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ return reader.getString(peekLength);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void readStartSequence() throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ if (reader.remaining() < peekLength)
+ {
+ Message message = ERR_ASN1_SEQUENCE_SET_TRUNCATED_VALUE
+ .get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+
+ ByteSequenceReader subByteString = reader.getByteSequence(
+ peekLength).asReader();
+ readerStack.addFirst(reader);
+ reader = subByteString;
+
+ // Reset the state
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void readStartSet() throws IOException
+ {
+ // From an implementation point of view, a set is equivalent to a
+ // sequence.
+ readStartSequence();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Reader skipElement() throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ if (reader.remaining() < peekLength)
+ {
+ Message message = ERR_ASN1_SKIP_TRUNCATED_VALUE.get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ reader.skip(peekLength);
+ return this;
+ }
+
+
+
+ /**
+ * Internal helper method reading the first length bytes and
+ * transition to the next state if successful.
+ *
+ * @param throwEofException
+ * <code>true</code> to throw an exception when the end of
+ * the sequence is encountered.
+ * @return <code>true</code> if the length bytes was successfully read
+ * @throws IOException
+ * If an error occurs while trying to decode an ASN1
+ * element.
+ */
+ private boolean needFirstLengthByteState(boolean throwEofException)
+ throws IOException
+ {
+ if (reader.remaining() <= 0)
+ {
+ if (throwEofException)
+ {
+ Message message = ERR_ASN1_TRUNCATED_LENGTH_BYTE.get();
+ throw DecodeException.fatalError(message);
+ }
+ return false;
+ }
+ int readByte = reader.get();
+ peekLength = (readByte & 0x7F);
+ if (peekLength != readByte)
+ {
+ int lengthBytesNeeded = peekLength;
+ if (lengthBytesNeeded > 4)
+ {
+ Message message = ERR_ASN1_INVALID_NUM_LENGTH_BYTES
+ .get(lengthBytesNeeded);
+ throw DecodeException.fatalError(message);
+ }
+
+ peekLength = 0x00;
+ if (reader.remaining() < lengthBytesNeeded)
+ {
+ if (throwEofException)
+ {
+ Message message = ERR_ASN1_TRUNCATED_LENGTH_BYTES
+ .get(lengthBytesNeeded);
+ throw DecodeException.fatalError(message);
+ }
+ return false;
+ }
+
+ while (lengthBytesNeeded > 0)
+ {
+ readByte = reader.get();
+ peekLength = (peekLength << 8) | (readByte & 0xFF);
+ lengthBytesNeeded--;
+ }
+ }
+
+ // Make sure that the element is not larger than the maximum allowed
+ // message size.
+ if ((maxElementSize > 0) && (peekLength > maxElementSize))
+ {
+ Message message = ERR_LDAP_CLIENT_DECODE_MAX_REQUEST_SIZE_EXCEEDED
+ .get(peekLength, maxElementSize);
+ throw DecodeException.fatalError(message);
+ }
+ state = ELEMENT_READ_STATE_NEED_VALUE_BYTES;
+ return true;
+ }
+
+
+
+ /**
+ * Internal helper method reading the ASN.1 type byte and transition
+ * to the next state if successful.
+ *
+ * @param throwEofException
+ * <code>true</code> to throw an exception when the end of
+ * the sequence is encountered.
+ * @return <code>true</code> if the type byte was successfully read
+ * @throws IOException
+ * If an error occurs while trying to decode an ASN1
+ * element.
+ */
+ private boolean needTypeState(boolean throwEofException)
+ throws IOException
+ {
+ // Read just the type.
+ if (reader.remaining() <= 0)
+ {
+ if (throwEofException)
+ {
+ Message message = ERR_ASN1_TRUCATED_TYPE_BYTE.get();
+ throw DecodeException.fatalError(message);
+ }
+ return false;
+ }
+ int type = reader.get();
+
+ peekType = (byte) type;
+ state = ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE;
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/asn1/ASN1Constants.java b/sdk/src/org/opends/sdk/asn1/ASN1Constants.java
new file mode 100644
index 0000000..63d37fd
--- /dev/null
+++ b/sdk/src/org/opends/sdk/asn1/ASN1Constants.java
@@ -0,0 +1,167 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2008 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.asn1;
+
+
+
+/**
+ * This class defines a number of constants that may be used when
+ * interacting with ASN.1 elements.
+ */
+public final class ASN1Constants
+{
+
+ // Prevent instantiation.
+ private ASN1Constants()
+ {
+ // Nothing to do.
+ }
+
+ /**
+ * The ASN.1 element decoding state that indicates that the next byte
+ * read should be the BER type for a new element.
+ */
+ static final int ELEMENT_READ_STATE_NEED_TYPE = 0;
+
+ /**
+ * The ASN.1 element decoding state that indicates that the next byte
+ * read should be the first byte for the element length.
+ */
+ static final int ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE = 1;
+
+ /**
+ * The ASN.1 element decoding state that indicates that the next byte
+ * read should be additional bytes of a multi-byte length.
+ */
+ static final int ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES = 2;
+
+ /**
+ * The ASN.1 element decoding state that indicates that the next byte
+ * read should be applied to the value of the element.
+ */
+ static final int ELEMENT_READ_STATE_NEED_VALUE_BYTES = 3;
+
+ /**
+ * The BER type that is assigned to the universal Boolean element.
+ */
+ public static final byte UNIVERSAL_BOOLEAN_TYPE = 0x01;
+
+ /**
+ * The BER type that is assigned to the universal integer type.
+ */
+ public static final byte UNIVERSAL_INTEGER_TYPE = 0x02;
+
+ /**
+ * The BER type that is assigned to the universal octet string type.
+ */
+ public static final byte UNIVERSAL_OCTET_STRING_TYPE = 0x04;
+
+ /**
+ * The BER type that is assigned to the universal null type.
+ */
+ public static final byte UNIVERSAL_NULL_TYPE = 0x05;
+
+ /**
+ * The BER type that is assigned to the universal enumerated type.
+ */
+ public static final byte UNIVERSAL_ENUMERATED_TYPE = 0x0A;
+
+ /**
+ * The BER type that is assigned to the universal sequence type.
+ */
+ public static final byte UNIVERSAL_SEQUENCE_TYPE = 0x30;
+
+ /**
+ * The BER type that is assigned to the universal set type.
+ */
+ public static final byte UNIVERSAL_SET_TYPE = 0x31;
+
+ /**
+ * The byte array that will be used for ASN.1 elements with no value.
+ */
+ static final byte[] NO_VALUE = new byte[0];
+
+ /**
+ * The bitmask that can be ANDed with the BER type to zero out all
+ * bits except those used in the class.
+ */
+ static final byte TYPE_MASK_ALL_BUT_CLASS = (byte) 0xC0;
+
+ /**
+ * The bitmask that can be ANDed with the BER type to determine if the
+ * element is in the universal class.
+ */
+ static final byte TYPE_MASK_UNIVERSAL = 0x00;
+
+ /**
+ * The bitmask that can be ANDed with the BER type to determine if the
+ * element is in the application-specific class.
+ */
+ static final byte TYPE_MASK_APPLICATION = 0x40;
+
+ /**
+ * The bitmask that can be ANDed with the BER type to determine if the
+ * element is in the context-specific class.
+ */
+ static final byte TYPE_MASK_CONTEXT = (byte) 0x80;
+
+ /**
+ * The bitmask that can be ANDed with the BER type to determine if the
+ * element is in the private class.
+ */
+ static final byte TYPE_MASK_PRIVATE = (byte) 0xC0;
+
+ /**
+ * The bitmask that can be ANDed with the BER type to zero out all
+ * bits except the primitive/constructed bit.
+ */
+ static final byte TYPE_MASK_ALL_BUT_PC = (byte) 0x20;
+
+ /**
+ * The bitmask that can be ANDed with the BER type to determine if the
+ * element is a primitive.
+ */
+ static final byte TYPE_MASK_PRIMITIVE = 0x00;
+
+ /**
+ * The bitmask that can be ANDed with the BER type to determine if the
+ * element is constructed.
+ */
+ static final byte TYPE_MASK_CONSTRUCTED = 0x20;
+
+ /**
+ * The byte array containing the pre-encoded ASN.1 encoding for a
+ * boolean value of "false".
+ */
+ public static final byte BOOLEAN_VALUE_FALSE = 0x00;
+
+ /**
+ * The byte array containing the pre-encoded ASN.1 encoding for a
+ * boolean value of "false".
+ */
+ public static final byte BOOLEAN_VALUE_TRUE = (byte) 0xFF;
+}
diff --git a/sdk/src/org/opends/sdk/asn1/ASN1InputStreamReader.java b/sdk/src/org/opends/sdk/asn1/ASN1InputStreamReader.java
new file mode 100644
index 0000000..1b67947
--- /dev/null
+++ b/sdk/src/org/opends/sdk/asn1/ASN1InputStreamReader.java
@@ -0,0 +1,789 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2008 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.asn1;
+
+
+
+import static org.opends.messages.ProtocolMessages.*;
+import static org.opends.sdk.asn1.ASN1Constants.ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES;
+import static org.opends.sdk.asn1.ASN1Constants.ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE;
+import static org.opends.sdk.asn1.ASN1Constants.ELEMENT_READ_STATE_NEED_TYPE;
+import static org.opends.sdk.asn1.ASN1Constants.ELEMENT_READ_STATE_NEED_VALUE_BYTES;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.LinkedList;
+import java.util.logging.Level;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.SizeLimitInputStream;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * An ASN1Reader that reads from an input stream.
+ */
+final class ASN1InputStreamReader extends AbstractASN1Reader implements
+ ASN1Reader
+{
+ private int state = ELEMENT_READ_STATE_NEED_TYPE;
+
+ private byte peekType = 0;
+
+ private int peekLength = -1;
+
+ private int lengthBytesNeeded = 0;
+
+ private final int maxElementSize;
+
+ private InputStream in;
+
+ private final LinkedList<InputStream> streamStack;
+
+ private byte[] buffer;
+
+
+
+ /**
+ * Creates a new ASN1 reader whose source is the provided input stream
+ * and having a user defined maximum BER element size.
+ *
+ * @param stream
+ * The input stream to be read.
+ * @param maxElementSize
+ * The maximum BER element size, or <code>0</code> to
+ * indicate that there is no limit.
+ */
+ ASN1InputStreamReader(InputStream stream, int maxElementSize)
+ {
+ this.in = stream;
+ this.streamStack = new LinkedList<InputStream>();
+ this.buffer = new byte[512];
+ this.maxElementSize = maxElementSize;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void close() throws IOException
+ {
+ // Calling close of SizeLimitInputStream should close the parent
+ // stream.
+ in.close();
+ streamStack.clear();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean elementAvailable() throws IOException
+ {
+ if ((state == ELEMENT_READ_STATE_NEED_TYPE)
+ && !needTypeState(false, false))
+ {
+ return false;
+ }
+ if ((state == ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE)
+ && !needFirstLengthByteState(false, false))
+ {
+ return false;
+ }
+ if ((state == ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES)
+ && !needAdditionalLengthBytesState(false, false))
+ {
+ return false;
+ }
+
+ return peekLength <= in.available();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean hasNextElement() throws IOException
+ {
+ if (!streamStack.isEmpty())
+ {
+ // We are reading a sub sequence. Return true as long as we
+ // haven't exhausted the size limit for the sub sequence sub input
+ // stream.
+ SizeLimitInputStream subSq = (SizeLimitInputStream) in;
+ return (subSq.getSizeLimit() - subSq.getBytesRead() > 0);
+ }
+
+ return (state != ELEMENT_READ_STATE_NEED_TYPE)
+ || needTypeState(true, false);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int peekLength() throws IOException
+ {
+ peekType();
+
+ switch (state)
+ {
+ case ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE:
+ needFirstLengthByteState(true, true);
+ break;
+
+ case ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES:
+ needAdditionalLengthBytesState(true, true);
+ }
+
+ return peekLength;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public byte peekType() throws IOException
+ {
+ if (state == ELEMENT_READ_STATE_NEED_TYPE)
+ {
+ needTypeState(true, true);
+ }
+
+ return peekType;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean readBoolean() throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ if (peekLength != 1)
+ {
+ Message message = ERR_ASN1_BOOLEAN_INVALID_LENGTH.get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+
+ int readByte = in.read();
+ if (readByte == -1)
+ {
+ Message message = ERR_ASN1_BOOLEAN_TRUNCATED_VALUE
+ .get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "READ ASN.1 BOOLEAN(type=0x%x, length=%d, value=%s)",
+ peekType, peekLength, String.valueOf(readByte != 0x00)));
+ }
+
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ return readByte != 0x00;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void readEndSequence() throws IOException,
+ IllegalStateException
+ {
+ if (streamStack.isEmpty())
+ {
+ Message message = ERR_ASN1_SEQUENCE_READ_NOT_STARTED.get();
+ throw new IllegalStateException(message.toString());
+ }
+
+ // Ignore all unused trailing components.
+ SizeLimitInputStream subSq = (SizeLimitInputStream) in;
+ if (subSq.getSizeLimit() - subSq.getBytesRead() > 0)
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINE))
+ {
+ StaticUtils.DEBUG_LOG.fine(String.format(
+ "Ignoring %d unused trailing bytes in ASN.1 SEQUENCE",
+ subSq.getSizeLimit() - subSq.getBytesRead()));
+ }
+
+ subSq.skip(subSq.getSizeLimit() - subSq.getBytesRead());
+
+ }
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String
+ .format("READ ASN.1 END SEQUENCE"));
+ }
+
+ in = streamStack.removeFirst();
+
+ // Reset the state
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void readEndSet() throws IOException
+ {
+ // From an implementation point of view, a set is equivalent to a
+ // sequence.
+ readEndSequence();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int readEnumerated() throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ if ((peekLength < 1) || (peekLength > 4))
+ {
+ Message message = ERR_ASN1_INTEGER_INVALID_LENGTH.get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+
+ // From an implementation point of view, an enumerated value is
+ // equivalent to an integer.
+ return (int) readInteger();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public long readInteger() throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ if ((peekLength < 1) || (peekLength > 8))
+ {
+ Message message = ERR_ASN1_INTEGER_INVALID_LENGTH.get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+
+ if (peekLength > 4)
+ {
+ long longValue = 0;
+ for (int i = 0; i < peekLength; i++)
+ {
+ int readByte = in.read();
+ if (readByte == -1)
+ {
+ Message message = ERR_ASN1_INTEGER_TRUNCATED_VALUE
+ .get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+ if ((i == 0) && (((byte) readByte) < 0))
+ {
+ longValue = 0xFFFFFFFFFFFFFFFFL;
+ }
+ longValue = (longValue << 8) | (readByte & 0xFF);
+ }
+
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ return longValue;
+ }
+ else
+ {
+ int intValue = 0;
+ for (int i = 0; i < peekLength; i++)
+ {
+ int readByte = in.read();
+ if (readByte == -1)
+ {
+ Message message = ERR_ASN1_INTEGER_TRUNCATED_VALUE
+ .get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+ if ((i == 0) && (((byte) readByte) < 0))
+ {
+ intValue = 0xFFFFFFFF;
+ }
+ intValue = (intValue << 8) | (readByte & 0xFF);
+ }
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "READ ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ peekType, peekLength, intValue));
+ }
+
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ return intValue;
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void readNull() throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ // Make sure that the decoded length is exactly zero byte.
+ if (peekLength != 0)
+ {
+ Message message = ERR_ASN1_NULL_INVALID_LENGTH.get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String
+ .format("READ ASN.1 NULL(type=0x%x, length=%d)", peekType,
+ peekLength));
+ }
+
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteString readOctetString() throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ if (peekLength == 0)
+ {
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ return ByteString.empty();
+ }
+
+ // Copy the value and construct the element to return.
+ byte[] value = new byte[peekLength];
+ int bytesNeeded = peekLength;
+ int bytesRead;
+ while (bytesNeeded > 0)
+ {
+ bytesRead = in.read(value, peekLength - bytesNeeded, bytesNeeded);
+ if (bytesRead < 0)
+ {
+ Message message = ERR_ASN1_OCTET_STRING_TRUNCATED_VALUE
+ .get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+
+ bytesNeeded -= bytesRead;
+ }
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "READ ASN.1 OCTETSTRING(type=0x%x, length=%d)", peekType,
+ peekLength));
+ }
+
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ return ByteString.wrap(value);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteStringBuilder readOctetString(ByteStringBuilder builder)
+ throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ if (peekLength == 0)
+ {
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ return builder;
+ }
+
+ // Copy the value and construct the element to return.
+ int bytesNeeded = peekLength;
+ int bytesRead;
+ while (bytesNeeded > 0)
+ {
+ bytesRead = builder.append(in, bytesNeeded);
+ if (bytesRead < 0)
+ {
+ Message message = ERR_ASN1_OCTET_STRING_TRUNCATED_VALUE
+ .get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+ bytesNeeded -= bytesRead;
+ }
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "READ ASN.1 OCTETSTRING(type=0x%x, length=%d)", peekType,
+ peekLength));
+ }
+
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ return builder;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String readOctetStringAsString() throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ if (peekLength == 0)
+ {
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ return "";
+ }
+
+ // Resize the temp buffer if needed
+ if (peekLength > buffer.length)
+ {
+ buffer = new byte[peekLength];
+ }
+
+ int bytesNeeded = peekLength;
+ int bytesRead;
+ while (bytesNeeded > 0)
+ {
+ bytesRead = in
+ .read(buffer, peekLength - bytesNeeded, bytesNeeded);
+ if (bytesRead < 0)
+ {
+ Message message = ERR_ASN1_OCTET_STRING_TRUNCATED_VALUE
+ .get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+ bytesNeeded -= bytesRead;
+ }
+
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+
+ String str;
+ try
+ {
+ str = new String(buffer, 0, peekLength, "UTF-8");
+ }
+ catch (Exception e)
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ StaticUtils.DEBUG_LOG
+ .warning("Unable to decode ASN.1 OCTETSTRING "
+ + "bytes as UTF-8 string: " + e.toString());
+ }
+
+ str = new String(buffer, 0, peekLength);
+ }
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "READ ASN.1 OCTETSTRING(type=0x%x, length=%d, value=%s)",
+ peekType, peekLength, str));
+ }
+
+ return str;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void readStartSequence() throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ SizeLimitInputStream subStream = new SizeLimitInputStream(in,
+ peekLength);
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "READ ASN.1 START SEQUENCE(type=0x%x, length=%d)", peekType,
+ peekLength));
+ }
+
+ streamStack.addFirst(in);
+ in = subStream;
+
+ // Reset the state
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void readStartSet() throws IOException
+ {
+ // From an implementation point of view, a set is equivalent to a
+ // sequence.
+ readStartSequence();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Reader skipElement() throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ long bytesSkipped = in.skip(peekLength);
+ if (bytesSkipped != peekLength)
+ {
+ Message message = ERR_ASN1_SKIP_TRUNCATED_VALUE.get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ return this;
+ }
+
+
+
+ /**
+ * Internal helper method reading the additional ASN.1 length bytes
+ * and transition to the next state if successful.
+ *
+ * @param isBlocking
+ * <code>true</code> to block if the type byte is not
+ * available or <code>false</code> to check for availability
+ * first.
+ * @param throwEofException
+ * <code>true</code> to throw an exception when an EOF is
+ * encountered or <code>false</code> to return false.
+ * @return <code>true</code> if the length bytes was successfully
+ * read.
+ * @throws IOException
+ * If an error occurs while reading from the stream.
+ */
+ private boolean needAdditionalLengthBytesState(boolean isBlocking,
+ boolean throwEofException) throws IOException
+ {
+ if (!isBlocking && (in.available() < lengthBytesNeeded))
+ {
+ return false;
+ }
+
+ int readByte;
+ while (lengthBytesNeeded > 0)
+ {
+ readByte = in.read();
+ if (readByte == -1)
+ {
+ state = ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES;
+ if (throwEofException)
+ {
+ Message message = ERR_ASN1_TRUNCATED_LENGTH_BYTES
+ .get(lengthBytesNeeded);
+ throw DecodeException.fatalError(message);
+ }
+ return false;
+ }
+ peekLength = (peekLength << 8) | (readByte & 0xFF);
+ lengthBytesNeeded--;
+ }
+
+ // Make sure that the element is not larger than the maximum allowed
+ // message size.
+ if ((maxElementSize > 0) && (peekLength > maxElementSize))
+ {
+ Message message = ERR_LDAP_CLIENT_DECODE_MAX_REQUEST_SIZE_EXCEEDED
+ .get(peekLength, maxElementSize);
+ throw DecodeException.fatalError(message);
+ }
+ state = ELEMENT_READ_STATE_NEED_VALUE_BYTES;
+ return true;
+ }
+
+
+
+ /**
+ * Internal helper method reading the first length bytes and
+ * transition to the next state if successful.
+ *
+ * @param isBlocking
+ * <code>true</code> to block if the type byte is not
+ * available or <code>false</code> to check for availability
+ * first.
+ * @param throwEofException
+ * <code>true</code> to throw an exception when an EOF is
+ * encountered or <code>false</code> to return false.
+ * @return <code>true</code> if the length bytes was successfully read
+ * @throws IOException
+ * If an error occurs while reading from the stream.
+ */
+ private boolean needFirstLengthByteState(boolean isBlocking,
+ boolean throwEofException) throws IOException
+ {
+ if (!isBlocking && (in.available() <= 0))
+ {
+ return false;
+ }
+
+ int readByte = in.read();
+ if (readByte == -1)
+ {
+ if (throwEofException)
+ {
+ Message message = ERR_ASN1_TRUNCATED_LENGTH_BYTE.get();
+ throw DecodeException.fatalError(message);
+ }
+ return false;
+ }
+ peekLength = (readByte & 0x7F);
+ if (peekLength != readByte)
+ {
+ lengthBytesNeeded = peekLength;
+ if (lengthBytesNeeded > 4)
+ {
+ Message message = ERR_ASN1_INVALID_NUM_LENGTH_BYTES
+ .get(lengthBytesNeeded);
+ throw DecodeException.fatalError(message);
+ }
+ peekLength = 0x00;
+
+ if (!isBlocking && (in.available() < lengthBytesNeeded))
+ {
+ state = ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES;
+ return false;
+ }
+
+ while (lengthBytesNeeded > 0)
+ {
+ readByte = in.read();
+ if (readByte == -1)
+ {
+ state = ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES;
+ if (throwEofException)
+ {
+ Message message = ERR_ASN1_TRUNCATED_LENGTH_BYTES
+ .get(lengthBytesNeeded);
+ throw DecodeException.fatalError(message);
+ }
+ return false;
+ }
+ peekLength = (peekLength << 8) | (readByte & 0xFF);
+ lengthBytesNeeded--;
+ }
+ }
+
+ // Make sure that the element is not larger than the maximum allowed
+ // message size.
+ if ((maxElementSize > 0) && (peekLength > maxElementSize))
+ {
+ Message message = ERR_LDAP_CLIENT_DECODE_MAX_REQUEST_SIZE_EXCEEDED
+ .get(peekLength, maxElementSize);
+ throw DecodeException.fatalError(message);
+ }
+ state = ELEMENT_READ_STATE_NEED_VALUE_BYTES;
+ return true;
+ }
+
+
+
+ /**
+ * Internal helper method reading the ASN.1 type byte and transition
+ * to the next state if successful.
+ *
+ * @param isBlocking
+ * <code>true</code> to block if the type byte is not
+ * available or <code>false</code> to check for availability
+ * first.
+ * @param throwEofException
+ * <code>true</code> to throw an exception when an EOF is
+ * encountered or <code>false</code> to return false.
+ * @return <code>true</code> if the type byte was successfully read
+ * @throws IOException
+ * If an error occurs while reading from the stream.
+ */
+ private boolean needTypeState(boolean isBlocking,
+ boolean throwEofException) throws IOException
+ {
+ // Read just the type.
+ if (!isBlocking && (in.available() <= 0))
+ {
+ return false;
+ }
+
+ int type = in.read();
+ if (type == -1)
+ {
+ if (throwEofException)
+ {
+ Message message = ERR_ASN1_TRUCATED_TYPE_BYTE.get();
+ throw DecodeException.fatalError(message);
+ }
+ return false;
+ }
+
+ peekType = (byte) type;
+ state = ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE;
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/asn1/ASN1OutputStreamWriter.java b/sdk/src/org/opends/sdk/asn1/ASN1OutputStreamWriter.java
new file mode 100644
index 0000000..e3ca2e6
--- /dev/null
+++ b/sdk/src/org/opends/sdk/asn1/ASN1OutputStreamWriter.java
@@ -0,0 +1,560 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.asn1;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_ASN1_SEQUENCE_WRITE_NOT_STARTED;
+import static org.opends.sdk.asn1.ASN1Constants.BOOLEAN_VALUE_FALSE;
+import static org.opends.sdk.asn1.ASN1Constants.BOOLEAN_VALUE_TRUE;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.logging.Level;
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteSequenceOutputStream;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.StaticUtils;
+
+
+/**
+ * An ASN1Writer implementation that outputs to an outputstream.
+ */
+final class ASN1OutputStreamWriter extends AbstractASN1Writer implements
+ ASN1Writer
+{
+ private final OutputStream rootStream;
+ private OutputStream out;
+ private final ArrayList<ByteSequenceOutputStream> streamStack;
+ private int stackDepth;
+
+
+
+ /**
+ * Creates a new ASN.1 output stream reader.
+ *
+ * @param stream
+ * The underlying output stream.
+ */
+ ASN1OutputStreamWriter(OutputStream stream)
+ {
+ this.out = stream;
+ this.rootStream = stream;
+ this.streamStack = new ArrayList<ByteSequenceOutputStream>();
+ this.stackDepth = -1;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void close() throws IOException
+ {
+ while (stackDepth >= 0)
+ {
+ writeEndSequence();
+ }
+ rootStream.flush();
+
+ streamStack.clear();
+ rootStream.close();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void flush() throws IOException
+ {
+ rootStream.flush();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeBoolean(byte type, boolean booleanValue)
+ throws IOException
+ {
+ out.write(type);
+ writeLength(1);
+ out.write(booleanValue ? BOOLEAN_VALUE_TRUE : BOOLEAN_VALUE_FALSE);
+
+ if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 BOOLEAN(type=0x%x, length=%d, value=%s)", type,
+ 1, String.valueOf(booleanValue)));
+ }
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeEndSequence() throws IOException, IllegalStateException
+ {
+ if (stackDepth < 0)
+ {
+ Message message = ERR_ASN1_SEQUENCE_WRITE_NOT_STARTED.get();
+ throw new IllegalStateException(message.toString());
+ }
+
+ ByteSequenceOutputStream childStream = streamStack.get(stackDepth);
+
+ // Decrement the stack depth and get the parent stream
+ --stackDepth;
+
+ OutputStream parentStream =
+ stackDepth < 0 ? rootStream : streamStack.get(stackDepth);
+
+ // Switch to parent stream and reset the sub-stream
+ out = parentStream;
+
+ // Write the length and contents of the sub-stream
+ writeLength(childStream.length());
+ childStream.writeTo(parentStream);
+
+ if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 END SEQUENCE(length=%d)", childStream.length()));
+ }
+
+ childStream.reset();
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeEndSet() throws IOException
+ {
+ return writeEndSequence();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeEnumerated(byte type, int intValue)
+ throws IOException
+ {
+ return writeInteger(type, intValue);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeInteger(byte type, int intValue)
+ throws IOException
+ {
+ out.write(type);
+ if (((intValue < 0) && ((intValue & 0xFFFFFF80) == 0xFFFFFF80))
+ || ((intValue & 0x0000007F) == intValue))
+ {
+ writeLength(1);
+ out.write((byte) (intValue & 0xFF));
+ if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ type, 1, intValue));
+ }
+ }
+ else if (((intValue < 0) && ((intValue & 0xFFFF8000) == 0xFFFF8000))
+ || ((intValue & 0x00007FFF) == intValue))
+ {
+ writeLength(2);
+ out.write((byte) ((intValue >> 8) & 0xFF));
+ out.write((byte) (intValue & 0xFF));
+ if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ type, 2, intValue));
+ }
+ }
+ else if (((intValue < 0) && ((intValue & 0xFF800000) == 0xFF800000))
+ || ((intValue & 0x007FFFFF) == intValue))
+ {
+ writeLength(3);
+ out.write((byte) ((intValue >> 16) & 0xFF));
+ out.write((byte) ((intValue >> 8) & 0xFF));
+ out.write((byte) (intValue & 0xFF));
+ if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ type, 3, intValue));
+ }
+ }
+ else
+ {
+ writeLength(4);
+ out.write((byte) ((intValue >> 24) & 0xFF));
+ out.write((byte) ((intValue >> 16) & 0xFF));
+ out.write((byte) ((intValue >> 8) & 0xFF));
+ out.write((byte) (intValue & 0xFF));
+ if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ type, 4, intValue));
+ }
+ }
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeInteger(byte type, long longValue)
+ throws IOException
+ {
+ out.write(type);
+ if (((longValue < 0) && ((longValue & 0xFFFFFFFFFFFFFF80L) == 0xFFFFFFFFFFFFFF80L))
+ || ((longValue & 0x000000000000007FL) == longValue))
+ {
+ writeLength(1);
+ out.write((byte) (longValue & 0xFF));
+ if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ type, 1, longValue));
+ }
+ }
+ else if (((longValue < 0) && ((longValue & 0xFFFFFFFFFFFF8000L) == 0xFFFFFFFFFFFF8000L))
+ || ((longValue & 0x0000000000007FFFL) == longValue))
+ {
+ writeLength(2);
+ out.write((byte) ((longValue >> 8) & 0xFF));
+ out.write((byte) (longValue & 0xFF));
+ if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ type, 2, longValue));
+ }
+ }
+ else if (((longValue < 0) && ((longValue & 0xFFFFFFFFFF800000L) == 0xFFFFFFFFFF800000L))
+ || ((longValue & 0x00000000007FFFFFL) == longValue))
+ {
+ writeLength(3);
+ out.write((byte) ((longValue >> 16) & 0xFF));
+ out.write((byte) ((longValue >> 8) & 0xFF));
+ out.write((byte) (longValue & 0xFF));
+ if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ type, 3, longValue));
+ }
+ }
+ else if (((longValue < 0) && ((longValue & 0xFFFFFFFF80000000L) == 0xFFFFFFFF80000000L))
+ || ((longValue & 0x000000007FFFFFFFL) == longValue))
+ {
+ writeLength(4);
+ out.write((byte) ((longValue >> 24) & 0xFF));
+ out.write((byte) ((longValue >> 16) & 0xFF));
+ out.write((byte) ((longValue >> 8) & 0xFF));
+ out.write((byte) (longValue & 0xFF));
+ if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ type, 4, longValue));
+ }
+ }
+ else if (((longValue < 0) && ((longValue & 0xFFFFFF8000000000L) == 0xFFFFFF8000000000L))
+ || ((longValue & 0x0000007FFFFFFFFFL) == longValue))
+ {
+ writeLength(5);
+ out.write((byte) ((longValue >> 32) & 0xFF));
+ out.write((byte) ((longValue >> 24) & 0xFF));
+ out.write((byte) ((longValue >> 16) & 0xFF));
+ out.write((byte) ((longValue >> 8) & 0xFF));
+ out.write((byte) (longValue & 0xFF));
+ if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ type, 5, longValue));
+ }
+ }
+ else if (((longValue < 0) && ((longValue & 0xFFFF800000000000L) == 0xFFFF800000000000L))
+ || ((longValue & 0x00007FFFFFFFFFFFL) == longValue))
+ {
+ writeLength(6);
+ out.write((byte) ((longValue >> 40) & 0xFF));
+ out.write((byte) ((longValue >> 32) & 0xFF));
+ out.write((byte) ((longValue >> 24) & 0xFF));
+ out.write((byte) ((longValue >> 16) & 0xFF));
+ out.write((byte) ((longValue >> 8) & 0xFF));
+ out.write((byte) (longValue & 0xFF));
+ if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ type, 6, longValue));
+ }
+ }
+ else if (((longValue < 0) && ((longValue & 0xFF80000000000000L) == 0xFF80000000000000L))
+ || ((longValue & 0x007FFFFFFFFFFFFFL) == longValue))
+ {
+ writeLength(7);
+ out.write((byte) ((longValue >> 48) & 0xFF));
+ out.write((byte) ((longValue >> 40) & 0xFF));
+ out.write((byte) ((longValue >> 32) & 0xFF));
+ out.write((byte) ((longValue >> 24) & 0xFF));
+ out.write((byte) ((longValue >> 16) & 0xFF));
+ out.write((byte) ((longValue >> 8) & 0xFF));
+ out.write((byte) (longValue & 0xFF));
+ if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ type, 7, longValue));
+ }
+ }
+ else
+ {
+ writeLength(8);
+ out.write((byte) ((longValue >> 56) & 0xFF));
+ out.write((byte) ((longValue >> 48) & 0xFF));
+ out.write((byte) ((longValue >> 40) & 0xFF));
+ out.write((byte) ((longValue >> 32) & 0xFF));
+ out.write((byte) ((longValue >> 24) & 0xFF));
+ out.write((byte) ((longValue >> 16) & 0xFF));
+ out.write((byte) ((longValue >> 8) & 0xFF));
+ out.write((byte) (longValue & 0xFF));
+ if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ type, 8, longValue));
+ }
+ }
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeNull(byte type) throws IOException
+ {
+ out.write(type);
+ writeLength(0);
+
+ if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 NULL(type=0x%x, length=%d)", type, 0));
+ }
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeOctetString(byte type, byte[] value,
+ int offset, int length) throws IOException
+ {
+ out.write(type);
+ writeLength(length);
+ out.write(value, offset, length);
+
+ if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d)",
+ type, length));
+ }
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeOctetString(byte type, ByteSequence value)
+ throws IOException
+ {
+ out.write(type);
+ writeLength(value.length());
+ value.copyTo(out);
+
+ if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d)", type, value
+ .length()));
+ }
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeOctetString(byte type, String value)
+ throws IOException
+ {
+ out.write(type);
+
+ if (value == null)
+ {
+ writeLength(0);
+ return this;
+ }
+
+ byte[] bytes = StaticUtils.getBytes(value);
+ writeLength(bytes.length);
+ out.write(bytes);
+
+ if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d, "
+ + "value=%s)", type, bytes.length, value));
+ }
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeStartSequence(byte type) throws IOException
+ {
+ // Write the type in current stream switch to next sub-stream
+ out.write(type);
+
+ // Increment the stack depth and get the sub-stream from the stack
+ ++stackDepth;
+
+ // Make sure we have a cached sub-stream at this depth
+ if (stackDepth >= streamStack.size())
+ {
+ ByteSequenceOutputStream subStream =
+ new ByteSequenceOutputStream(new ByteStringBuilder());
+ streamStack.add(subStream);
+ out = subStream;
+ }
+ else
+ {
+ out = streamStack.get(stackDepth);
+ }
+
+ if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 START SEQUENCE(type=0x%x)", type));
+ }
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeStartSet(byte type) throws IOException
+ {
+ // From an implementation point of view, a set is equivalent to a
+ // sequence.
+ return writeStartSequence(type);
+ }
+
+
+
+ /**
+ * Writes the provided value for use as the length of an ASN.1
+ * element.
+ *
+ * @param length
+ * The length to encode for use in an ASN.1 element.
+ * @throws IOException
+ * if an error occurs while writing.
+ */
+ private void writeLength(int length) throws IOException
+ {
+ if (length < 128)
+ {
+ out.write((byte) length);
+ }
+ else if ((length & 0x000000FF) == length)
+ {
+ out.write((byte) 0x81);
+ out.write((byte) (length & 0xFF));
+ }
+ else if ((length & 0x0000FFFF) == length)
+ {
+ out.write((byte) 0x82);
+ out.write((byte) ((length >> 8) & 0xFF));
+ out.write((byte) (length & 0xFF));
+ }
+ else if ((length & 0x00FFFFFF) == length)
+ {
+ out.write((byte) 0x83);
+ out.write((byte) ((length >> 16) & 0xFF));
+ out.write((byte) ((length >> 8) & 0xFF));
+ out.write((byte) (length & 0xFF));
+ }
+ else
+ {
+ out.write((byte) 0x84);
+ out.write((byte) ((length >> 24) & 0xFF));
+ out.write((byte) ((length >> 16) & 0xFF));
+ out.write((byte) ((length >> 8) & 0xFF));
+ out.write((byte) (length & 0xFF));
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/asn1/ASN1Reader.java b/sdk/src/org/opends/sdk/asn1/ASN1Reader.java
new file mode 100644
index 0000000..424da2a
--- /dev/null
+++ b/sdk/src/org/opends/sdk/asn1/ASN1Reader.java
@@ -0,0 +1,448 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2008 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.asn1;
+
+
+
+import java.io.Closeable;
+import java.io.IOException;
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+
+
+
+/**
+ * An interface for decoding ASN.1 elements from a data source.
+ * <p>
+ * Methods for creating {@link ASN1Reader}s are provided in the
+ * {@link ASN1} class.
+ */
+public interface ASN1Reader extends Closeable
+{
+
+ /**
+ * Closes this ASN.1 reader.
+ *
+ * @throws IOException
+ * If an error occurs while closing.
+ */
+ void close() throws IOException;
+
+
+
+ /**
+ * Indicates whether or not the next element can be read without
+ * blocking.
+ *
+ * @return {@code true} if a complete element is available or {@code
+ * false} otherwise.
+ * @throws DecodeException
+ * If the available data was not valid ASN.1.
+ * @throws IOException
+ * If an unexpected IO error occurred.
+ */
+ boolean elementAvailable() throws DecodeException, IOException;
+
+
+
+ /**
+ * Indicates whether or not the current stream, sequence, or set
+ * contains another element. Note that this method may return {@code
+ * true} even if a previous call to {@link #elementAvailable} returned
+ * {@code false}, indicating that the current set or sequence contains
+ * another element but an attempt to read that element may block. This
+ * method will block if there is not enough data available to make the
+ * determination (typically only the next element's type is required).
+ *
+ * @return {@code true} if the current stream, sequence, or set
+ * contains another element, or {@code false} if the end of
+ * the stream, sequence, or set has been reached.
+ * @throws DecodeException
+ * If the available data was not valid ASN.1.
+ * @throws IOException
+ * If an unexpected IO error occurred.
+ */
+ boolean hasNextElement() throws DecodeException, IOException;
+
+
+
+ /**
+ * Returns the data length of the next element without actually
+ * reading it.
+ *
+ * @return The data length of the next element, or {@code -1} if the
+ * end of the stream, sequence, or set has been reached.
+ * @throws DecodeException
+ * If the available data was not valid ASN.1.
+ * @throws IOException
+ * If an unexpected IO error occurred.
+ */
+ int peekLength() throws DecodeException, IOException;
+
+
+
+ /**
+ * Returns the type of the next element without actually reading it.
+ *
+ * @return The type of the next element, or {@code -1} if the end of
+ * the stream, sequence, or set has been reached.
+ * @throws DecodeException
+ * If the available data was not valid ASN.1.
+ * @throws IOException
+ * If an unexpected IO error occurred.
+ */
+ byte peekType() throws DecodeException, IOException;
+
+
+
+ /**
+ * Reads the next element as a boolean having the Universal Boolean
+ * ASN.1 type tag.
+ *
+ * @return The decoded boolean value.
+ * @throws DecodeException
+ * If the element cannot be decoded as a boolean.
+ * @throws IOException
+ * If an unexpected IO error occurred.
+ */
+ boolean readBoolean() throws DecodeException, IOException;
+
+
+
+ /**
+ * Reads the next element as a boolean having the provided type tag.
+ *
+ * @param type
+ * The expected type tag of the element.
+ * @return The decoded boolean value.
+ * @throws DecodeException
+ * If the element cannot be decoded as a boolean.
+ * @throws IOException
+ * If an unexpected IO error occurred.
+ */
+ boolean readBoolean(byte type) throws DecodeException, IOException;
+
+
+
+ /**
+ * Finishes reading a sequence and discards any unread elements.
+ *
+ * @throws DecodeException
+ * If an error occurs while advancing to the end of the
+ * sequence.
+ * @throws IOException
+ * If an unexpected IO error occurred.
+ * @throws IllegalStateException
+ * If there is no sequence being read.
+ */
+ void readEndSequence() throws DecodeException, IOException,
+ IllegalStateException;
+
+
+
+ /**
+ * Finishes reading a set and discards any unread elements.
+ *
+ * @throws DecodeException
+ * If an error occurs while advancing to the end of the set.
+ * @throws IOException
+ * If an unexpected IO error occurred.
+ * @throws IllegalStateException
+ * If there is no set being read.
+ */
+ void readEndSet() throws DecodeException, IOException,
+ IllegalStateException;
+
+
+
+ /**
+ * Reads the next element as an enumerated having the Universal
+ * Enumerated ASN.1 type tag.
+ *
+ * @return The decoded enumerated value.
+ * @throws DecodeException
+ * If the element cannot be decoded as an enumerated value.
+ * @throws IOException
+ * If an unexpected IO error occurred.
+ */
+ int readEnumerated() throws DecodeException, IOException;
+
+
+
+ /**
+ * Reads the next element as an enumerated having the provided type
+ * tag.
+ *
+ * @param type
+ * The expected type tag of the element.
+ * @return The decoded enumerated value.
+ * @throws DecodeException
+ * If the element cannot be decoded as an enumerated value.
+ * @throws IOException
+ * If an unexpected IO error occurred.
+ */
+ int readEnumerated(byte type) throws DecodeException, IOException;
+
+
+
+ /**
+ * Reads the next element as an integer having the Universal Integer
+ * ASN.1 type tag.
+ *
+ * @return The decoded integer value.
+ * @throws DecodeException
+ * If the element cannot be decoded as an integer.
+ * @throws IOException
+ * If an unexpected IO error occurred.
+ */
+ long readInteger() throws DecodeException, IOException;
+
+
+
+ /**
+ * Reads the next element as an integer having the provided type tag.
+ *
+ * @param type
+ * The expected type tag of the element.
+ * @return The decoded integer value.
+ * @throws DecodeException
+ * If the element cannot be decoded as an integer.
+ * @throws IOException
+ * If an unexpected IO error occurred.
+ */
+ long readInteger(byte type) throws DecodeException, IOException;
+
+
+
+ /**
+ * Reads the next element as a null element having the Universal Null
+ * ASN.1 type tag.
+ *
+ * @throws DecodeException
+ * If the element cannot be decoded as a null element.
+ * @throws IOException
+ * If an unexpected IO error occurred.
+ */
+ void readNull() throws DecodeException, IOException;
+
+
+
+ /**
+ * Reads the next element as a null element having the provided type
+ * tag.
+ *
+ * @param type
+ * The expected type tag of the element.
+ * @throws DecodeException
+ * If the element cannot be decoded as a null element.
+ * @throws IOException
+ * If an unexpected IO error occurred.
+ */
+ void readNull(byte type) throws DecodeException, IOException;
+
+
+
+ /**
+ * Reads the next element as an octet string having the Universal
+ * Octet String ASN.1 type tag.
+ *
+ * @return The decoded octet string represented using a
+ * {@link ByteString}.
+ * @throws DecodeException
+ * If the element cannot be decoded as an octet string.
+ * @throws IOException
+ * If an unexpected IO error occurred.
+ */
+ ByteString readOctetString() throws DecodeException, IOException;
+
+
+
+ /**
+ * Reads the next element as an octet string having the provided type
+ * tag.
+ *
+ * @param type
+ * The expected type tag of the element.
+ * @return The decoded octet string represented using a
+ * {@link ByteString}.
+ * @throws DecodeException
+ * If the element cannot be decoded as an octet string.
+ * @throws IOException
+ * If an unexpected IO error occurred.
+ */
+ ByteString readOctetString(byte type) throws DecodeException,
+ IOException;
+
+
+
+ /**
+ * Reads the next element as an octet string having the provided type
+ * tag and appends it to the provided {@link ByteStringBuilder}.
+ *
+ * @param type
+ * The expected type tag of the element.
+ * @param builder
+ * The {@link ByteStringBuilder} to append the octet string
+ * to.
+ * @return A reference to {@code builder}.
+ * @throws DecodeException
+ * If the element cannot be decoded as an octet string.
+ * @throws IOException
+ * If an unexpected IO error occurred.
+ */
+ ByteStringBuilder readOctetString(byte type, ByteStringBuilder builder)
+ throws DecodeException, IOException;
+
+
+
+ /**
+ * Reads the next element as an octet string having the Universal
+ * Octet String ASN.1 type tag and appends it to the provided
+ * {@link ByteStringBuilder}.
+ *
+ * @param builder
+ * The {@link ByteStringBuilder} to append the octet string
+ * to.
+ * @return A reference to {@code builder}.
+ * @throws DecodeException
+ * If the element cannot be decoded as an octet string.
+ * @throws IOException
+ * If an unexpected IO error occurred.
+ */
+ ByteStringBuilder readOctetString(ByteStringBuilder builder)
+ throws DecodeException, IOException;
+
+
+
+ /**
+ * Reads the next element as an octet string having the Universal
+ * Octet String ASN.1 type tag and decodes the value as a UTF-8
+ * encoded string.
+ *
+ * @return The decoded octet string as a UTF-8 encoded string.
+ * @throws DecodeException
+ * If the element cannot be decoded as an octet string.
+ * @throws IOException
+ * If an unexpected IO error occurred.
+ */
+ String readOctetStringAsString() throws DecodeException,
+ IOException;
+
+
+
+ /**
+ * Reads the next element as an octet string having the provided type
+ * tag and decodes the value as a UTF-8 encoded string.
+ *
+ * @param type
+ * The expected type tag of the element.
+ * @return The decoded octet string as a UTF-8 encoded string.
+ * @throws DecodeException
+ * If the element cannot be decoded as an octet string.
+ * @throws IOException
+ * If an unexpected IO error occurred.
+ */
+ String readOctetStringAsString(byte type) throws DecodeException,
+ IOException;
+
+
+
+ /**
+ * Reads the next element as a sequence having the Universal Sequence
+ * ASN.1 type tag. All further reads will read the elements in the
+ * sequence until {@link #readEndSequence()} is called.
+ *
+ * @throws DecodeException
+ * If the element cannot be decoded as a sequence.
+ * @throws IOException
+ * If an unexpected IO error occurred.
+ */
+ void readStartSequence() throws DecodeException, IOException;
+
+
+
+ /**
+ * Reads the next element as a sequence having the provided type tag.
+ * All further reads will read the elements in the sequence until
+ * {@link #readEndSequence()} is called.
+ *
+ * @param type
+ * The expected type tag of the element.
+ * @throws DecodeException
+ * If the element cannot be decoded as a sequence.
+ * @throws IOException
+ * If an unexpected IO error occurred.
+ */
+ void readStartSequence(byte type) throws DecodeException,
+ IOException;
+
+
+
+ /**
+ * Reads the next element as a set having the Universal Set ASN.1 type
+ * tag. All further reads will read the elements in the set until
+ * {@link #readEndSet()} is called.
+ *
+ * @throws DecodeException
+ * If the element cannot be decoded as a set.
+ * @throws IOException
+ * If an unexpected IO error occurred.
+ */
+ void readStartSet() throws DecodeException, IOException;
+
+
+
+ /**
+ * Reads the next element as a set having the provided type tag. All
+ * further reads will read the elements in the set until
+ * {@link #readEndSet()} is called.
+ *
+ * @param type
+ * The expected type tag of the element.
+ * @throws DecodeException
+ * If the element cannot be decoded as a set.
+ * @throws IOException
+ * If an unexpected IO error occurred.
+ */
+ void readStartSet(byte type) throws DecodeException, IOException;
+
+
+
+ /**
+ * Skips the next element without decoding it.
+ *
+ * @return A reference to this ASN.1 reader.
+ * @throws DecodeException
+ * If the next element could not be skipped.
+ * @throws IOException
+ * If an unexpected IO error occurred.
+ */
+ ASN1Reader skipElement() throws DecodeException, IOException;
+}
diff --git a/sdk/src/org/opends/sdk/asn1/ASN1Writer.java b/sdk/src/org/opends/sdk/asn1/ASN1Writer.java
new file mode 100644
index 0000000..dae71e0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/asn1/ASN1Writer.java
@@ -0,0 +1,401 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.asn1;
+
+
+
+import java.io.Closeable;
+import java.io.Flushable;
+import java.io.IOException;
+
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * An interface for encoding ASN.1 elements to a data source.
+ * <p>
+ * Methods for creating {@link ASN1Writer}s are provided in the
+ * {@link ASN1} class.
+ */
+public interface ASN1Writer extends Closeable, Flushable
+{
+
+ /**
+ * Closes this ASN.1 writer, flushing it first. Closing a previously
+ * closed ASN.1 writer has no effect. Any unfinished sequences and/or
+ * sets will be ended.
+ *
+ * @throws IOException
+ * If an error occurs while closing.
+ */
+ void close() throws IOException;
+
+
+
+ /**
+ * Flushes this ASN.1 writer so that any buffered elements are written
+ * immediately to their intended destination. Then, if that
+ * destination is another byte stream, flush it. Thus one {@code
+ * flush()} invocation will flush all the buffers in a chain of
+ * streams.
+ * <p>
+ * If the intended destination of this stream is an abstraction
+ * provided by the underlying operating system, for example a file,
+ * then flushing the stream guarantees only that bytes previously
+ * written to the stream are passed to the operating system for
+ * writing; it does not guarantee that they are actually written to a
+ * physical device such as a disk drive.
+ *
+ * @throws IOException
+ * If an error occurs while flushing.
+ */
+ void flush() throws IOException;
+
+
+
+ /**
+ * Writes a boolean element using the Universal Boolean ASN.1 type
+ * tag.
+ *
+ * @param value
+ * The boolean value.
+ * @return A reference to this ASN.1 writer.
+ * @throws IOException
+ * If an error occurs while writing the element.
+ */
+ ASN1Writer writeBoolean(boolean value) throws IOException;
+
+
+
+ /**
+ * Writes a boolean element using the provided type tag.
+ *
+ * @param type
+ * The type tag of the element.
+ * @param value
+ * The boolean value.
+ * @return A reference to this ASN.1 writer.
+ * @throws IOException
+ * If an error occurs while writing the element.
+ */
+ ASN1Writer writeBoolean(byte type, boolean value) throws IOException;
+
+
+
+ /**
+ * Finishes writing a sequence element.
+ *
+ * @return A reference to this ASN.1 writer.
+ * @throws IOException
+ * If an error occurs while writing the element.
+ * @throws IllegalStateException
+ * If there is no sequence being written.
+ */
+ ASN1Writer writeEndSequence() throws IOException,
+ IllegalStateException;
+
+
+
+ /**
+ * Finishes writing a set element.
+ *
+ * @return A reference to this ASN.1 writer.
+ * @throws IOException
+ * If an error occurs while writing the element.
+ * @throws IllegalStateException
+ * If there is no set being written, IllegalStateException.
+ */
+ ASN1Writer writeEndSet() throws IOException;
+
+
+
+ /**
+ * Writes an enumerated element using the provided type tag.
+ *
+ * @param type
+ * The type tag of the element.
+ * @param value
+ * The enumerated value.
+ * @return A reference to this ASN.1 writer.
+ * @throws IOException
+ * If an error occurs while writing the element.
+ */
+ ASN1Writer writeEnumerated(byte type, int value) throws IOException;
+
+
+
+ /**
+ * Writes an enumerated element using the Universal Enumerated ASN.1
+ * type tag.
+ *
+ * @param value
+ * The enumerated value.
+ * @return A reference to this ASN.1 writer.
+ * @throws IOException
+ * If an error occurs while writing the element.
+ */
+ ASN1Writer writeEnumerated(int value) throws IOException;
+
+
+
+ /**
+ * Writes an integer element using the provided type tag.
+ *
+ * @param type
+ * The type tag of the element.
+ * @param value
+ * The integer value.
+ * @return A reference to this ASN.1 writer.
+ * @throws IOException
+ * If an error occurs while writing the element.
+ */
+ ASN1Writer writeInteger(byte type, int value) throws IOException;
+
+
+
+ /**
+ * Writes an integer element using the provided type tag.
+ *
+ * @param type
+ * The type tag of the element.
+ * @param value
+ * The integer value.
+ * @return A reference to this ASN.1 writer.
+ * @throws IOException
+ * If an error occurs while writing the element.
+ */
+ ASN1Writer writeInteger(byte type, long value) throws IOException;
+
+
+
+ /**
+ * Writes an integer element using the Universal Integer ASN.1 type
+ * tag.
+ *
+ * @param value
+ * The integer value.
+ * @return A reference to this ASN.1 writer.
+ * @throws IOException
+ * If an error occurs while writing the element.
+ */
+ ASN1Writer writeInteger(int value) throws IOException;
+
+
+
+ /**
+ * Writes an integer element using the Universal Integer ASN.1 type
+ * tag.
+ *
+ * @param value
+ * The integer value.
+ * @return A reference to this ASN.1 writer.
+ * @throws IOException
+ * If an error occurs while writing the element.
+ */
+ ASN1Writer writeInteger(long value) throws IOException;
+
+
+
+ /**
+ * Writes a null element using the Universal Null ASN.1 type tag.
+ *
+ * @return A reference to this ASN.1 writer.
+ * @throws IOException
+ * If an error occurs while writing the element.
+ */
+ ASN1Writer writeNull() throws IOException;
+
+
+
+ /**
+ * Writes a null element using the provided type tag.
+ *
+ * @param type
+ * The type tag of the element.
+ * @return A reference to this ASN.1 writer.
+ * @throws IOException
+ * If an error occurs while writing the element.
+ */
+ ASN1Writer writeNull(byte type) throws IOException;
+
+
+
+ /**
+ * Writes an octet string element using the provided type tag.
+ *
+ * @param type
+ * The type tag of the element.
+ * @param value
+ * The byte array containing the octet string data.
+ * @param offset
+ * The offset in the byte array.
+ * @param length
+ * The number of bytes to write.
+ * @return A reference to this ASN.1 writer.
+ * @throws IOException
+ * If an error occurs while writing the element.
+ */
+ ASN1Writer writeOctetString(byte type, byte[] value, int offset,
+ int length) throws IOException;
+
+
+
+ /**
+ * Writes an octet string element using the provided type tag.
+ *
+ * @param type
+ * The type tag of the element.
+ * @param value
+ * The octet string value.
+ * @return A reference to this ASN.1 writer.
+ * @throws IOException
+ * If an error occurs while writing the element.
+ */
+ ASN1Writer writeOctetString(byte type, ByteSequence value)
+ throws IOException;
+
+
+
+ /**
+ * Writes a string as a UTF-8 encoded octet string element using the
+ * provided type tag.
+ *
+ * @param type
+ * The type tag of the element.
+ * @param value
+ * The string to be written as a UTF-8 encoded octet string.
+ * @return A reference to this ASN.1 writer.
+ * @throws IOException
+ * If an error occurs while writing the element.
+ */
+ ASN1Writer writeOctetString(byte type, String value)
+ throws IOException;
+
+
+
+ /**
+ * Writes an octet string element using the Universal Octet String
+ * ASN.1 type tag.
+ *
+ * @param value
+ * The byte array containing the octet string data.
+ * @param offset
+ * The offset in the byte array.
+ * @param length
+ * The number of bytes to write.
+ * @return A reference to this ASN.1 writer.
+ * @throws IOException
+ * If an error occurs while writing the element.
+ */
+ ASN1Writer writeOctetString(byte[] value, int offset, int length)
+ throws IOException;
+
+
+
+ /**
+ * Writes an octet string element using the Universal Octet String
+ * ASN.1 type tag.
+ *
+ * @param value
+ * The octet string value.
+ * @return A reference to this ASN.1 writer.
+ * @throws IOException
+ * If an error occurs while writing the element.
+ */
+ ASN1Writer writeOctetString(ByteSequence value) throws IOException;
+
+
+
+ /**
+ * Writes a string as a UTF-8 encoded octet string element using the
+ * Universal Octet String ASN.1 type tag.
+ *
+ * @param value
+ * The string to be written as a UTF-8 encoded octet string.
+ * @return A reference to this ASN.1 writer.
+ * @throws IOException
+ * If an error occurs while writing the element.
+ */
+ ASN1Writer writeOctetString(String value) throws IOException;
+
+
+
+ /**
+ * Writes a sequence element using the Universal Sequence ASN.1 type
+ * tag. All further writes will append elements to the sequence until
+ * {@link #writeEndSequence} is called.
+ *
+ * @return A reference to this ASN.1 writer.
+ * @throws IOException
+ * If an error occurs while writing the element.
+ */
+ ASN1Writer writeStartSequence() throws IOException;
+
+
+
+ /**
+ * Writes a sequence element using the provided type tag. All further
+ * writes will append elements to the sequence until
+ * {@link #writeEndSequence} is called.
+ *
+ * @param type
+ * The type tag of the element.
+ * @return A reference to this ASN.1 writer.
+ * @throws IOException
+ * If an error occurs while writing the element.
+ */
+ ASN1Writer writeStartSequence(byte type) throws IOException;
+
+
+
+ /**
+ * Writes a set element using the Universal Set ASN.1 type tag. All
+ * further writes will append elements to the set until
+ * {@link #writeEndSet} is called.
+ *
+ * @return A reference to this ASN.1 writer.
+ * @throws IOException
+ * If an error occurs while writing the element.
+ */
+ ASN1Writer writeStartSet() throws IOException;
+
+
+
+ /**
+ * Writes a set element using the provided type tag. All further
+ * writes will append elements to the set until {@link #writeEndSet}
+ * is called.
+ *
+ * @param type
+ * The type tag of the element.
+ * @return A reference to this ASN.1 writer.
+ * @throws IOException
+ * If an error occurs while writing the element.
+ */
+ ASN1Writer writeStartSet(byte type) throws IOException;
+}
diff --git a/sdk/src/org/opends/sdk/asn1/AbstractASN1Reader.java b/sdk/src/org/opends/sdk/asn1/AbstractASN1Reader.java
new file mode 100644
index 0000000..131c9ab
--- /dev/null
+++ b/sdk/src/org/opends/sdk/asn1/AbstractASN1Reader.java
@@ -0,0 +1,210 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2008 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.asn1;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_ASN1_UNEXPECTED_TAG;
+import static org.opends.sdk.asn1.ASN1Constants.*;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+
+
+
+/**
+ * An abstract {@code ASN1Reader} which can be used as the basis for
+ * implementing new ASN1 reader implementations.
+ */
+public abstract class AbstractASN1Reader implements ASN1Reader
+{
+ /**
+ * Creates a new abstract ASN.1 reader.
+ */
+ protected AbstractASN1Reader()
+ {
+ // No implementation required.
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean readBoolean(byte type) throws IOException
+ {
+ if (type == 0x00)
+ {
+ type = UNIVERSAL_BOOLEAN_TYPE;
+ }
+ checkType(type);
+ return readBoolean();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int readEnumerated(byte type) throws IOException
+ {
+ if (type == 0x00)
+ {
+ type = UNIVERSAL_ENUMERATED_TYPE;
+ }
+ checkType(type);
+ return readEnumerated();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public long readInteger(byte type) throws IOException
+ {
+ if (type == 0x00)
+ {
+ type = UNIVERSAL_INTEGER_TYPE;
+ }
+ checkType(type);
+ return readInteger();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void readNull(byte type) throws IOException
+ {
+ if (type == 0x00)
+ {
+ type = UNIVERSAL_NULL_TYPE;
+ }
+ checkType(type);
+ readNull();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteString readOctetString(byte type) throws IOException
+ {
+ if (type == 0x00)
+ {
+ type = UNIVERSAL_OCTET_STRING_TYPE;
+ }
+ checkType(type);
+ return readOctetString();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteStringBuilder readOctetString(byte type,
+ ByteStringBuilder builder) throws IOException
+ {
+ if (type == 0x00)
+ {
+ type = UNIVERSAL_OCTET_STRING_TYPE;
+ }
+ checkType(type);
+ readOctetString(builder);
+ return builder;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String readOctetStringAsString(byte type) throws IOException
+ {
+ // We could cache the UTF-8 CharSet if performance proves to be an
+ // issue.
+ if (type == 0x00)
+ {
+ type = UNIVERSAL_OCTET_STRING_TYPE;
+ }
+ checkType(type);
+ return readOctetStringAsString();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void readStartSequence(byte type) throws IOException
+ {
+ if (type == 0x00)
+ {
+ type = UNIVERSAL_SEQUENCE_TYPE;
+ }
+ checkType(type);
+ readStartSequence();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void readStartSet(byte type) throws IOException
+ {
+ // From an implementation point of view, a set is equivalent to a
+ // sequence.
+ if (type == 0x00)
+ {
+ type = UNIVERSAL_SET_TYPE;
+ }
+ checkType(type);
+ readStartSet();
+ }
+
+
+
+ private void checkType(byte expectedType) throws IOException
+ {
+ if (peekType() != expectedType)
+ {
+ Message message = ERR_ASN1_UNEXPECTED_TAG.get(expectedType,
+ peekType());
+ throw DecodeException.fatalError(message);
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/asn1/AbstractASN1Writer.java b/sdk/src/org/opends/sdk/asn1/AbstractASN1Writer.java
new file mode 100644
index 0000000..662043c
--- /dev/null
+++ b/sdk/src/org/opends/sdk/asn1/AbstractASN1Writer.java
@@ -0,0 +1,157 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.asn1;
+
+
+
+import static org.opends.sdk.asn1.ASN1Constants.*;
+
+import java.io.IOException;
+
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * An abstract {@code ASN1Writer} which can be used as the basis for
+ * implementing new ASN1 writer implementations.
+ */
+public abstract class AbstractASN1Writer implements ASN1Writer
+{
+
+ /**
+ * Creates a new abstract ASN.1 writer.
+ */
+ protected AbstractASN1Writer()
+ {
+ // No implementation required.
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeBoolean(boolean value) throws IOException
+ {
+ return writeBoolean(UNIVERSAL_BOOLEAN_TYPE, value);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeEnumerated(int value) throws IOException
+ {
+ return writeEnumerated(UNIVERSAL_ENUMERATED_TYPE, value);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeInteger(int value) throws IOException
+ {
+ return writeInteger(UNIVERSAL_INTEGER_TYPE, value);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeInteger(long value) throws IOException
+ {
+ return writeInteger(UNIVERSAL_INTEGER_TYPE, value);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeNull() throws IOException
+ {
+ return writeNull(UNIVERSAL_NULL_TYPE);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeOctetString(byte[] value, int offset,
+ int length) throws IOException
+ {
+ return writeOctetString(UNIVERSAL_OCTET_STRING_TYPE, value, offset,
+ length);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeOctetString(ByteSequence value)
+ throws IOException
+ {
+ return writeOctetString(UNIVERSAL_OCTET_STRING_TYPE, value);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeOctetString(String value) throws IOException
+ {
+ return writeOctetString(UNIVERSAL_OCTET_STRING_TYPE, value);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeStartSequence() throws IOException
+ {
+ return writeStartSequence(UNIVERSAL_SEQUENCE_TYPE);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeStartSet() throws IOException
+ {
+ return writeStartSet(UNIVERSAL_SET_TYPE);
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/controls/AccountUsabilityControl.java b/sdk/src/org/opends/sdk/controls/AccountUsabilityControl.java
new file mode 100644
index 0000000..5b6bb74
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/AccountUsabilityControl.java
@@ -0,0 +1,691 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_ACCTUSABLEREQ_CONTROL_HAS_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_ACCTUSABLERES_DECODE_ERROR;
+import static org.opends.messages.ProtocolMessages.ERR_ACCTUSABLERES_NO_CONTROL_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_ACCTUSABLERES_UNKNOWN_VALUE_ELEMENT_TYPE;
+import static org.opends.sdk.util.StaticUtils.byteToHex;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * This class implements the Sun-defined account usable control.
+ */
+public class AccountUsabilityControl
+{
+ /**
+ * The OID for the account usable request and response controls.
+ */
+ public static final String OID_ACCOUNT_USABLE_CONTROL = "1.3.6.1.4.1.42.2.27.9.5.8";
+
+
+
+ /**
+ * This class implements the Sun-defined account usable request
+ * control. The OID for this control is 1.3.6.1.4.1.42.2.27.9.5.8, and
+ * it does not have a value.
+ */
+ public static class Request extends Control
+ {
+ public Request()
+ {
+ super(OID_ACCOUNT_USABLE_CONTROL, false);
+ }
+
+
+
+ public Request(boolean isCritical)
+ {
+ super(OID_ACCOUNT_USABLE_CONTROL, isCritical);
+ }
+
+
+
+ @Override
+ public ByteString getValue()
+ {
+ return null;
+ }
+
+
+
+ @Override
+ public boolean hasValue()
+ {
+ return false;
+ }
+
+
+
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("AccountUsableRequestControl(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+ buffer.append(")");
+ }
+ }
+
+
+
+ /**
+ * This class implements the account usable response control. This is
+ * a Sun-defined control with OID 1.3.6.1.4.1.42.2.27.9.5.8. The value
+ * of this control is composed according to the following BNF: <BR>
+ *
+ * <PRE>
+ * ACCOUNT_USABLE_RESPONSE ::= CHOICE {
+ * is_available [0] INTEGER, -- Seconds before expiration --
+ * is_not_available [1] MORE_INFO }
+ * MORE_INFO ::= SEQUENCE {
+ * inactive [0] BOOLEAN DEFAULT FALSE,
+ * reset [1] BOOLEAN DEFAULT FALSE,
+ * expired [2] BOOLEAN DEFAULT_FALSE,
+ * remaining_grace [3] INTEGER OPTIONAL,
+ * seconds_before_unlock [4] INTEGER OPTIONAL }
+ * </PRE>
+ */
+ public static class Response extends Control
+ {
+ // Indicates whether the user's account is usable.
+ private final boolean isUsable;
+
+ // Indicates whether the user's password is expired.
+ private final boolean isExpired;
+
+ // Indicates whether the user's account is inactive.
+ private final boolean isInactive;
+
+ // Indicates whether the user's account is currently locked.
+ private final boolean isLocked;
+
+ // Indicates whether the user's password has been reset and must be
+ // changed
+ // before anything else can be done.
+ private final boolean isReset;
+
+ // The number of remaining grace logins, if available.
+ private final int remainingGraceLogins;
+
+ // The length of time in seconds before the user's password expires,
+ // if
+ // available.
+ private final int secondsBeforeExpiration;
+
+ // The length of time before the user's account is unlocked, if
+ // available.
+ private final int secondsBeforeUnlock;
+
+
+
+ /**
+ * Creates a new account usability response control that may be used
+ * to indicate that the account is not available and provide
+ * information about the underlying reason. It will use the default
+ * OID and criticality.
+ *
+ * @param isCritical
+ * Indicates whether this control should be considered
+ * critical in processing the request.
+ * @param isInactive
+ * Indicates whether the user's account has been
+ * inactivated by an administrator.
+ * @param isReset
+ * Indicates whether the user's password has been reset by
+ * an administrator.
+ * @param isExpired
+ * Indicates whether the user's password is expired.
+ * @param remainingGraceLogins
+ * The number of grace logins remaining. A value of zero
+ * indicates that there are none remaining. A value of -1
+ * indicates that grace login functionality is not enabled.
+ * @param isLocked
+ * Indicates whether the user's account is currently locked
+ * out.
+ * @param secondsBeforeUnlock
+ * The length of time in seconds until the account is
+ * unlocked. A value of -1 indicates that the account will
+ * not be automatically unlocked and must be reset by an
+ * administrator.
+ */
+ public Response(boolean isCritical, boolean isInactive,
+ boolean isReset, boolean isExpired, int remainingGraceLogins,
+ boolean isLocked, int secondsBeforeUnlock)
+ {
+ super(OID_ACCOUNT_USABLE_CONTROL, isCritical);
+
+ this.isInactive = isInactive;
+ this.isReset = isReset;
+ this.isExpired = isExpired;
+ this.remainingGraceLogins = remainingGraceLogins;
+ this.isLocked = isLocked;
+ this.secondsBeforeUnlock = secondsBeforeUnlock;
+
+ isUsable = false;
+ secondsBeforeExpiration = -1;
+ }
+
+
+
+ /**
+ * Creates a new account usability response control that may be used
+ * to indicate that the account is not available and provide
+ * information about the underlying reason. It will use the default
+ * OID and criticality.
+ *
+ * @param isInactive
+ * Indicates whether the user's account has been
+ * inactivated by an administrator.
+ * @param isReset
+ * Indicates whether the user's password has been reset by
+ * an administrator.
+ * @param isExpired
+ * Indicates whether the user's password is expired.
+ * @param remainingGraceLogins
+ * The number of grace logins remaining. A value of zero
+ * indicates that there are none remaining. A value of -1
+ * indicates that grace login functionality is not enabled.
+ * @param isLocked
+ * Indicates whether the user's account is currently locked
+ * out.
+ * @param secondsBeforeUnlock
+ * The length of time in seconds until the account is
+ * unlocked. A value of -1 indicates that the account will
+ * not be automatically unlocked and must be reset by an
+ * administrator.
+ */
+ public Response(boolean isInactive, boolean isReset,
+ boolean isExpired, int remainingGraceLogins, boolean isLocked,
+ int secondsBeforeUnlock)
+ {
+ this(false, isInactive, isReset, isExpired, remainingGraceLogins,
+ isLocked, secondsBeforeUnlock);
+ }
+
+
+
+ /**
+ * Creates a new account usability response control that may be used
+ * to indicate that the account is available and provide the number
+ * of seconds until expiration. It will use the default OID and
+ * criticality.
+ *
+ * @param isCritical
+ * Indicates whether this control should be considered
+ * critical in processing the request.
+ * @param secondsBeforeExpiration
+ * The length of time in seconds until the user's password
+ * expires, or -1 if the user's password will not expire or
+ * the expiration time is unknown.
+ */
+ public Response(boolean isCritical, int secondsBeforeExpiration)
+ {
+ super(OID_ACCOUNT_USABLE_CONTROL, isCritical);
+
+ this.secondsBeforeExpiration = secondsBeforeExpiration;
+
+ isUsable = true;
+ isInactive = false;
+ isReset = false;
+ isExpired = false;
+ remainingGraceLogins = -1;
+ isLocked = false;
+ secondsBeforeUnlock = 0;
+ }
+
+
+
+ /**
+ * Creates a new account usability response control that may be used
+ * to indicate that the account is available and provide the number
+ * of seconds until expiration. It will use the default OID and
+ * criticality.
+ *
+ * @param secondsBeforeExpiration
+ * The length of time in seconds until the user's password
+ * expires, or -1 if the user's password will not expire or
+ * the expiration time is unknown.
+ */
+ public Response(int secondsBeforeExpiration)
+ {
+ this(false, secondsBeforeExpiration);
+ }
+
+
+
+ /**
+ * Retrieves the number of remaining grace logins for the user. This
+ * value is unreliable if the user's password is not expired.
+ *
+ * @return The number of remaining grace logins for the user, or -1
+ * if the grace logins feature is not enabled for the user.
+ */
+ public int getRemainingGraceLogins()
+ {
+ return remainingGraceLogins;
+ }
+
+
+
+ /**
+ * Retrieves the length of time in seconds before the user's
+ * password expires. This value is unreliable if the account is not
+ * available.
+ *
+ * @return The length of time in seconds before the user's password
+ * expires, or -1 if it is unknown or password expiration is
+ * not enabled for the user.
+ */
+ public int getSecondsBeforeExpiration()
+ {
+ return secondsBeforeExpiration;
+ }
+
+
+
+ /**
+ * Retrieves the length of time in seconds before the user's account
+ * is automatically unlocked. This value is unreliable is the user's
+ * account is not locked.
+ *
+ * @return The length of time in seconds before the user's account
+ * is automatically unlocked, or -1 if it requires
+ * administrative action to unlock the account.
+ */
+ public int getSecondsBeforeUnlock()
+ {
+ return secondsBeforeUnlock;
+ }
+
+
+
+ @Override
+ public ByteString getValue()
+ {
+ ByteStringBuilder buffer = new ByteStringBuilder();
+ ASN1Writer writer = ASN1.getWriter(buffer);
+ try
+ {
+ writeValue(writer);
+ return buffer.toByteString();
+ }
+ catch (IOException ioe)
+ {
+ // This should never happen unless there is a bug somewhere.
+ throw new RuntimeException(ioe);
+ }
+ }
+
+
+
+ @Override
+ public boolean hasValue()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether the user's password is expired.
+ *
+ * @return <CODE>true</CODE> if the user's password is expired, or
+ * <CODE>false</CODE> if not.
+ */
+ public boolean isExpired()
+ {
+ return isExpired;
+ }
+
+
+
+ /**
+ * Indicates whether the user's account has been inactivated by an
+ * administrator.
+ *
+ * @return <CODE>true</CODE> if the user's account has been
+ * inactivated by an administrator, or <CODE>false</CODE> if
+ * not.
+ */
+ public boolean isInactive()
+ {
+ return isInactive;
+ }
+
+
+
+ /**
+ * Indicates whether the user's account is locked for some reason.
+ *
+ * @return <CODE>true</CODE> if the user's account is locked, or
+ * <CODE>false</CODE> if it is not.
+ */
+ public boolean isLocked()
+ {
+ return isLocked;
+ }
+
+
+
+ /**
+ * Indicates whether the user's password has been administratively
+ * reset and the user must change that password before any other
+ * operations will be allowed.
+ *
+ * @return <CODE>true</CODE> if the user's password has been
+ * administratively reset, or <CODE>false</CODE> if not.
+ */
+ public boolean isReset()
+ {
+ return isReset;
+ }
+
+
+
+ /**
+ * Indicates whether the associated user account is available for
+ * use.
+ *
+ * @return <CODE>true</CODE> if the associated user account is
+ * available, or <CODE>false</CODE> if not.
+ */
+ public boolean isUsable()
+ {
+ return isUsable;
+ }
+
+
+
+ /**
+ * Appends a string representation of this password policy response
+ * control to the provided buffer.
+ *
+ * @param buffer
+ * The buffer to which the information should be appended.
+ */
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("AccountUsableResponseControl(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+ buffer.append(", isUsable=");
+ buffer.append(isUsable);
+ if (isUsable)
+ {
+ buffer.append(",secondsBeforeExpiration=");
+ buffer.append(secondsBeforeExpiration);
+ }
+ else
+ {
+ buffer.append(",isInactive=");
+ buffer.append(isInactive);
+ buffer.append(",isReset=");
+ buffer.append(isReset);
+ buffer.append(",isExpired=");
+ buffer.append(isExpired);
+ buffer.append(",remainingGraceLogins=");
+ buffer.append(remainingGraceLogins);
+ buffer.append(",isLocked=");
+ buffer.append(isLocked);
+ buffer.append(",secondsBeforeUnlock=");
+ buffer.append(secondsBeforeUnlock);
+ }
+
+ buffer.append(")");
+ }
+
+
+
+ /**
+ * Writes this control's value to an ASN.1 writer.
+ *
+ * @param writer
+ * The ASN.1 output stream to write to.
+ * @throws IOException
+ * If a problem occurs while writing to the stream.
+ */
+ public void writeValue(ASN1Writer writer) throws IOException
+ {
+ if (secondsBeforeExpiration < 0)
+ {
+ writer.writeInteger(TYPE_SECONDS_BEFORE_EXPIRATION,
+ secondsBeforeExpiration);
+ }
+ else
+ {
+ writer.writeStartSequence(TYPE_MORE_INFO);
+ if (isInactive)
+ {
+ writer.writeBoolean(TYPE_INACTIVE, true);
+ }
+
+ if (isReset)
+ {
+ writer.writeBoolean(TYPE_RESET, true);
+ }
+
+ if (isExpired)
+ {
+ writer.writeBoolean(TYPE_EXPIRED, true);
+
+ if (remainingGraceLogins >= 0)
+ {
+ writer.writeInteger(TYPE_REMAINING_GRACE_LOGINS,
+ remainingGraceLogins);
+ }
+ }
+
+ if (isLocked)
+ {
+ writer.writeInteger(TYPE_SECONDS_BEFORE_UNLOCK,
+ secondsBeforeUnlock);
+ }
+ writer.writeEndSequence();
+ }
+ }
+ }
+
+
+
+ /**
+ * ControlDecoder implentation to decode this control from a
+ * ByteString.
+ */
+ private static final class RequestDecoder implements
+ ControlDecoder<Request>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public Request decode(boolean isCritical, ByteString value, Schema schema)
+ throws DecodeException
+ {
+ if (value != null)
+ {
+ Message message = ERR_ACCTUSABLEREQ_CONTROL_HAS_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ return new Request(isCritical);
+ }
+
+
+
+ public String getOID()
+ {
+ return OID_ACCOUNT_USABLE_CONTROL;
+ }
+
+ }
+
+
+
+ /**
+ * ControlDecoder implentation to decode this control from a
+ * ByteString.
+ */
+ private final static class ResponseDecoder implements
+ ControlDecoder<Response>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public Response decode(boolean isCritical, ByteString value, Schema schema)
+ throws DecodeException
+ {
+ if (value == null)
+ {
+ // The response control must always have a value.
+ Message message = ERR_ACCTUSABLERES_NO_CONTROL_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ try
+ {
+ ASN1Reader reader = ASN1.getReader(value);
+ switch (reader.peekType())
+ {
+ case TYPE_SECONDS_BEFORE_EXPIRATION:
+ int secondsBeforeExpiration = (int) reader.readInteger();
+ return new Response(isCritical, secondsBeforeExpiration);
+ case TYPE_MORE_INFO:
+ boolean isInactive = false;
+ boolean isReset = false;
+ boolean isExpired = false;
+ boolean isLocked = false;
+ int remainingGraceLogins = -1;
+ int secondsBeforeUnlock = 0;
+
+ reader.readStartSequence();
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_INACTIVE))
+ {
+ isInactive = reader.readBoolean();
+ }
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_RESET))
+ {
+ isReset = reader.readBoolean();
+ }
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_EXPIRED))
+ {
+ isExpired = reader.readBoolean();
+ }
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_REMAINING_GRACE_LOGINS))
+ {
+ remainingGraceLogins = (int) reader.readInteger();
+ }
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_SECONDS_BEFORE_UNLOCK))
+ {
+ isLocked = true;
+ secondsBeforeUnlock = (int) reader.readInteger();
+ }
+ reader.readEndSequence();
+
+ return new Response(isCritical, isInactive, isReset,
+ isExpired, remainingGraceLogins, isLocked,
+ secondsBeforeUnlock);
+
+ default:
+ Message message = ERR_ACCTUSABLERES_UNKNOWN_VALUE_ELEMENT_TYPE
+ .get(byteToHex(reader.peekType()));
+ throw DecodeException.error(message);
+ }
+ }
+ catch (IOException e)
+ {
+ StaticUtils.DEBUG_LOG.throwing(
+ "AccountUsabilityControl.ResponseDecoder", "decode", e);
+
+ Message message = ERR_ACCTUSABLERES_DECODE_ERROR
+ .get(getExceptionMessage(e));
+ throw DecodeException.error(message);
+ }
+ }
+
+
+
+ public String getOID()
+ {
+ return OID_ACCOUNT_USABLE_CONTROL;
+ }
+
+ }
+
+
+
+ /**
+ * The BER type to use for the seconds before expiration when the
+ * account is available.
+ */
+ private static final byte TYPE_SECONDS_BEFORE_EXPIRATION = (byte) 0x80;
+
+ /**
+ * The BER type to use for the MORE_INFO sequence when the account is
+ * not available.
+ */
+ private static final byte TYPE_MORE_INFO = (byte) 0xA1;
+
+ /**
+ * The BER type to use for the MORE_INFO element that indicates that
+ * the account has been inactivated.
+ */
+ private static final byte TYPE_INACTIVE = (byte) 0x80;
+
+ /**
+ * The BER type to use for the MORE_INFO element that indicates that
+ * the password has been administratively reset.
+ */
+ private static final byte TYPE_RESET = (byte) 0x81;
+
+ /**
+ * The BER type to use for the MORE_INFO element that indicates that
+ * the user's password is expired.
+ */
+ private static final byte TYPE_EXPIRED = (byte) 0x82;
+
+ /**
+ * The BER type to use for the MORE_INFO element that provides the
+ * number of remaining grace logins.
+ */
+ private static final byte TYPE_REMAINING_GRACE_LOGINS = (byte) 0x83;
+
+ /**
+ * The BER type to use for the MORE_INFO element that indicates that
+ * the password has been administratively reset.
+ */
+ private static final byte TYPE_SECONDS_BEFORE_UNLOCK = (byte) 0x84;
+
+ /**
+ * The Control Decoder that can be used to decode the request control.
+ */
+ public static final ControlDecoder<Request> REQUEST_DECODER = new RequestDecoder();
+
+ /**
+ * The Control Decoder that can be used to decode the response
+ * control.
+ */
+ public static final ControlDecoder<Response> RESPONSE_DECODER = new ResponseDecoder();
+}
diff --git a/sdk/src/org/opends/sdk/controls/AssertionControl.java b/sdk/src/org/opends/sdk/controls/AssertionControl.java
new file mode 100644
index 0000000..76e8dee
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/AssertionControl.java
@@ -0,0 +1,201 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_LDAPASSERT_INVALID_CONTROL_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_LDAPASSERT_NO_CONTROL_VALUE;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.Filter;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.ldap.LDAPUtils;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Assertion control.
+ */
+public class AssertionControl extends Control
+{
+ /**
+ * The IANA-assigned OID for the LDAP assertion control.
+ */
+ public static final String OID_LDAP_ASSERTION = "1.3.6.1.1.12";
+
+
+
+ /**
+ * Decodes a assertion control from a byte string.
+ */
+ private final static class Decoder implements
+ ControlDecoder<AssertionControl>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public AssertionControl decode(boolean isCritical, ByteString value, Schema schema)
+ throws DecodeException
+ {
+ if (value == null)
+ {
+ Message message = ERR_LDAPASSERT_NO_CONTROL_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ ASN1Reader reader = ASN1.getReader(value);
+ Filter filter;
+ try
+ {
+ filter = LDAPUtils.decodeFilter(reader);
+ }
+ catch (IOException e)
+ {
+ throw DecodeException.error(
+ ERR_LDAPASSERT_INVALID_CONTROL_VALUE
+ .get(getExceptionMessage(e)), e);
+ }
+
+ return new AssertionControl(isCritical, filter);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getOID()
+ {
+ return OID_LDAP_ASSERTION;
+ }
+
+ }
+
+
+
+ /**
+ * A control decoder which can be used to decode assertion controls.
+ */
+ public static final ControlDecoder<AssertionControl> DECODER = new Decoder();
+
+ // The assertion filter.
+ private final Filter filter;
+
+
+
+ /**
+ * Creates a new assertion using the default OID and the provided
+ * criticality and assertion filter.
+ *
+ * @param isCritical
+ * Indicates whether this control should be considered
+ * critical to the operation processing.
+ * @param filter
+ * The assertion filter.
+ */
+ public AssertionControl(boolean isCritical, Filter filter)
+ {
+ super(OID_LDAP_ASSERTION, isCritical);
+
+ Validator.ensureNotNull(filter);
+ this.filter = filter;
+ }
+
+
+
+ /**
+ * Returns the assertion filter.
+ *
+ * @return The assertion filter.
+ */
+ public Filter getFilter()
+ {
+ return filter;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ByteString getValue()
+ {
+ ByteStringBuilder buffer = new ByteStringBuilder();
+ ASN1Writer writer = ASN1.getWriter(buffer);
+ try
+ {
+ LDAPUtils.encodeFilter(writer, filter);
+ return buffer.toByteString();
+ }
+ catch (IOException ioe)
+ {
+ // This should never happen unless there is a bug somewhere.
+ throw new RuntimeException(ioe);
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean hasValue()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("AssertionControl(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+ buffer.append(", filter=\"");
+ filter.toString(buffer);
+ buffer.append("\")");
+ }
+}
diff --git a/sdk/src/org/opends/sdk/controls/AuthorizationIdentityControl.java b/sdk/src/org/opends/sdk/controls/AuthorizationIdentityControl.java
new file mode 100644
index 0000000..ddd3260
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/AuthorizationIdentityControl.java
@@ -0,0 +1,295 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_AUTHZIDREQ_CONTROL_HAS_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_AUTHZIDRESP_NO_CONTROL_VALUE;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DN;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class implements the authorization identity control as defined
+ * in RFC 3829.
+ */
+public class AuthorizationIdentityControl
+{
+ /**
+ * The OID for the authorization identity request control.
+ */
+ public static final String OID_AUTHZID_REQUEST = "2.16.840.1.113730.3.4.16";
+
+ /**
+ * The OID for the authorization identity response control.
+ */
+ public static final String OID_AUTHZID_RESPONSE = "2.16.840.1.113730.3.4.15";
+
+
+
+ /**
+ * This class implements the authorization identity request control as
+ * defined in RFC 3829.
+ */
+ public static class Request extends Control
+ {
+ public Request()
+ {
+ super(OID_AUTHZID_RESPONSE, false);
+ }
+
+
+
+ public Request(boolean isCritical)
+ {
+ super(OID_AUTHZID_RESPONSE, isCritical);
+ }
+
+
+
+ @Override
+ public ByteString getValue()
+ {
+ return null;
+ }
+
+
+
+ @Override
+ public boolean hasValue()
+ {
+ return false;
+ }
+
+
+
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("AuthorizationIdentityRequestControl(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+ buffer.append(")");
+ }
+ }
+
+
+
+ public static class Response extends Control
+ {
+ // The authorization ID for this control.
+ private String authorizationID;
+
+
+
+ /**
+ * Creates a new authorization identity response control with the
+ * provided information.
+ *
+ * @param isCritical
+ * Indicates whether this control should be considered
+ * critical in processing the request.
+ * @param authorizationDN
+ * The authorization DN for this control.
+ */
+ public Response(boolean isCritical, DN authorizationDN)
+ {
+ super(OID_AUTHZID_REQUEST, isCritical);
+
+ Validator.ensureNotNull(authorizationDN);
+ if (authorizationDN == null)
+ {
+ this.authorizationID = "dn:";
+ }
+ else
+ {
+ this.authorizationID = "dn:" + authorizationDN.toString();
+ }
+ }
+
+
+
+ /**
+ * Creates a new authorization identity response control with the
+ * provided information.
+ *
+ * @param isCritical
+ * Indicates whether this control should be considered
+ * critical in processing the request.
+ * @param authorizationID
+ * The authorization ID for this control.
+ */
+ public Response(boolean isCritical, String authorizationID)
+ {
+ super(OID_AUTHZID_RESPONSE, isCritical);
+
+ Validator.ensureNotNull(authorizationID);
+ this.authorizationID = authorizationID;
+ }
+
+
+
+ /**
+ * Creates a new authorization identity response control with the
+ * provided information.
+ *
+ * @param authorizationDN
+ * The authorization DN for this control.
+ */
+ public Response(DN authorizationDN)
+ {
+ this(false, authorizationDN);
+ }
+
+
+
+ /**
+ * Creates a new authorization identity response control with the
+ * provided information.
+ *
+ * @param authorizationID
+ * The authorization ID for this control.
+ */
+ public Response(String authorizationID)
+ {
+ this(false, authorizationID);
+ }
+
+
+
+ /**
+ * Retrieves the authorization ID for this authorization identity
+ * response control.
+ *
+ * @return The authorization ID for this authorization identity
+ * response control.
+ */
+ public String getAuthorizationID()
+ {
+ return authorizationID;
+ }
+
+
+
+ @Override
+ public ByteString getValue()
+ {
+ return ByteString.valueOf(authorizationID);
+ }
+
+
+
+ @Override
+ public boolean hasValue()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Appends a string representation of this authorization identity
+ * response control to the provided buffer.
+ *
+ * @param buffer
+ * The buffer to which the information should be appended.
+ */
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("AuthorizationIdentityResponseControl(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+ buffer.append(", authzID=\"");
+ buffer.append(authorizationID);
+ buffer.append("\")");
+ }
+
+ }
+
+
+
+ /**
+ * ControlDecoder implentation to decode this control from a
+ * ByteString.
+ */
+ private static final class RequestDecoder implements
+ ControlDecoder<Request>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public Request decode(boolean isCritical, ByteString value, Schema schema)
+ throws DecodeException
+ {
+ if (value != null)
+ {
+ Message message = ERR_AUTHZIDREQ_CONTROL_HAS_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ return new Request(isCritical);
+ }
+
+
+
+ public String getOID()
+ {
+ return OID_AUTHZID_REQUEST;
+ }
+ }
+
+
+
+ /**
+ * ControlDecoder implentation to decode this control from a
+ * ByteString.
+ */
+ private final static class ResponseDecoder implements
+ ControlDecoder<Response>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public Response decode(boolean isCritical, ByteString value, Schema schema)
+ throws DecodeException
+ {
+ if (value == null)
+ {
+ Message message = ERR_AUTHZIDRESP_NO_CONTROL_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ String authID = value.toString();
+ return new Response(isCritical, authID);
+
+ }
+
+
+
+ public String getOID()
+ {
+ return OID_AUTHZID_RESPONSE;
+ }
+ }
+
+
+
+ /**
+ * The Control Decoder that can be used to decode the request control.
+ */
+ public static final ControlDecoder<Request> REQUEST_DECODER = new RequestDecoder();
+
+ /**
+ * The Control Decoder that can be used to decode the response
+ * control.
+ */
+ public static final ControlDecoder<Response> RESPONSE_DECODER = new ResponseDecoder();
+}
diff --git a/sdk/src/org/opends/sdk/controls/Control.java b/sdk/src/org/opends/sdk/controls/Control.java
new file mode 100644
index 0000000..5f9af74
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/Control.java
@@ -0,0 +1,80 @@
+package org.opends.sdk.controls;
+
+
+
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * Created by IntelliJ IDEA. User: boli Date: Jun 29, 2009 Time:
+ * 10:59:19 AM To change this template use File | Settings | File
+ * Templates.
+ */
+public abstract class Control
+{
+ // The criticality for this control.
+ protected final boolean isCritical;
+
+ // The OID for this control.
+ protected final String oid;
+
+
+
+ public Control(String oid, boolean isCritical)
+ {
+ this.isCritical = isCritical;
+ this.oid = oid;
+ }
+
+
+
+ /**
+ * Retrieves the OID for this control.
+ *
+ * @return The OID for this control.
+ */
+ public String getOID()
+ {
+ return oid;
+ }
+
+
+
+ public abstract ByteString getValue();
+
+
+
+ public abstract boolean hasValue();
+
+
+
+ /**
+ * Indicates whether this control should be considered critical in
+ * processing the request.
+ *
+ * @return <CODE>true</CODE> if this code should be considered
+ * critical, or <CODE>false</CODE> if not.
+ */
+ public boolean isCritical()
+ {
+ return isCritical;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ StringBuilder buffer = new StringBuilder();
+ toString(buffer);
+ return buffer.toString();
+ }
+
+
+
+ public abstract void toString(StringBuilder buffer);
+}
diff --git a/sdk/src/org/opends/sdk/controls/ControlDecoder.java b/sdk/src/org/opends/sdk/controls/ControlDecoder.java
new file mode 100644
index 0000000..f853361
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/ControlDecoder.java
@@ -0,0 +1,72 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2008 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.controls;
+
+
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * An interface for decoding controls.
+ *
+ * @param <T>
+ * The type of control decoded by this decoder.
+ */
+public interface ControlDecoder<T extends Control>
+{
+
+ /**
+ * Decodes the provided control.
+ *
+ * @param isCritical
+ * Indicates whether the control should be considered
+ * critical.
+ * @param value
+ * The value for the control.
+ * @param schema
+ * The schema which should be used when decoding the control,
+ * if required.
+ * @return The decoded control.
+ * @throws DecodeException
+ * If the control could not be decoded.
+ */
+ T decode(boolean isCritical, ByteString value, Schema schema)
+ throws DecodeException;
+
+
+
+ /**
+ * Gets the OID of the control decoded by this decoded.
+ *
+ * @return The OID of the control decoded by this decoded.
+ */
+ String getOID();
+}
diff --git a/sdk/src/org/opends/sdk/controls/EntryChangeNotificationControl.java b/sdk/src/org/opends/sdk/controls/EntryChangeNotificationControl.java
new file mode 100644
index 0000000..90b9c02
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/EntryChangeNotificationControl.java
@@ -0,0 +1,396 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_ECN_CANNOT_DECODE_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_ECN_ILLEGAL_PREVIOUS_DN;
+import static org.opends.messages.ProtocolMessages.ERR_ECN_NO_CONTROL_VALUE;
+import static org.opends.sdk.asn1.ASN1Constants.UNIVERSAL_INTEGER_TYPE;
+import static org.opends.sdk.asn1.ASN1Constants.UNIVERSAL_OCTET_STRING_TYPE;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DN;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class implements the entry change notification control defined
+ * in draft-ietf-ldapext-psearch. It may be included in entries returned
+ * in response to a persistent search operation.
+ */
+public class EntryChangeNotificationControl extends Control
+{
+ /**
+ * The OID for the entry change notification control.
+ */
+ public static final String OID_ENTRY_CHANGE_NOTIFICATION = "2.16.840.1.113730.3.4.7";
+
+
+
+ /**
+ * ControlDecoder implentation to decode this control from a
+ * ByteString.
+ */
+ private final static class Decoder implements
+ ControlDecoder<EntryChangeNotificationControl>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public EntryChangeNotificationControl decode(boolean isCritical,
+ ByteString value, Schema schema) throws DecodeException
+ {
+ if (value == null)
+ {
+ Message message = ERR_ECN_NO_CONTROL_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ String previousDN = null;
+ long changeNumber = -1;
+ PersistentSearchChangeType changeType;
+ ASN1Reader reader = ASN1.getReader(value);
+ try
+ {
+ reader.readStartSequence();
+ changeType = PersistentSearchChangeType.valueOf(reader
+ .readEnumerated());
+
+ if (reader.hasNextElement()
+ && (reader.peekType() == UNIVERSAL_OCTET_STRING_TYPE))
+ {
+ if (changeType != PersistentSearchChangeType.MODIFY_DN)
+ {
+ Message message = ERR_ECN_ILLEGAL_PREVIOUS_DN.get(String
+ .valueOf(changeType));
+ throw DecodeException.error(message);
+ }
+
+ previousDN = reader.readOctetStringAsString();
+ }
+ if (reader.hasNextElement()
+ && (reader.peekType() == UNIVERSAL_INTEGER_TYPE))
+ {
+ changeNumber = reader.readInteger();
+ }
+ }
+ catch (IOException e)
+ {
+ StaticUtils.DEBUG_LOG.throwing(
+ "EntryChangeNotificationControl.Decoder", "decode", e);
+
+ Message message = ERR_ECN_CANNOT_DECODE_VALUE
+ .get(getExceptionMessage(e));
+ throw DecodeException.error(message, e);
+ }
+
+ return new EntryChangeNotificationControl(isCritical, changeType,
+ previousDN, changeNumber);
+ }
+
+
+
+ public String getOID()
+ {
+ return OID_ENTRY_CHANGE_NOTIFICATION;
+ }
+
+ }
+
+
+
+ /**
+ * The Control Decoder that can be used to decode this control.
+ */
+ public static final ControlDecoder<EntryChangeNotificationControl> DECODER = new Decoder();
+
+ // The previous DN for this change notification control.
+ private String previousDN;
+
+ // The change number for this change notification control.
+ private long changeNumber;
+
+ // The change type for this change notification control.
+ private final PersistentSearchChangeType changeType;
+
+
+
+ /**
+ * Creates a new entry change notification control with the provided
+ * information.
+ *
+ * @param isCritical
+ * Indicates whether this control should be considered
+ * critical in processing the request.
+ * @param changeType
+ * The change type for this change notification control.
+ */
+ public EntryChangeNotificationControl(boolean isCritical,
+ PersistentSearchChangeType changeType)
+ {
+ super(OID_ENTRY_CHANGE_NOTIFICATION, isCritical);
+
+ Validator.ensureNotNull(changeType);
+ this.changeType = changeType;
+
+ previousDN = null;
+ }
+
+
+
+ /**
+ * Creates a new entry change notification control with the provided
+ * information.
+ *
+ * @param isCritical
+ * Indicates whether this control should be considered
+ * critical in processing the request.
+ * @param changeType
+ * The change type for this change notification control.
+ * @param previousDN
+ * The DN that the entry had prior to a modify DN operation,
+ * or <CODE>null</CODE> if the operation was not a modify DN.
+ * @param changeNumber
+ * The change number for the associated change, or a negative
+ * value if no change number is available.
+ */
+ public EntryChangeNotificationControl(boolean isCritical,
+ PersistentSearchChangeType changeType, DN previousDN,
+ long changeNumber)
+ {
+ super(OID_ENTRY_CHANGE_NOTIFICATION, isCritical);
+
+ Validator.ensureNotNull(changeType);
+ this.changeType = changeType;
+
+ if (previousDN != null)
+ {
+ this.previousDN = previousDN.toString();
+ }
+ this.changeNumber = changeNumber;
+ }
+
+
+
+ /**
+ * Creates a new entry change notification control with the provided
+ * information.
+ *
+ * @param isCritical
+ * Indicates whether this control should be considered
+ * critical in processing the request.
+ * @param changeType
+ * The change type for this change notification control.
+ * @param previousDN
+ * The DN that the entry had prior to a modify DN operation,
+ * or <CODE>null</CODE> if the operation was not a modify DN.
+ * @param changeNumber
+ * The change number for the associated change, or a negative
+ * value if no change number is available.
+ */
+ public EntryChangeNotificationControl(boolean isCritical,
+ PersistentSearchChangeType changeType, String previousDN,
+ long changeNumber)
+ {
+ super(OID_ENTRY_CHANGE_NOTIFICATION, isCritical);
+
+ Validator.ensureNotNull(changeType);
+ this.changeType = changeType;
+ this.previousDN = previousDN;
+ this.changeNumber = changeNumber;
+ }
+
+
+
+ /**
+ * Creates a new entry change notification control with the provided
+ * information.
+ *
+ * @param changeType
+ * The change type for this change notification control.
+ */
+ public EntryChangeNotificationControl(
+ PersistentSearchChangeType changeType)
+ {
+ this(false, changeType);
+ }
+
+
+
+ /**
+ * Creates a new entry change notification control with the provided
+ * information.
+ *
+ * @param changeType
+ * The change type for this change notification control.
+ * @param previousDN
+ * The DN that the entry had prior to a modify DN operation,
+ * or <CODE>null</CODE> if the operation was not a modify DN.
+ * @param changeNumber
+ * The change number for the associated change, or a negative
+ * value if no change number is available.
+ */
+ public EntryChangeNotificationControl(
+ PersistentSearchChangeType changeType, DN previousDN,
+ long changeNumber)
+ {
+ this(false, changeType, previousDN, changeNumber);
+ }
+
+
+
+ /**
+ * Creates a new entry change notification control with the provided
+ * information.
+ *
+ * @param changeType
+ * The change type for this change notification control.
+ * @param previousDN
+ * The DN that the entry had prior to a modify DN operation,
+ * or <CODE>null</CODE> if the operation was not a modify DN.
+ * @param changeNumber
+ * The change number for the associated change, or a negative
+ * value if no change number is available.
+ */
+ public EntryChangeNotificationControl(
+ PersistentSearchChangeType changeType, String previousDN,
+ long changeNumber)
+ {
+ this(false, changeType, previousDN, changeNumber);
+ }
+
+
+
+ /**
+ * Retrieves the change number for this entry change notification
+ * control.
+ *
+ * @return The change number for this entry change notification
+ * control, or a negative value if no change number is
+ * available.
+ */
+ public long getChangeNumber()
+ {
+ return changeNumber;
+ }
+
+
+
+ /**
+ * Retrieves the change type for this entry change notification
+ * control.
+ *
+ * @return The change type for this entry change notification control.
+ */
+ public PersistentSearchChangeType getChangeType()
+ {
+ return changeType;
+ }
+
+
+
+ /**
+ * Retrieves the previous DN for this entry change notification
+ * control.
+ *
+ * @return The previous DN for this entry change notification control,
+ * or <CODE>null</CODE> if there is none.
+ */
+ public String getPreviousDN()
+ {
+ return previousDN;
+ }
+
+
+
+ @Override
+ public ByteString getValue()
+ {
+ ByteStringBuilder buffer = new ByteStringBuilder();
+ ASN1Writer writer = ASN1.getWriter(buffer);
+ try
+ {
+ writeValue(writer);
+ return buffer.toByteString();
+ }
+ catch (IOException ioe)
+ {
+ // This should never happen unless there is a bug somewhere.
+ throw new RuntimeException(ioe);
+ }
+ }
+
+
+
+ @Override
+ public boolean hasValue()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Appends a string representation of this entry change notification
+ * control to the provided buffer.
+ *
+ * @param buffer
+ * The buffer to which the information should be appended.
+ */
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("EntryChangeNotificationControl(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+ buffer.append(", changeType=");
+ buffer.append(changeType.toString());
+ buffer.append(", previousDN=\"");
+ buffer.append(previousDN);
+ buffer.append("\"");
+ buffer.append(", changeNumber=");
+ buffer.append(changeNumber);
+ buffer.append(")");
+ }
+
+
+
+ /**
+ * Writes this control's value to an ASN.1 writer. The value (if any)
+ * must be written as an ASN1OctetString.
+ *
+ * @param writer
+ * The ASN.1 output stream to write to.
+ * @throws java.io.IOException
+ * If a problem occurs while writing to the stream.
+ */
+ public void writeValue(ASN1Writer writer) throws IOException
+ {
+ writer.writeStartSequence();
+ writer.writeInteger(changeType.intValue());
+
+ if (previousDN != null)
+ {
+ writer.writeOctetString(previousDN);
+ }
+
+ if (changeNumber > 0)
+ {
+ writer.writeInteger(changeNumber);
+ }
+ writer.writeEndSequence();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/controls/GenericControl.java b/sdk/src/org/opends/sdk/controls/GenericControl.java
new file mode 100644
index 0000000..019648d
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/GenericControl.java
@@ -0,0 +1,157 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.controls;
+
+
+
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * A generic raw request. A raw control is a control whose parameters
+ * have not been fully decoded.
+ * <p>
+ * TODO: this should be hooked into the remaining controls class
+ * hierarchy.
+ * <p>
+ * TODO: push ASN1 encoding into ASN1 package.
+ */
+public final class GenericControl extends Control
+{
+
+ // The control value.
+ private final ByteString value;
+
+
+
+ /**
+ * Creates a new control with the specified OID. It will not be
+ * critical, and will not have a value.
+ *
+ * @param oid
+ * The OID for this control.
+ */
+ public GenericControl(String oid)
+ {
+ this(oid, false, null);
+ }
+
+
+
+ /**
+ * Creates a new raw control with the specified OID and criticality.
+ * It will not have a value.
+ *
+ * @param oid
+ * The OID for this control.
+ * @param isCritical
+ * Indicates whether this control should be considered
+ * critical.
+ */
+ public GenericControl(String oid, boolean isCritical)
+ {
+ this(oid, isCritical, null);
+ }
+
+
+
+ /**
+ * Creates a new raw control with the specified OID, criticality, and
+ * value.
+ *
+ * @param oid
+ * The OID for this control.
+ * @param isCritical
+ * Indicates whether this control should be considered
+ * critical.
+ * @param value
+ * The value for this control.
+ */
+ public GenericControl(String oid, boolean isCritical, ByteString value)
+ {
+ super(oid, isCritical);
+ this.value = value;
+ }
+
+
+
+ /**
+ * Retrieves the value for this control.
+ *
+ * @return The value for this control, or <CODE>null</CODE> if there
+ * is no value.
+ */
+ @Override
+ public ByteString getValue()
+ {
+ return value;
+ }
+
+
+
+ /**
+ * Indicates whether this control has a value.
+ *
+ * @return <CODE>true</CODE> if this control has a value, or
+ * <CODE>false</CODE> if it does not.
+ */
+ @Override
+ public boolean hasValue()
+ {
+ return (value != null);
+ }
+
+
+
+ /**
+ * Appends a string representation of this control to the provided
+ * buffer.
+ *
+ * @param buffer
+ * The buffer to which the information should be appended.
+ */
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("Control(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+
+ if (value != null)
+ {
+ buffer.append(", value=");
+ StaticUtils.toHexPlusAscii(value, buffer, 4);
+ }
+
+ buffer.append(")");
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/controls/GetEffectiveRightsRequestControl.java b/sdk/src/org/opends/sdk/controls/GetEffectiveRightsRequestControl.java
new file mode 100644
index 0000000..cbce065
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/GetEffectiveRightsRequestControl.java
@@ -0,0 +1,271 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.INFO_GETEFFECTIVERIGHTS_DECODE_ERROR;
+import static org.opends.messages.ProtocolMessages.INFO_GETEFFECTIVERIGHTS_INVALID_AUTHZID;
+
+import java.io.IOException;
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DN;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.schema.AttributeType;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class partially implements the geteffectiverights control as
+ * defined
+ * in draft-ietf-ldapext-acl-model-08.txt. The main differences are:
+ * - The response control is not supported. Instead the dseecompat
+ * geteffectiverights control implementation creates attributes
+ * containing
+ * right information strings and adds those attributes to the
+ * entry being returned. The attribute type names are dynamically
+ * created;
+ * see the dseecompat's AciGetEffectiveRights class for details.
+ * - The dseecompat implementation allows additional attribute types
+ * in the request control for which rights information can be returned.
+ * These are known as the specified attribute types.
+ * The dseecompat request control value is the following: <BR>
+ *
+ * <PRE>
+ * GetRightsControl ::= SEQUENCE {
+ * authzId authzId
+ * attributes SEQUENCE OF AttributeType
+ * }
+ * -- Only the "dn:DN form is supported.
+ *
+ * </PRE>
+ **/
+public class GetEffectiveRightsRequestControl extends Control
+{
+ /**
+ * The OID for the get effective rights control.
+ */
+ public static final String OID_GET_EFFECTIVE_RIGHTS = "1.3.6.1.4.1.42.2.27.9.5.2";
+
+
+
+ /**
+ * ControlDecoder implentation to decode this control from a
+ * ByteString.
+ */
+ private static final class Decoder implements
+ ControlDecoder<GetEffectiveRightsRequestControl>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public GetEffectiveRightsRequestControl decode(boolean isCritical,
+ ByteString value, Schema schema) throws DecodeException
+ {
+ // If the value is null create a GetEffectiveRightsRequestControl
+ // class with null authzDN and attribute list, else try to
+ // decode the value.
+ if (value == null)
+ return new GetEffectiveRightsRequestControl(isCritical,
+ (String) null);
+ else
+ {
+ ASN1Reader reader = ASN1.getReader(value);
+ String authzDN;
+ List<String> attrs = Collections.emptyList();
+ try
+ {
+ reader.readStartSequence();
+ String authzIDString = reader.readOctetStringAsString();
+ String lowerAuthzIDString = authzIDString.toLowerCase();
+ // Make sure authzId starts with "dn:" and is a valid DN.
+ if (lowerAuthzIDString.startsWith("dn:"))
+ authzDN = authzIDString.substring(3);
+ else
+ {
+ Message message = INFO_GETEFFECTIVERIGHTS_INVALID_AUTHZID
+ .get(lowerAuthzIDString);
+ throw DecodeException.error(message);
+ }
+ // There is an sequence containing an attribute list, try to
+ // decode it.
+ if (reader.hasNextElement())
+ {
+ attrs = new LinkedList<String>();
+ reader.readStartSequence();
+ while (reader.hasNextElement())
+ {
+ // Decode as an octet string.
+ attrs.add(reader.readOctetStringAsString());
+ }
+ reader.readEndSequence();
+ }
+ reader.readEndSequence();
+ }
+ catch (IOException e)
+ {
+ Message message = INFO_GETEFFECTIVERIGHTS_DECODE_ERROR.get(e
+ .getMessage());
+ throw DecodeException.error(message);
+ }
+
+ return new GetEffectiveRightsRequestControl(isCritical,
+ authzDN, attrs);
+ }
+ }
+
+
+
+ public String getOID()
+ {
+ return OID_GET_EFFECTIVE_RIGHTS;
+ }
+
+ }
+
+
+
+ /**
+ * The Control Decoder that can be used to decode this control.
+ */
+ public static final ControlDecoder<GetEffectiveRightsRequestControl> DECODER = new Decoder();
+
+ // The raw DN representing the authzId
+ private String authorizationDN = null;
+
+ // The raw DN representing the authzId
+ private List<String> attributes = null;
+
+
+
+ public GetEffectiveRightsRequestControl(boolean isCritical,
+ String authorizationDN, String... attributes)
+ {
+ super(OID_GET_EFFECTIVE_RIGHTS, isCritical);
+
+ this.authorizationDN = authorizationDN;
+ if (attributes != null)
+ {
+ this.attributes = new ArrayList<String>(attributes.length);
+ this.attributes.addAll(Arrays.asList(attributes));
+ }
+ else
+ {
+ this.attributes = Collections.emptyList();
+ }
+ }
+
+
+
+ public GetEffectiveRightsRequestControl(boolean isCritical,
+ DN authorizationDN, AttributeType... attributes)
+ {
+ super(OID_GET_EFFECTIVE_RIGHTS, isCritical);
+
+ Validator.ensureNotNull(authorizationDN, attributes);
+
+ this.authorizationDN = authorizationDN.toString();
+
+ if (attributes != null)
+ {
+ for (AttributeType attr : attributes)
+ {
+ this.attributes = new ArrayList<String>(attributes.length);
+ this.attributes.add(attr.getNameOrOID());
+ }
+ }
+ else
+ {
+ this.attributes = Collections.emptyList();
+ }
+ }
+
+
+
+ private GetEffectiveRightsRequestControl(boolean isCritical,
+ String authorizationDN, List<String> attributes)
+ {
+ super(OID_GET_EFFECTIVE_RIGHTS, isCritical);
+
+ Validator.ensureNotNull(authorizationDN, attributes);
+
+ this.authorizationDN = authorizationDN;
+ this.attributes = attributes;
+ }
+
+
+
+ public ByteString getValue()
+ {
+ if (authorizationDN == null && attributes.isEmpty())
+ {
+ return ByteString.empty();
+ }
+
+ ByteStringBuilder buffer = new ByteStringBuilder();
+ ASN1Writer writer = ASN1.getWriter(buffer);
+ try
+ {
+ writer.writeStartSequence();
+ if (authorizationDN != null)
+ {
+ writer.writeOctetString("dn:" + authorizationDN);
+ }
+
+ if (!attributes.isEmpty())
+ {
+ writer.writeStartSequence();
+ for (String attr : attributes)
+ {
+ writer.writeOctetString(attr);
+ }
+ writer.writeEndSequence();
+ }
+ writer.writeEndSequence();
+ return buffer.toByteString();
+ }
+ catch (IOException ioe)
+ {
+ // This should never happen unless there is a bug somewhere.
+ throw new RuntimeException(ioe);
+ }
+ }
+
+
+
+ public boolean hasValue()
+ {
+ return authorizationDN != null || !attributes.isEmpty();
+ }
+
+
+
+ /**
+ * Appends a string representation of this proxied auth v2 control to
+ * the provided buffer.
+ *
+ * @param buffer
+ * The buffer to which the information should be appended.
+ */
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("GetEffectiveRightsRequestControl(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+ buffer.append(", authorizationDN=\"");
+ buffer.append(authorizationDN);
+ buffer.append("\"");
+ buffer.append(", attributes=(");
+ buffer.append(attributes);
+ buffer.append("))");
+ }
+}
diff --git a/sdk/src/org/opends/sdk/controls/MatchedValuesControl.java b/sdk/src/org/opends/sdk/controls/MatchedValuesControl.java
new file mode 100644
index 0000000..8162bc0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/MatchedValuesControl.java
@@ -0,0 +1,351 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.*;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opends.messages.Message;
+import org.opends.sdk.AbstractFilterVisitor;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.Filter;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.ldap.LDAPUtils;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.*;
+
+
+
+/**
+ * This class implements the matched values control as defined in RFC
+ * 3876. It may be included in a search request to indicate that only
+ * attribute values matching one or more filters contained in the
+ * matched values control should be returned to the client.
+ */
+public class MatchedValuesControl extends Control
+{
+ /**
+ * The OID for the matched values control used to specify which
+ * particular attribute values should be returned in a search result
+ * entry.
+ */
+ public static final String OID_MATCHED_VALUES = "1.2.826.0.1.3344810.2.3";
+
+
+
+ /**
+ * Visitor for validating matched values filters.
+ */
+ private static final class FilterValidator extends
+ AbstractFilterVisitor<LocalizedIllegalArgumentException, Filter>
+ {
+
+ @Override
+ public LocalizedIllegalArgumentException visitAndFilter(Filter p,
+ List<Filter> subFilters)
+ {
+ Message message = ERR_MVFILTER_BAD_FILTER_AND.get(p.toString());
+ return new LocalizedIllegalArgumentException(message);
+ }
+
+
+
+ @Override
+ public LocalizedIllegalArgumentException visitExtensibleMatchFilter(
+ Filter p, String matchingRule, String attributeDescription,
+ ByteSequence assertionValue, boolean dnAttributes)
+ {
+ if (dnAttributes)
+ {
+ Message message = ERR_MVFILTER_BAD_FILTER_EXT.get(p.toString());
+ return new LocalizedIllegalArgumentException(message);
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+
+
+ @Override
+ public LocalizedIllegalArgumentException visitNotFilter(Filter p,
+ Filter subFilter)
+ {
+ Message message = ERR_MVFILTER_BAD_FILTER_NOT.get(p.toString());
+ return new LocalizedIllegalArgumentException(message);
+ }
+
+
+
+ @Override
+ public LocalizedIllegalArgumentException visitOrFilter(Filter p,
+ List<Filter> subFilters)
+ {
+ Message message = ERR_MVFILTER_BAD_FILTER_OR.get(p.toString());
+ return new LocalizedIllegalArgumentException(message);
+ }
+
+
+
+ @Override
+ public LocalizedIllegalArgumentException visitUnrecognizedFilter(
+ Filter p, byte filterTag, ByteSequence filterBytes)
+ {
+ Message message = ERR_MVFILTER_BAD_FILTER_UNRECOGNIZED.get(p
+ .toString(), filterTag);
+ return new LocalizedIllegalArgumentException(message);
+ }
+ }
+
+
+
+ /**
+ * Decodes a matched values control from a byte string.
+ */
+ private final static class Decoder implements
+ ControlDecoder<MatchedValuesControl>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public MatchedValuesControl decode(boolean isCritical,
+ ByteString value, Schema schema) throws DecodeException
+ {
+ if (value == null)
+ {
+ Message message = ERR_MATCHEDVALUES_NO_CONTROL_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ ASN1Reader reader = ASN1.getReader(value);
+ try
+ {
+ reader.readStartSequence();
+ if (!reader.hasNextElement())
+ {
+ Message message = ERR_MATCHEDVALUES_NO_FILTERS.get();
+ throw DecodeException.error(message);
+ }
+
+ LinkedList<Filter> filters = new LinkedList<Filter>();
+ do
+ {
+ Filter filter = LDAPUtils.decodeFilter(reader);
+
+ try
+ {
+ validateFilter(filter);
+ }
+ catch (LocalizedIllegalArgumentException e)
+ {
+ throw DecodeException
+ .error(e.getMessageObject());
+ }
+
+ filters.add(filter);
+ } while (reader.hasNextElement());
+
+ reader.readEndSequence();
+
+ return new MatchedValuesControl(isCritical, Collections
+ .unmodifiableList(filters));
+ }
+ catch (IOException e)
+ {
+ StaticUtils.DEBUG_LOG.throwing("MatchedValuesControl.Decoder",
+ "decode", e);
+
+ Message message = ERR_MATCHEDVALUES_CANNOT_DECODE_VALUE_AS_SEQUENCE
+ .get(getExceptionMessage(e));
+ throw DecodeException.error(message);
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getOID()
+ {
+ return OID_MATCHED_VALUES;
+ }
+
+ }
+
+
+
+ /**
+ * A control decoder which can be used to decode matched values
+ * controls.
+ */
+ public static final ControlDecoder<MatchedValuesControl> DECODER = new Decoder();
+
+ private static final FilterValidator FILTER_VALIDATOR = new FilterValidator();
+
+
+
+ private static void validateFilter(final Filter filter)
+ throws LocalizedIllegalArgumentException
+ {
+ LocalizedIllegalArgumentException e = filter.accept(
+ FILTER_VALIDATOR, filter);
+ if (e != null)
+ {
+ throw e;
+ }
+ }
+
+
+
+ private List<Filter> filters;
+
+
+
+ /**
+ * Creates a new matched values control using the default OID and the
+ * provided criticality and set of filters.
+ *
+ * @param isCritical
+ * Indicates whether this control should be considered
+ * critical to the operation processing.
+ * @param filters
+ * The list of matched value filters.
+ * @throws LocalizedIllegalArgumentException
+ * If one of the filters is not permitted by the matched
+ * values control.
+ */
+ public MatchedValuesControl(boolean isCritical, Filter... filters)
+ throws LocalizedIllegalArgumentException
+ {
+ super(OID_MATCHED_VALUES, isCritical);
+
+ Validator.ensureNotNull((Object) filters);
+ Validator.ensureTrue(filters.length > 0, "filters is empty");
+
+ if (filters.length == 1)
+ {
+ validateFilter(filters[0]);
+ this.filters = Collections.singletonList(filters[0]);
+ }
+ else
+ {
+ LinkedList<Filter> list = new LinkedList<Filter>();
+ for (Filter filter : filters)
+ {
+ validateFilter(filter);
+ list.add(filter);
+ }
+ this.filters = Collections.unmodifiableList(list);
+ }
+ }
+
+
+
+ private MatchedValuesControl(boolean isCritical, List<Filter> filters)
+ {
+ super(OID_MATCHED_VALUES, isCritical);
+ this.filters = filters;
+ }
+
+
+
+ /**
+ * Returns an {@code Iterable} containing the list of filters
+ * associated with this matched values control.
+ *
+ * @return An {@code Iterable} containing the list of filters.
+ */
+ public Iterable<Filter> getFilters()
+ {
+ return filters;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ByteString getValue()
+ {
+ ByteStringBuilder buffer = new ByteStringBuilder();
+ ASN1Writer writer = ASN1.getWriter(buffer);
+ try
+ {
+ writer.writeStartSequence();
+ for (Filter f : filters)
+ {
+ LDAPUtils.encodeFilter(writer, f);
+ }
+ writer.writeEndSequence();
+ return buffer.toByteString();
+ }
+ catch (IOException ioe)
+ {
+ // This should never happen unless there is a bug somewhere.
+ throw new RuntimeException(ioe);
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean hasValue()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("MatchingValuesControl(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+ buffer.append(")");
+ }
+}
diff --git a/sdk/src/org/opends/sdk/controls/PagedResultsControl.java b/sdk/src/org/opends/sdk/controls/PagedResultsControl.java
new file mode 100644
index 0000000..fc01e7c
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/PagedResultsControl.java
@@ -0,0 +1,258 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_LDAP_PAGED_RESULTS_DECODE_COOKIE;
+import static org.opends.messages.ProtocolMessages.ERR_LDAP_PAGED_RESULTS_DECODE_NULL;
+import static org.opends.messages.ProtocolMessages.ERR_LDAP_PAGED_RESULTS_DECODE_SEQUENCE;
+import static org.opends.messages.ProtocolMessages.ERR_LDAP_PAGED_RESULTS_DECODE_SIZE;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class represents a paged results control value as defined in RFC
+ * 2696. The searchControlValue is an OCTET STRING wrapping the
+ * BER-encoded version of the following SEQUENCE: realSearchControlValue
+ * ::= SEQUENCE { size INTEGER (0..maxInt), -- requested page size from
+ * client -- result set size estimate from server cookie OCTET STRING }
+ */
+public class PagedResultsControl extends Control
+{
+ /**
+ * The OID for the paged results control defined in RFC 2696.
+ */
+ public static final String OID_PAGED_RESULTS_CONTROL = "1.2.840.113556.1.4.319";
+
+
+
+ /**
+ * ControlDecoder implentation to decode this control from a
+ * ByteString.
+ */
+ private final static class Decoder implements
+ ControlDecoder<PagedResultsControl>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public PagedResultsControl decode(boolean isCritical,
+ ByteString value, Schema schema) throws DecodeException
+ {
+ if (value == null)
+ {
+ Message message = ERR_LDAP_PAGED_RESULTS_DECODE_NULL.get();
+ throw DecodeException.error(message);
+ }
+
+ ASN1Reader reader = ASN1.getReader(value);
+ try
+ {
+ reader.readStartSequence();
+ }
+ catch (Exception e)
+ {
+ StaticUtils.DEBUG_LOG.throwing("PagedResultsControl.Decoder",
+ "decode", e);
+
+ Message message = ERR_LDAP_PAGED_RESULTS_DECODE_SEQUENCE
+ .get(String.valueOf(e));
+ throw DecodeException.error(message, e);
+ }
+
+ int size;
+ try
+ {
+ size = (int) reader.readInteger();
+ }
+ catch (Exception e)
+ {
+ StaticUtils.DEBUG_LOG.throwing("PagedResultsControl.Decoder",
+ "decode", e);
+
+ Message message = ERR_LDAP_PAGED_RESULTS_DECODE_SIZE.get(String
+ .valueOf(e));
+ throw DecodeException.error(message, e);
+ }
+
+ ByteString cookie;
+ try
+ {
+ cookie = reader.readOctetString();
+ }
+ catch (Exception e)
+ {
+ StaticUtils.DEBUG_LOG.throwing("PagedResultsControl.Decoder",
+ "decode", e);
+
+ Message message = ERR_LDAP_PAGED_RESULTS_DECODE_COOKIE
+ .get(String.valueOf(e));
+ throw DecodeException.error(message, e);
+ }
+
+ try
+ {
+ reader.readEndSequence();
+ }
+ catch (Exception e)
+ {
+ StaticUtils.DEBUG_LOG.throwing("PagedResultsControl.Decoder",
+ "decode", e);
+
+ Message message = ERR_LDAP_PAGED_RESULTS_DECODE_SEQUENCE
+ .get(String.valueOf(e));
+ throw DecodeException.error(message, e);
+ }
+
+ return new PagedResultsControl(isCritical, size, cookie);
+ }
+
+
+
+ public String getOID()
+ {
+ return OID_PAGED_RESULTS_CONTROL;
+ }
+ }
+
+
+
+ /**
+ * The Control Decoder that can be used to decode this control.
+ */
+ public static final ControlDecoder<PagedResultsControl> DECODER = new Decoder();
+
+ /**
+ * The control value size element, which is either the requested page
+ * size from the client, or the result set size estimate from the
+ * server.
+ */
+ private final int size;
+
+ /**
+ * The control value cookie element.
+ */
+ private final ByteString cookie;
+
+
+
+ /**
+ * Creates a new paged results control with the specified information.
+ *
+ * @param isCritical
+ * Indicates whether this control should be considered
+ * critical in processing the request.
+ * @param size
+ * The size element.
+ * @param cookie
+ * The cookie element.
+ */
+ public PagedResultsControl(boolean isCritical, int size,
+ ByteString cookie)
+ {
+ super(OID_PAGED_RESULTS_CONTROL, isCritical);
+
+ Validator.ensureNotNull(cookie);
+ this.size = size;
+ this.cookie = cookie;
+ }
+
+
+
+ /**
+ * Creates a new paged results control with the specified information.
+ *
+ * @param size
+ * The size element.
+ * @param cookie
+ * The cookie element.
+ */
+ public PagedResultsControl(int size, ByteString cookie)
+ {
+ this(false, size, cookie);
+ }
+
+
+
+ /**
+ * Get the control value cookie element.
+ *
+ * @return The control value cookie element.
+ */
+ public ByteString getCookie()
+ {
+ return cookie;
+ }
+
+
+
+ /**
+ * Get the control value size element, which is either the requested
+ * page size from the client, or the result set size estimate from the
+ * server.
+ *
+ * @return The control value size element.
+ */
+ public int getSize()
+ {
+ return size;
+ }
+
+
+
+ @Override
+ public ByteString getValue()
+ {
+ ByteStringBuilder buffer = new ByteStringBuilder();
+ ASN1Writer writer = ASN1.getWriter(buffer);
+ try
+ {
+ writer.writeStartSequence();
+ writer.writeInteger(size);
+ writer.writeOctetString(cookie);
+ writer.writeEndSequence();
+ return buffer.toByteString();
+ }
+ catch (IOException ioe)
+ {
+ // This should never happen unless there is a bug somewhere.
+ throw new RuntimeException(ioe);
+ }
+ }
+
+
+
+ @Override
+ public boolean hasValue()
+ {
+ return true;
+ }
+
+
+
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("PagedResultsControl(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+ buffer.append(", size=");
+ buffer.append(size);
+ buffer.append(", cookie=");
+ buffer.append(cookie);
+ buffer.append(")");
+ }
+}
diff --git a/sdk/src/org/opends/sdk/controls/PasswordExpiredControl.java b/sdk/src/org/opends/sdk/controls/PasswordExpiredControl.java
new file mode 100644
index 0000000..a84c432
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/PasswordExpiredControl.java
@@ -0,0 +1,137 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_PWEXPIRED_CONTROL_INVALID_VALUE;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the Netscape password expired control. The
+ * value for this control should be a string that indicates the length
+ * of time until the password expires, but because it is already expired
+ * it will always be "0".
+ */
+public class PasswordExpiredControl extends Control
+{
+ /**
+ * The OID for the Netscape password expired control.
+ */
+ public static final String OID_NS_PASSWORD_EXPIRED = "2.16.840.1.113730.3.4.4";
+
+
+
+ /**
+ * ControlDecoder implentation to decode this control from a
+ * ByteString.
+ */
+ private final static class Decoder implements
+ ControlDecoder<PasswordExpiredControl>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public PasswordExpiredControl decode(boolean isCritical,
+ ByteString value, Schema schema) throws DecodeException
+ {
+ if (value != null)
+ {
+ try
+ {
+ Integer.parseInt(value.toString());
+ }
+ catch (Exception e)
+ {
+ Message message = ERR_PWEXPIRED_CONTROL_INVALID_VALUE.get();
+ throw DecodeException.error(message);
+ }
+ }
+
+ return new PasswordExpiredControl(isCritical);
+ }
+
+
+
+ public String getOID()
+ {
+ return OID_NS_PASSWORD_EXPIRED;
+ }
+
+ }
+
+
+
+ private final static ByteString CONTROL_VALUE = ByteString
+ .valueOf("0");
+
+ /**
+ * The Control Decoder that can be used to decode this control.
+ */
+ public static final ControlDecoder<PasswordExpiredControl> DECODER = new Decoder();
+
+
+
+ /**
+ * Creates a new instance of the password expired control with the
+ * default settings.
+ */
+ public PasswordExpiredControl()
+ {
+ this(false);
+ }
+
+
+
+ /**
+ * Creates a new instance of the password expired control with the
+ * provided information.
+ *
+ * @param isCritical
+ * Indicates whether support for this control should be
+ * considered a critical part of the client processing.
+ */
+ public PasswordExpiredControl(boolean isCritical)
+ {
+ super(OID_NS_PASSWORD_EXPIRED, isCritical);
+ }
+
+
+
+ @Override
+ public ByteString getValue()
+ {
+ return CONTROL_VALUE;
+ }
+
+
+
+ @Override
+ public boolean hasValue()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Appends a string representation of this password expired control to
+ * the provided buffer.
+ *
+ * @param buffer
+ * The buffer to which the information should be appended.
+ */
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("PasswordExpiredControl(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+ buffer.append(")");
+ }
+}
diff --git a/sdk/src/org/opends/sdk/controls/PasswordExpiringControl.java b/sdk/src/org/opends/sdk/controls/PasswordExpiringControl.java
new file mode 100644
index 0000000..b5c24bf
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/PasswordExpiringControl.java
@@ -0,0 +1,175 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_PWEXPIRING_CANNOT_DECODE_SECONDS_UNTIL_EXPIRATION;
+import static org.opends.messages.ProtocolMessages.ERR_PWEXPIRING_NO_CONTROL_VALUE;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * This class implements the Netscape password expiring control, which
+ * serves as a warning to clients that the user's password is about to
+ * expire. The only element contained in the control value is a string
+ * representation of the number of seconds until expiration.
+ */
+public class PasswordExpiringControl extends Control
+{
+ /**
+ * The OID for the Netscape password expiring control.
+ */
+ public static final String OID_NS_PASSWORD_EXPIRING = "2.16.840.1.113730.3.4.5";
+
+
+
+ /**
+ * ControlDecoder implentation to decode this control from a
+ * ByteString.
+ */
+ private final static class Decoder implements
+ ControlDecoder<PasswordExpiringControl>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public PasswordExpiringControl decode(boolean isCritical,
+ ByteString value, Schema schema) throws DecodeException
+ {
+ if (value == null)
+ {
+ Message message = ERR_PWEXPIRING_NO_CONTROL_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ int secondsUntilExpiration;
+ try
+ {
+ secondsUntilExpiration = Integer.parseInt(value.toString());
+ }
+ catch (Exception e)
+ {
+ StaticUtils.DEBUG_LOG.throwing(
+ "PasswordExpiringControl.Decoder", "decode", e);
+
+ Message message = ERR_PWEXPIRING_CANNOT_DECODE_SECONDS_UNTIL_EXPIRATION
+ .get(getExceptionMessage(e));
+ throw DecodeException.error(message);
+ }
+
+ return new PasswordExpiringControl(isCritical,
+ secondsUntilExpiration);
+ }
+
+
+
+ public String getOID()
+ {
+ return OID_NS_PASSWORD_EXPIRING;
+ }
+
+ }
+
+
+
+ /**
+ * The Control Decoder that can be used to decode this control.
+ */
+ public static final ControlDecoder<PasswordExpiringControl> DECODER = new Decoder();
+
+ // The length of time in seconds until the password actually expires.
+ private final int secondsUntilExpiration;
+
+
+
+ /**
+ * Creates a new instance of the password expiring control with the
+ * provided information.
+ *
+ * @param isCritical
+ * Indicates whether support for this control should be
+ * considered a critical part of the client processing.
+ * @param secondsUntilExpiration
+ * The length of time in seconds until the password actually
+ * expires.
+ */
+ public PasswordExpiringControl(boolean isCritical,
+ int secondsUntilExpiration)
+ {
+ super(OID_NS_PASSWORD_EXPIRING, isCritical);
+
+ this.secondsUntilExpiration = secondsUntilExpiration;
+ }
+
+
+
+ /**
+ * Creates a new instance of the password expiring control with the
+ * provided information.
+ *
+ * @param secondsUntilExpiration
+ * The length of time in seconds until the password actually
+ * expires.
+ */
+ public PasswordExpiringControl(int secondsUntilExpiration)
+ {
+ this(false, secondsUntilExpiration);
+ }
+
+
+
+ /**
+ * Retrieves the length of time in seconds until the password actually
+ * expires.
+ *
+ * @return The length of time in seconds until the password actually
+ * expires.
+ */
+ public int getSecondsUntilExpiration()
+ {
+ return secondsUntilExpiration;
+ }
+
+
+
+ @Override
+ public ByteString getValue()
+ {
+ return ByteString.valueOf(String.valueOf(secondsUntilExpiration));
+ }
+
+
+
+ @Override
+ public boolean hasValue()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Appends a string representation of this password expiring control
+ * to the provided buffer.
+ *
+ * @param buffer
+ * The buffer to which the information should be appended.
+ */
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("PasswordExpiringControl(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+ buffer.append(", secondsUntilExpiration=");
+ buffer.append(secondsUntilExpiration);
+ buffer.append(")");
+ }
+}
diff --git a/sdk/src/org/opends/sdk/controls/PasswordPolicyControl.java b/sdk/src/org/opends/sdk/controls/PasswordPolicyControl.java
new file mode 100644
index 0000000..9671bbd
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/PasswordPolicyControl.java
@@ -0,0 +1,412 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.*;
+import static org.opends.sdk.util.StaticUtils.byteToHex;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class implements the password policy control defined in
+ * draft-behera-ldap-password-policy.
+ */
+public class PasswordPolicyControl
+{
+ /**
+ * The OID for the password policy control from
+ * draft-behera-ldap-password-policy.
+ */
+ public static final String OID_PASSWORD_POLICY_CONTROL = "1.3.6.1.4.1.42.2.27.8.5.1";
+
+
+
+ /**
+ * This class implements the password policy request control defined
+ * in draft-behera-ldap-password-policy. It does not have a value.
+ */
+ public static class Request extends Control
+ {
+ public Request()
+ {
+ super(OID_PASSWORD_POLICY_CONTROL, false);
+ }
+
+
+
+ public Request(boolean isCritical)
+ {
+ super(OID_PASSWORD_POLICY_CONTROL, isCritical);
+ }
+
+
+
+ @Override
+ public ByteString getValue()
+ {
+ return null;
+ }
+
+
+
+ @Override
+ public boolean hasValue()
+ {
+ return false;
+ }
+
+
+
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("PasswordPolicyRequestControl(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+ buffer.append(")");
+ }
+ }
+
+
+
+ /**
+ * This class implements the password policy response control defined
+ * in draft-behera-ldap-password-policy. The value may have zero, one,
+ * or two elements, which may include flags to indicate a warning
+ * and/or an error.
+ */
+ public static class Response extends Control
+ {
+ // The warning value for this password policy response control.
+ private int warningValue;
+
+ // The error type for this password policy response control.
+ private PasswordPolicyErrorType errorType;
+
+ // The warning type for the password policy response control.
+ private PasswordPolicyWarningType warningType;
+
+
+
+ /**
+ * Creates a new instance of the password policy response control
+ * with the default OID and criticality, and without either a
+ * warning or an error flag.
+ */
+ public Response()
+ {
+ this(false);
+ }
+
+
+
+ /**
+ * Creates a new instance of the password policy response control
+ * with the default OID and criticality, and without either a
+ * warning or an error flag.
+ *
+ * @param isCritical
+ * Indicates whether support for this control should be
+ * considered a critical part of the client processing.
+ */
+ public Response(boolean isCritical)
+ {
+ super(OID_PASSWORD_POLICY_CONTROL, isCritical);
+
+ warningType = null;
+ errorType = null;
+ warningValue = -1;
+ }
+
+
+
+ /**
+ * Retrieves the password policy error type contained in this
+ * control.
+ *
+ * @return The password policy error type contained in this control,
+ * or <CODE>null</CODE> if there is no error type.
+ */
+ public PasswordPolicyErrorType getErrorType()
+ {
+ return errorType;
+ }
+
+
+
+ @Override
+ public ByteString getValue()
+ {
+ ByteStringBuilder buffer = new ByteStringBuilder();
+ ASN1Writer writer = ASN1.getWriter(buffer);
+ try
+ {
+ writer.writeStartSequence();
+ if (warningType != null)
+ {
+ // Just write the CHOICE element as a single element SEQUENCE.
+ writer.writeStartSequence(TYPE_WARNING_ELEMENT);
+ writer.writeInteger((byte) (0x80 | warningType.intValue()),
+ warningValue);
+ writer.writeEndSequence();
+ }
+
+ if (errorType != null)
+ {
+ writer.writeInteger(TYPE_ERROR_ELEMENT, errorType.intValue());
+ }
+ writer.writeEndSequence();
+ return buffer.toByteString();
+ }
+ catch (IOException ioe)
+ {
+ // This should never happen unless there is a bug somewhere.
+ throw new RuntimeException(ioe);
+ }
+ }
+
+
+
+ /**
+ * Retrieves the password policy warning type contained in this
+ * control.
+ *
+ * @return The password policy warning type contained in this
+ * control, or <CODE>null</CODE> if there is no warning
+ * type.
+ */
+ public PasswordPolicyWarningType getWarningType()
+ {
+ return warningType;
+ }
+
+
+
+ /**
+ * Retrieves the password policy warning value for this control. The
+ * value is undefined if there is no warning type.
+ *
+ * @return The password policy warning value for this control.
+ */
+ public int getWarningValue()
+ {
+ return warningValue;
+ }
+
+
+
+ @Override
+ public boolean hasValue()
+ {
+ return true;
+ }
+
+
+
+ public Response setError(PasswordPolicyErrorType error)
+ {
+ Validator.ensureNotNull(error);
+ this.errorType = error;
+ return this;
+ }
+
+
+
+ public Response setWarning(PasswordPolicyWarningType type, int value)
+ {
+ Validator.ensureNotNull(type);
+ this.warningType = type;
+ this.warningValue = value;
+ return this;
+ }
+
+
+
+ /**
+ * Appends a string representation of this password policy response
+ * control to the provided buffer.
+ *
+ * @param buffer
+ * The buffer to which the information should be appended.
+ */
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("PasswordPolicyResponseControl(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+ buffer.append(", warningType=");
+ buffer.append(warningType);
+ buffer.append(", warningValue=");
+ buffer.append(warningValue);
+ buffer.append(", errorType=");
+ buffer.append(errorType);
+ buffer.append(")");
+ }
+ }
+
+
+
+ /**
+ * ControlDecoder implentation to decode this control from a
+ * ByteString.
+ */
+ private static final class RequestDecoder implements
+ ControlDecoder<Request>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public Request decode(boolean isCritical, ByteString value, Schema schema)
+ throws DecodeException
+ {
+ if (value != null)
+ {
+ Message message = ERR_PWPOLICYREQ_CONTROL_HAS_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ return new Request(isCritical);
+ }
+
+
+
+ public String getOID()
+ {
+ return OID_PASSWORD_POLICY_CONTROL;
+ }
+ }
+
+
+
+ /**
+ * ControlDecoder implentation to decode this control from a
+ * ByteString.
+ */
+ private final static class ResponseDecoder implements
+ ControlDecoder<Response>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public Response decode(boolean isCritical, ByteString value, Schema schema)
+ throws DecodeException
+ {
+ if (value == null)
+ {
+ // The response control must always have a value.
+ Message message = ERR_PWPOLICYRES_NO_CONTROL_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ ASN1Reader reader = ASN1.getReader(value);
+ try
+ {
+ PasswordPolicyWarningType warningType = null;
+ PasswordPolicyErrorType errorType = null;
+ int warningValue = -1;
+
+ reader.readStartSequence();
+
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_WARNING_ELEMENT))
+ {
+ // Its a CHOICE element. Read as sequence to retrieve
+ // nested element.
+ reader.readStartSequence();
+ warningType = PasswordPolicyWarningType.valueOf(0x7F & reader
+ .peekType());
+ warningValue = (int) reader.readInteger();
+ if (warningType == null)
+ {
+ Message message = ERR_PWPOLICYRES_INVALID_WARNING_TYPE
+ .get(byteToHex(reader.peekType()));
+ throw DecodeException.error(message);
+ }
+ reader.readEndSequence();
+ }
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_ERROR_ELEMENT))
+ {
+ int errorValue = (int) reader.readInteger();
+ errorType = PasswordPolicyErrorType.valueOf(errorValue);
+ if (errorType == null)
+ {
+ Message message = ERR_PWPOLICYRES_INVALID_ERROR_TYPE
+ .get(errorValue);
+ throw DecodeException.error(message);
+ }
+ }
+
+ reader.readEndSequence();
+
+ Response response = new Response(isCritical);
+ if (warningType != null)
+ {
+ response.setWarning(warningType, warningValue);
+ }
+ if (errorType != null)
+ {
+ response.setError(errorType);
+ }
+ return response;
+ }
+ catch (IOException e)
+ {
+ StaticUtils.DEBUG_LOG.throwing(
+ "PasswordPolicyControl.ResponseDecoder", "decode", e);
+
+ Message message = ERR_PWPOLICYRES_DECODE_ERROR
+ .get(getExceptionMessage(e));
+ throw DecodeException.error(message);
+ }
+ }
+
+
+
+ public String getOID()
+ {
+ return OID_PASSWORD_POLICY_CONTROL;
+ }
+
+ }
+
+
+
+ /**
+ * The BER type value for the warning element of the control value.
+ */
+ private static final byte TYPE_WARNING_ELEMENT = (byte) 0xA0;
+
+ /**
+ * The BER type value for the error element of the control value.
+ */
+ private static final byte TYPE_ERROR_ELEMENT = (byte) 0x81;
+
+ /**
+ * The Control Decoder that can be used to decode the request control.
+ */
+ public static final ControlDecoder<Request> REQUEST_DECODER = new RequestDecoder();
+
+ /**
+ * The Control Decoder that can be used to decode the response
+ * control.
+ */
+ public static final ControlDecoder<Response> RESPONSE_DECODER = new ResponseDecoder();
+}
diff --git a/sdk/src/org/opends/sdk/controls/PasswordPolicyErrorType.java b/sdk/src/org/opends/sdk/controls/PasswordPolicyErrorType.java
new file mode 100644
index 0000000..08d3f45
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/PasswordPolicyErrorType.java
@@ -0,0 +1,128 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.*;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.opends.messages.Message;
+
+
+
+/**
+ * This enumeration defines the set of password policy warnings that may
+ * be included in the password policy response control defined in
+ * draft-behera-ldap-password-policy.
+ */
+public class PasswordPolicyErrorType
+{
+ private static final PasswordPolicyErrorType[] ELEMENTS = new PasswordPolicyErrorType[9];
+
+ public static final PasswordPolicyErrorType PASSWORD_EXPIRED = register(
+ 0, INFO_PWPERRTYPE_DESCRIPTION_PASSWORD_EXPIRED.get());
+
+ public static final PasswordPolicyErrorType ACCOUNT_LOCKED = register(
+ 1, INFO_PWPERRTYPE_DESCRIPTION_ACCOUNT_LOCKED.get());
+
+ public static final PasswordPolicyErrorType CHANGE_AFTER_RESET = register(
+ 2, INFO_PWPERRTYPE_DESCRIPTION_CHANGE_AFTER_RESET.get());
+
+ public static final PasswordPolicyErrorType PASSWORD_MOD_NOT_ALLOWED = register(
+ 3, INFO_PWPERRTYPE_DESCRIPTION_PASSWORD_MOD_NOT_ALLOWED.get());
+
+ public static final PasswordPolicyErrorType MUST_SUPPLY_OLD_PASSWORD = register(
+ 4, INFO_PWPERRTYPE_DESCRIPTION_MUST_SUPPLY_OLD_PASSWORD.get());
+
+ public static final PasswordPolicyErrorType INSUFFICIENT_PASSWORD_QUALITY = register(
+ 5, INFO_PWPERRTYPE_DESCRIPTION_INSUFFICIENT_PASSWORD_QUALITY
+ .get());
+
+ public static final PasswordPolicyErrorType PASSWORD_TOO_SHORT = register(
+ 6, INFO_PWPERRTYPE_DESCRIPTION_PASSWORD_TOO_SHORT.get());
+
+ public static final PasswordPolicyErrorType PASSWORD_TOO_YOUNG = register(
+ 7, INFO_PWPERRTYPE_DESCRIPTION_PASSWORD_TOO_YOUNG.get());
+
+ public static final PasswordPolicyErrorType PASSWORD_IN_HISTORY = register(
+ 8, INFO_PWPERRTYPE_DESCRIPTION_PASSWORD_IN_HISTORY.get());
+
+
+
+ public static PasswordPolicyErrorType valueOf(int intValue)
+ {
+ PasswordPolicyErrorType e = ELEMENTS[intValue];
+ if (e == null)
+ {
+ e = new PasswordPolicyErrorType(intValue, Message
+ .raw("undefined(" + intValue + ")"));
+ }
+ return e;
+ }
+
+
+
+ public static List<PasswordPolicyErrorType> values()
+ {
+ return Arrays.asList(ELEMENTS);
+ }
+
+
+
+ private static PasswordPolicyErrorType register(int intValue,
+ Message name)
+ {
+ PasswordPolicyErrorType t = new PasswordPolicyErrorType(intValue,
+ name);
+ ELEMENTS[intValue] = t;
+ return t;
+ }
+
+
+
+ private final int intValue;
+
+ private final Message name;
+
+
+
+ private PasswordPolicyErrorType(int intValue, Message name)
+ {
+ this.intValue = intValue;
+ this.name = name;
+ }
+
+
+
+ @Override
+ public boolean equals(Object o)
+ {
+ return (this == o)
+ || ((o instanceof PasswordPolicyErrorType) && (this.intValue == ((PasswordPolicyErrorType) o).intValue));
+
+ }
+
+
+
+ @Override
+ public int hashCode()
+ {
+ return intValue;
+ }
+
+
+
+ public int intValue()
+ {
+ return intValue;
+ }
+
+
+
+ @Override
+ public String toString()
+ {
+ return name.toString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/controls/PasswordPolicyWarningType.java b/sdk/src/org/opends/sdk/controls/PasswordPolicyWarningType.java
new file mode 100644
index 0000000..880f561
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/PasswordPolicyWarningType.java
@@ -0,0 +1,107 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.INFO_PWPWARNTYPE_DESCRIPTION_GRACE_LOGINS_REMAINING;
+import static org.opends.messages.ProtocolMessages.INFO_PWPWARNTYPE_DESCRIPTION_TIME_BEFORE_EXPIRATION;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.opends.messages.Message;
+
+
+
+/**
+ * This enumeration defines the set of password policy warnings that may
+ * be included in the password policy response control defined in
+ * draft-behera-ldap-password-policy.
+ */
+public class PasswordPolicyWarningType
+{
+ private static final PasswordPolicyWarningType[] ELEMENTS = new PasswordPolicyWarningType[2];
+
+ public static final PasswordPolicyWarningType TIME_BEFORE_EXPIRATION = register(
+ 0, INFO_PWPWARNTYPE_DESCRIPTION_TIME_BEFORE_EXPIRATION.get());
+
+ public static final PasswordPolicyWarningType GRACE_LOGINS_REMAINING = register(
+ 1, INFO_PWPWARNTYPE_DESCRIPTION_GRACE_LOGINS_REMAINING.get());
+
+
+
+ public static PasswordPolicyWarningType valueOf(int intValue)
+ {
+ PasswordPolicyWarningType e = ELEMENTS[intValue];
+ if (e == null)
+ {
+ e = new PasswordPolicyWarningType(intValue, Message
+ .raw("undefined(" + intValue + ")"));
+ }
+ return e;
+ }
+
+
+
+ public static List<PasswordPolicyWarningType> values()
+ {
+ return Arrays.asList(ELEMENTS);
+ }
+
+
+
+ private static PasswordPolicyWarningType register(int intValue,
+ Message name)
+ {
+ PasswordPolicyWarningType t = new PasswordPolicyWarningType(
+ intValue, name);
+ ELEMENTS[intValue] = t;
+ return t;
+ }
+
+
+
+ private final int intValue;
+
+ private final Message name;
+
+
+
+ private PasswordPolicyWarningType(int intValue, Message name)
+ {
+ this.intValue = intValue;
+ this.name = name;
+ }
+
+
+
+ @Override
+ public boolean equals(Object o)
+ {
+ return (this == o)
+ || ((o instanceof PasswordPolicyWarningType) && (this.intValue == ((PasswordPolicyWarningType) o).intValue));
+
+ }
+
+
+
+ @Override
+ public int hashCode()
+ {
+ return intValue;
+ }
+
+
+
+ public int intValue()
+ {
+ return intValue;
+ }
+
+
+
+ @Override
+ public String toString()
+ {
+ return name.toString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/controls/PersistentSearchChangeType.java b/sdk/src/org/opends/sdk/controls/PersistentSearchChangeType.java
new file mode 100644
index 0000000..1749678
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/PersistentSearchChangeType.java
@@ -0,0 +1,108 @@
+package org.opends.sdk.controls;
+
+
+
+import java.util.Arrays;
+import java.util.List;
+
+
+
+/**
+ * This enumeration defines the set of possible change types that may be
+ * used in conjunction with the persistent search control, as defined in
+ * draft-ietf-ldapext-psearch.
+ */
+public final class PersistentSearchChangeType
+{
+ private static final PersistentSearchChangeType[] ELEMENTS = new PersistentSearchChangeType[4];
+
+ public static final PersistentSearchChangeType ADD = register(1,
+ "add");
+
+ public static final PersistentSearchChangeType DELETE = register(2,
+ "delete");
+
+ public static final PersistentSearchChangeType MODIFY = register(4,
+ "modify");
+
+ public static final PersistentSearchChangeType MODIFY_DN = register(
+ 8, "modify DN");
+
+
+
+ public static PersistentSearchChangeType valueOf(int intValue)
+ {
+ PersistentSearchChangeType e = ELEMENTS[intValue];
+ if (e == null)
+ {
+ e = new PersistentSearchChangeType(intValue, "undefined("
+ + intValue + ")");
+ }
+ return e;
+ }
+
+
+
+ public static List<PersistentSearchChangeType> values()
+ {
+ return Arrays.asList(ELEMENTS);
+ }
+
+
+
+ private static PersistentSearchChangeType register(int intValue,
+ String name)
+ {
+ PersistentSearchChangeType t = new PersistentSearchChangeType(
+ intValue, name);
+ ELEMENTS[intValue] = t;
+ return t;
+ }
+
+
+
+ private final int intValue;
+
+ private final String name;
+
+
+
+ private PersistentSearchChangeType(int intValue, String name)
+ {
+ this.intValue = intValue;
+ this.name = name;
+ }
+
+
+
+ @Override
+ public boolean equals(Object o)
+ {
+ return (this == o)
+ || ((o instanceof PersistentSearchChangeType) && (this.intValue == ((PersistentSearchChangeType) o).intValue));
+
+ }
+
+
+
+ @Override
+ public int hashCode()
+ {
+ return intValue;
+ }
+
+
+
+ public int intValue()
+ {
+ return intValue;
+ }
+
+
+
+ @Override
+ public String toString()
+ {
+ return name;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/controls/PersistentSearchControl.java b/sdk/src/org/opends/sdk/controls/PersistentSearchControl.java
new file mode 100644
index 0000000..d9afee4
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/PersistentSearchControl.java
@@ -0,0 +1,328 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_PSEARCH_CANNOT_DECODE_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_PSEARCH_NO_CONTROL_VALUE;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * This class implements the persistent search control defined in
+ * draft-ietf-ldapext-psearch. It makes it possible for clients to be
+ * notified of changes to information in the Directory Server as they
+ * occur.
+ */
+public class PersistentSearchControl extends Control
+{
+ /**
+ * The OID for the persistent search control.
+ */
+ public static final String OID_PERSISTENT_SEARCH = "2.16.840.1.113730.3.4.3";
+
+
+
+ /**
+ * ControlDecoder implentation to decode this control from a
+ * ByteString.
+ */
+ private final static class Decoder implements
+ ControlDecoder<PersistentSearchControl>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public PersistentSearchControl decode(boolean isCritical,
+ ByteString value, Schema schema) throws DecodeException
+ {
+ if (value == null)
+ {
+ Message message = ERR_PSEARCH_NO_CONTROL_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ ASN1Reader reader = ASN1.getReader(value);
+ boolean changesOnly;
+ boolean returnECs;
+ int changeTypes;
+ try
+ {
+ reader.readStartSequence();
+
+ changeTypes = (int) reader.readInteger();
+ changesOnly = reader.readBoolean();
+ returnECs = reader.readBoolean();
+
+ reader.readEndSequence();
+ }
+ catch (IOException e)
+ {
+ StaticUtils.DEBUG_LOG.throwing(
+ "PersistentSearchControl.Decoder", "decode", e);
+
+ Message message = ERR_PSEARCH_CANNOT_DECODE_VALUE
+ .get(getExceptionMessage(e));
+ throw DecodeException.error(message, e);
+ }
+
+ return new PersistentSearchControl(isCritical, changeTypes,
+ changesOnly, returnECs);
+ }
+
+
+
+ public String getOID()
+ {
+ return OID_PERSISTENT_SEARCH;
+ }
+
+ }
+
+
+
+ /**
+ * The Control Decoder that can be used to decode this control.
+ */
+ public static final ControlDecoder<PersistentSearchControl> DECODER = new Decoder();
+
+ // Indicates whether to only return entries that have been updated
+ // since the
+ // beginning of the search.
+ private final boolean changesOnly;
+
+ // Indicates whether entries returned as a result of changes to
+ // directory data
+ // should include the entry change notification control.
+ private final boolean returnECs;
+
+ // The logical OR of change types associated with this control.
+ private int changeTypes;
+
+
+
+ /**
+ * Creates a new persistent search control with the provided
+ * information.
+ *
+ * @param isCritical
+ * Indicates whether the control should be considered
+ * critical for the operation processing.
+ * @param changesOnly
+ * Indicates whether to only return changes that match the
+ * associated search criteria, or to also return all existing
+ * entries that match the filter.
+ * @param returnECs
+ * Indicates whether to include the entry change notification
+ * control in updated entries that match the associated
+ * search criteria.
+ * @param changeTypes
+ * The change types for which to provide notification to the
+ * client.
+ */
+ public PersistentSearchControl(boolean isCritical,
+ boolean changesOnly, boolean returnECs,
+ PersistentSearchChangeType... changeTypes)
+ {
+ super(OID_PERSISTENT_SEARCH, isCritical);
+
+ this.changeTypes = 0;
+ this.changesOnly = changesOnly;
+ this.returnECs = returnECs;
+
+ if (changeTypes != null)
+ {
+ for (PersistentSearchChangeType type : changeTypes)
+ {
+ this.changeTypes |= type.intValue();
+ }
+ }
+ }
+
+
+
+ /**
+ * Creates a new persistent search control with the provided
+ * information.
+ *
+ * @param changesOnly
+ * Indicates whether to only return changes that match the
+ * associated search criteria, or to also return all existing
+ * entries that match the filter.
+ * @param returnECs
+ * Indicates whether to include the entry change notification
+ * control in updated entries that match the associated
+ * search criteria.
+ * @param changeTypes
+ * The set of change types for which to provide notification
+ * to the client.
+ */
+ public PersistentSearchControl(boolean changesOnly,
+ boolean returnECs, PersistentSearchChangeType... changeTypes)
+ {
+ this(true, changesOnly, returnECs, changeTypes);
+ }
+
+
+
+ private PersistentSearchControl(boolean isCritical, int changeTypes,
+ boolean changesOnly, boolean returnECs)
+ {
+ super(OID_PERSISTENT_SEARCH, isCritical);
+
+ this.changeTypes = changeTypes;
+ this.changesOnly = changesOnly;
+ this.returnECs = returnECs;
+ }
+
+
+
+ public PersistentSearchControl addChangeType(
+ PersistentSearchChangeType type)
+ {
+ changeTypes |= type.intValue();
+ return this;
+ }
+
+
+
+ /**
+ * Indicates if the change type is included in this persistent search
+ * control.
+ *
+ * @param type
+ * The change type whose presence is to be tested.
+ * @return <code>true</code> if the change type is included or
+ * <code>false</code> otherwise.
+ */
+ public boolean containsChangeType(PersistentSearchChangeType type)
+ {
+ return (changeTypes & type.intValue()) == type.intValue();
+ }
+
+
+
+ /**
+ * Indicates whether to only return changes that match the associated
+ * search criteria, or to also return all existing entries that match
+ * the filter.
+ *
+ * @return <CODE>true</CODE> if only changes to matching entries
+ * should be returned, or <CODE>false</CODE> if existing
+ * matches should also be included.
+ */
+ public boolean getChangesOnly()
+ {
+ return changesOnly;
+ }
+
+
+
+ /**
+ * Indicates whether to include the entry change notification control
+ * in entries returned to the client as the result of a change in the
+ * Directory Server data.
+ *
+ * @return <CODE>true</CODE> if entry change notification controls
+ * should be included in applicable entries, or
+ * <CODE>false</CODE> if not.
+ */
+ public boolean getReturnECs()
+ {
+ return returnECs;
+ }
+
+
+
+ @Override
+ public ByteString getValue()
+ {
+ ByteStringBuilder buffer = new ByteStringBuilder();
+ ASN1Writer writer = ASN1.getWriter(buffer);
+ try
+ {
+ writer.writeStartSequence();
+ writer.writeInteger(changeTypes);
+ writer.writeBoolean(changesOnly);
+ writer.writeBoolean(returnECs);
+ writer.writeEndSequence();
+ return buffer.toByteString();
+ }
+ catch (IOException ioe)
+ {
+ // This should never happen unless there is a bug somewhere.
+ throw new RuntimeException(ioe);
+ }
+ }
+
+
+
+ @Override
+ public boolean hasValue()
+ {
+ return true;
+ }
+
+
+
+ public PersistentSearchControl removeChangeType(
+ PersistentSearchChangeType type)
+ {
+ changeTypes &= ~type.intValue();
+ return this;
+ }
+
+
+
+ /**
+ * Appends a string representation of this persistent search control
+ * to the provided buffer.
+ *
+ * @param buffer
+ * The buffer to which the information should be appended.
+ */
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("PersistentSearchControl(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+ buffer.append(", changeTypes=[");
+
+ boolean comma = false;
+ for (PersistentSearchChangeType type : PersistentSearchChangeType
+ .values())
+ {
+ if (containsChangeType(type))
+ {
+ if (comma)
+ {
+ buffer.append(", ");
+ }
+ buffer.append(type);
+ comma = true;
+ }
+ }
+
+ buffer.append("](");
+ buffer.append(changeTypes);
+ buffer.append("), changesOnly=");
+ buffer.append(changesOnly);
+ buffer.append(", returnECs=");
+ buffer.append(returnECs);
+ buffer.append(")");
+ }
+}
diff --git a/sdk/src/org/opends/sdk/controls/PostReadControl.java b/sdk/src/org/opends/sdk/controls/PostReadControl.java
new file mode 100644
index 0000000..c835007
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/PostReadControl.java
@@ -0,0 +1,447 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_POSTREADREQ_CANNOT_DECODE_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_POSTREADREQ_NO_CONTROL_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_POSTREADRESP_CANNOT_DECODE_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_POSTREADRESP_NO_CONTROL_VALUE;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.ldap.LDAPUtils;
+import org.opends.sdk.responses.SearchResultEntry;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class implements the post-read control as defined in RFC 4527.
+ * This control makes it possible to retrieve an entry in the state that
+ * it held immediately after an add, modify, or modify DN operation. It
+ * may specify a specific set of attributes that should be included in
+ * that entry.
+ */
+public class PostReadControl
+{
+ /**
+ * The IANA-assigned OID for the LDAP readentry control used for
+ * retrieving an
+ * entry in the state it had immediately after an update was applied.
+ */
+ public static final String OID_LDAP_READENTRY_POSTREAD = "1.3.6.1.1.13.2";
+
+
+
+ /**
+ * This class implements the post-read request control as defined in
+ * RFC 4527. This control makes it possible to retrieve an entry in
+ * the state that it held immediately after an add, modify, or modify
+ * DN operation. It may specify a specific set of attributes that
+ * should be included in that entry. The entry will be encoded in a
+ * corresponding response control.
+ */
+ public static class Request extends Control
+ {
+ // The set of raw attributes to return in the entry.
+ private final Set<String> attributes;
+
+
+
+ /**
+ * Creates a new post-read request control with the provided
+ * information.
+ *
+ * @param isCritical
+ * Indicates whether support for this control should be
+ * considered a critical part of the server processing.
+ * @param attributeDescriptions
+ * The names of the attributes to be included with the
+ * response control.
+ */
+ public Request(boolean isCritical, String... attributeDescriptions)
+ {
+ super(OID_LDAP_READENTRY_POSTREAD, isCritical);
+
+ this.attributes = new LinkedHashSet<String>();
+ if (attributeDescriptions != null)
+ {
+ this.attributes.addAll(Arrays.asList(attributeDescriptions));
+ }
+ }
+
+
+
+ private Request(boolean isCritical, Set<String> attributes)
+ {
+ super(OID_LDAP_READENTRY_POSTREAD, isCritical);
+
+ this.attributes = attributes;
+ }
+
+
+
+ /**
+ * Adds the provided attribute name to the list of attributes to be
+ * included in the response control. Attributes that are sub-types
+ * of listed attributes are implicitly included.
+ *
+ * @param attributeDescription
+ * The name of the attribute to be included in the response
+ * control.
+ * @return This post-read control.
+ * @throws NullPointerException
+ * If {@code attributeDescription} was {@code null}.
+ */
+ public Request addAttribute(String attributeDescription)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(attributeDescription);
+ attributes.add(attributeDescription);
+ return this;
+ }
+
+
+
+ /**
+ * Returns an {@code Iterable} containing the list of attributes to
+ * be included with the response control. Attributes that are
+ * sub-types of listed attributes are implicitly included.
+ *
+ * @return An {@code Iterable} containing the list of attributes.
+ */
+ public Iterable<String> getAttributes()
+ {
+ return attributes;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ByteString getValue()
+ {
+ ByteStringBuilder buffer = new ByteStringBuilder();
+ ASN1Writer writer = ASN1.getWriter(buffer);
+ try
+ {
+ writer.writeStartSequence();
+ if (attributes != null)
+ {
+ for (String attr : attributes)
+ {
+ writer.writeOctetString(attr);
+ }
+ }
+ writer.writeEndSequence();
+ return buffer.toByteString();
+ }
+ catch (IOException ioe)
+ {
+ // This should never happen unless there is a bug somewhere.
+ throw new RuntimeException(ioe);
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean hasValue()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("PostReadRequestControl(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+ buffer.append(", attributes=");
+ buffer.append(attributes);
+ buffer.append(")");
+ }
+ }
+
+
+
+ /**
+ * This class implements the post-read response control as defined in
+ * RFC 4527. This control holds the search result entry representing
+ * the state of the entry immediately after an add, modify, or modify
+ * DN operation.
+ */
+ public static class Response extends Control
+ {
+ private final SearchResultEntry entry;
+
+
+
+ /**
+ * Creates a new post-read response control with the provided
+ * information.
+ *
+ * @param isCritical
+ * Indicates whether support for this control should be
+ * considered a critical part of the server processing.
+ * @param searchEntry
+ * The search result entry to include in the response
+ * control.
+ */
+ public Response(boolean isCritical, SearchResultEntry searchEntry)
+ {
+ super(OID_LDAP_READENTRY_POSTREAD, isCritical);
+
+ this.entry = searchEntry;
+ }
+
+
+
+ /**
+ * Creates a new post-read response control with the provided
+ * information.
+ *
+ * @param searchEntry
+ * The search result entry to include in the response
+ * control.
+ */
+ public Response(SearchResultEntry searchEntry)
+ {
+ this(false, searchEntry);
+ }
+
+
+
+ /**
+ * Returns the search result entry associated with this post-read
+ * response control.
+ *
+ * @return The search result entry associated with this post-read
+ * response control.
+ */
+ public SearchResultEntry getSearchEntry()
+ {
+ return entry;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ByteString getValue()
+ {
+ ByteStringBuilder buffer = new ByteStringBuilder();
+ ASN1Writer writer = ASN1.getWriter(buffer);
+ try
+ {
+ LDAPUtils.encodeSearchResultEntry(writer, entry);
+ return buffer.toByteString();
+ }
+ catch (IOException ioe)
+ {
+ // This should never happen unless there is a bug somewhere.
+ throw new RuntimeException(ioe);
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean hasValue()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("PostReadResponseControl(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+ buffer.append(", entry=");
+ buffer.append(entry);
+ buffer.append(")");
+ }
+ }
+
+
+
+ /**
+ * Decodes a post-read request control from a byte string.
+ */
+ private final static class RequestDecoder implements
+ ControlDecoder<Request>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public Request decode(boolean isCritical, ByteString value, Schema schema)
+ throws DecodeException
+ {
+ if (value == null)
+ {
+ Message message = ERR_POSTREADREQ_NO_CONTROL_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ ASN1Reader reader = ASN1.getReader(value);
+ LinkedHashSet<String> attributes = new LinkedHashSet<String>();
+ try
+ {
+ reader.readStartSequence();
+ while (reader.hasNextElement())
+ {
+ attributes.add(reader.readOctetStringAsString());
+ }
+ reader.readEndSequence();
+ }
+ catch (Exception ae)
+ {
+ StaticUtils.DEBUG_LOG.throwing(
+ "PersistentSearchControl.RequestDecoder", "decode", ae);
+
+ Message message = ERR_POSTREADREQ_CANNOT_DECODE_VALUE.get(ae
+ .getMessage());
+ throw DecodeException.error(message, ae);
+ }
+
+ return new Request(isCritical, attributes);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getOID()
+ {
+ return OID_LDAP_READENTRY_POSTREAD;
+ }
+ }
+
+
+
+ /**
+ * Decodes a post-read response control from a byte string.
+ */
+ private final static class ResponseDecoder implements
+ ControlDecoder<Response>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public Response decode(boolean isCritical, ByteString value, Schema schema)
+ throws DecodeException
+ {
+ if (value == null)
+ {
+ Message message = ERR_POSTREADRESP_NO_CONTROL_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ ASN1Reader reader = ASN1.getReader(value);
+ SearchResultEntry searchEntry;
+ try
+ {
+ searchEntry = LDAPUtils.decodeSearchResultEntry(reader, schema);
+ }
+ catch (IOException le)
+ {
+ StaticUtils.DEBUG_LOG.throwing(
+ "PersistentSearchControl.ResponseDecoder", "decode", le);
+
+ Message message = ERR_POSTREADRESP_CANNOT_DECODE_VALUE.get(le
+ .getMessage());
+ throw DecodeException.error(message, le);
+ }
+
+ return new Response(isCritical, searchEntry);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getOID()
+ {
+ return OID_LDAP_READENTRY_POSTREAD;
+ }
+
+ }
+
+
+
+ /**
+ * A control decoder which can be used to decode post-read request
+ * controls.
+ */
+ public static final ControlDecoder<Request> REQUEST_DECODER = new RequestDecoder();
+
+ /**
+ * A control decoder which can be used to decode post-read respoens
+ * controls.
+ */
+ public static final ControlDecoder<Response> RESPONSE_DECODER = new ResponseDecoder();
+}
diff --git a/sdk/src/org/opends/sdk/controls/PreReadControl.java b/sdk/src/org/opends/sdk/controls/PreReadControl.java
new file mode 100644
index 0000000..050aaac
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/PreReadControl.java
@@ -0,0 +1,447 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_PREREADREQ_CANNOT_DECODE_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_PREREADREQ_NO_CONTROL_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_PREREADRESP_CANNOT_DECODE_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_PREREADRESP_NO_CONTROL_VALUE;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.ldap.LDAPUtils;
+import org.opends.sdk.responses.SearchResultEntry;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class implements the pre-read request control as defined in RFC
+ * 4527. This control makes it possible to retrieve an entry in the
+ * state that it held immediately before a modify, delete, or modify DN
+ * operation. It may specify a specific set of attributes that should be
+ * included in that entry.
+ */
+public class PreReadControl
+{
+ /**
+ * The IANA-assigned OID for the LDAP readentry control used for
+ * retrieving an
+ * entry in the state it had immediately before an update was applied.
+ */
+ public static final String OID_LDAP_READENTRY_PREREAD = "1.3.6.1.1.13.1";
+
+
+
+ /**
+ * This class implements the pre-read request control as defined in
+ * RFC 4527. This control makes it possible to retrieve an entry in
+ * the state that it held immediately before a modify, delete, or
+ * modify DN operation. It may specify a specific set of attributes
+ * that should be included in that entry. The entry will be encoded in
+ * a corresponding response control.
+ */
+ public static class Request extends Control
+ {
+ // The set of raw attributes to return in the entry.
+ private final Set<String> attributes;
+
+
+
+ /**
+ * Creates a new pre-read request control with the provided
+ * information.
+ *
+ * @param isCritical
+ * Indicates whether support for this control should be
+ * considered a critical part of the server processing.
+ * @param attributeDescriptions
+ * The names of the attributes to be included with the
+ * response control.
+ */
+ public Request(boolean isCritical, String... attributeDescriptions)
+ {
+ super(OID_LDAP_READENTRY_PREREAD, isCritical);
+
+ this.attributes = new LinkedHashSet<String>();
+ if (attributeDescriptions != null)
+ {
+ this.attributes.addAll(Arrays.asList(attributeDescriptions));
+ }
+ }
+
+
+
+ private Request(boolean isCritical, Set<String> attributes)
+ {
+ super(OID_LDAP_READENTRY_PREREAD, isCritical);
+
+ this.attributes = attributes;
+ }
+
+
+
+ /**
+ * Adds the provided attribute name to the list of attributes to be
+ * included in the request control. Attributes that are sub-types
+ * of listed attributes are implicitly included.
+ *
+ * @param attributeDescription
+ * The name of the attribute to be included in the response
+ * control.
+ * @return This post-read control.
+ * @throws NullPointerException
+ * If {@code attributeDescription} was {@code null}.
+ */
+ public Request addAttribute(String attributeDescription)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(attributeDescription);
+ attributes.add(attributeDescription);
+ return this;
+ }
+
+
+
+ /**
+ * Returns an {@code Iterable} containing the list of attributes to
+ * be included with the response control. Attributes that are
+ * sub-types of listed attributes are implicitly included.
+ *
+ * @return An {@code Iterable} containing the list of attributes.
+ */
+ public Iterable<String> getAttributes()
+ {
+ return attributes;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ByteString getValue()
+ {
+ ByteStringBuilder buffer = new ByteStringBuilder();
+ ASN1Writer writer = ASN1.getWriter(buffer);
+ try
+ {
+ writer.writeStartSequence();
+ if (attributes != null)
+ {
+ for (String attr : attributes)
+ {
+ writer.writeOctetString(attr);
+ }
+ }
+ writer.writeEndSequence();
+ return buffer.toByteString();
+ }
+ catch (IOException ioe)
+ {
+ // This should never happen unless there is a bug somewhere.
+ throw new RuntimeException(ioe);
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean hasValue()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("PreReadRequestControl(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+ buffer.append(", attributes=");
+ buffer.append(attributes);
+ buffer.append(")");
+ }
+ }
+
+
+
+ /**
+ * This class implements the pre-read response control as defined in
+ * RFC 4527. This control holds the search result entry representing
+ * the state of the entry immediately before an add, modify, or modify
+ * DN operation.
+ */
+ public static class Response extends Control
+ {
+ private final SearchResultEntry entry;
+
+
+
+ /**
+ * Creates a new pre-read response control with the provided
+ * information.
+ *
+ * @param isCritical
+ * Indicates whether support for this control should be
+ * considered a critical part of the server processing.
+ * @param searchEntry
+ * The search result entry to include in the response
+ * control.
+ */
+ public Response(boolean isCritical, SearchResultEntry searchEntry)
+ {
+ super(OID_LDAP_READENTRY_PREREAD, isCritical);
+
+ this.entry = searchEntry;
+ }
+
+
+
+ /**
+ * Creates a new pre-read response control with the provided
+ * information.
+ *
+ * @param searchEntry
+ * The search result entry to include in the response
+ * control.
+ */
+ public Response(SearchResultEntry searchEntry)
+ {
+ this(false, searchEntry);
+ }
+
+
+
+ /**
+ * Returns the search result entry associated with this post-read
+ * response control.
+ *
+ * @return The search result entry associated with this post-read
+ * response control.
+ */
+ public SearchResultEntry getSearchEntry()
+ {
+ return entry;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ByteString getValue()
+ {
+ ByteStringBuilder buffer = new ByteStringBuilder();
+ ASN1Writer writer = ASN1.getWriter(buffer);
+ try
+ {
+ LDAPUtils.encodeSearchResultEntry(writer, entry);
+ return buffer.toByteString();
+ }
+ catch (IOException ioe)
+ {
+ // This should never happen unless there is a bug somewhere.
+ throw new RuntimeException(ioe);
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean hasValue()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("PreReadResponseControl(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+ buffer.append(", entry=");
+ buffer.append(entry);
+ buffer.append(")");
+ }
+ }
+
+
+
+ /**
+ * Decodes a pre-read request control from a byte string.
+ */
+ private final static class RequestDecoder implements
+ ControlDecoder<Request>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public Request decode(boolean isCritical, ByteString value, Schema schema)
+ throws DecodeException
+ {
+ if (value == null)
+ {
+ Message message = ERR_PREREADREQ_NO_CONTROL_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ ASN1Reader reader = ASN1.getReader(value);
+ LinkedHashSet<String> attributes = new LinkedHashSet<String>();
+ try
+ {
+ reader.readStartSequence();
+ while (reader.hasNextElement())
+ {
+ attributes.add(reader.readOctetStringAsString());
+ }
+ reader.readEndSequence();
+ }
+ catch (Exception ae)
+ {
+ StaticUtils.DEBUG_LOG.throwing("PreReadControl.RequestDecoder",
+ "decode", ae);
+
+ Message message = ERR_PREREADREQ_CANNOT_DECODE_VALUE.get(ae
+ .getMessage());
+ throw DecodeException.error(message, ae);
+ }
+
+ return new Request(isCritical, attributes);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getOID()
+ {
+ return OID_LDAP_READENTRY_PREREAD;
+ }
+ }
+
+
+
+ /**
+ * Decodes a pre-read response control from a byte string.
+ */
+ private final static class ResponseDecoder implements
+ ControlDecoder<Response>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public Response decode(boolean isCritical, ByteString value, Schema schema)
+ throws DecodeException
+ {
+ if (value == null)
+ {
+ Message message = ERR_PREREADRESP_NO_CONTROL_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ ASN1Reader reader = ASN1.getReader(value);
+ SearchResultEntry searchEntry;
+ try
+ {
+ searchEntry = LDAPUtils.decodeSearchResultEntry(reader, schema);
+ }
+ catch (IOException le)
+ {
+ StaticUtils.DEBUG_LOG.throwing(
+ "PersistentSearchControl.ResponseDecoder", "decode", le);
+
+ Message message = ERR_PREREADRESP_CANNOT_DECODE_VALUE.get(le
+ .getMessage());
+ throw DecodeException.error(message, le);
+ }
+
+ return new Response(isCritical, searchEntry);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getOID()
+ {
+ return OID_LDAP_READENTRY_PREREAD;
+ }
+
+ }
+
+
+
+ /**
+ * A control decoder which can be used to decode pre-read request
+ * controls.
+ */
+ public static final ControlDecoder<Request> REQUEST_DECODER = new RequestDecoder();
+
+ /**
+ * A control decoder which can be used to decode pre-read respoens
+ * controls.
+ */
+ public static final ControlDecoder<Response> RESPONSE_DECODER = new ResponseDecoder();
+}
diff --git a/sdk/src/org/opends/sdk/controls/ProxiedAuthV1Control.java b/sdk/src/org/opends/sdk/controls/ProxiedAuthV1Control.java
new file mode 100644
index 0000000..0b51fb1
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/ProxiedAuthV1Control.java
@@ -0,0 +1,212 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_PROXYAUTH1_CANNOT_DECODE_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_PROXYAUTH1_CONTROL_NOT_CRITICAL;
+import static org.opends.messages.ProtocolMessages.ERR_PROXYAUTH1_NO_CONTROL_VALUE;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DN;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class implements version 1 of the proxied authorization control
+ * as defined in early versions of draft-weltman-ldapv3-proxy (this
+ * implementation is based on the "-04" revision). It makes it possible
+ * for one user to request that an operation be performed under the
+ * authorization of another. The target user is specified as a DN in the
+ * control value, which distinguishes it from later versions of the
+ * control (which used a different OID) in which the target user was
+ * specified using an authorization ID.
+ */
+public class ProxiedAuthV1Control extends Control
+{
+ /**
+ * The OID for the proxied authorization v1 control.
+ */
+ public static final String OID_PROXIED_AUTH_V1 = "2.16.840.1.113730.3.4.12";
+
+
+
+ /**
+ * ControlDecoder implentation to decode this control from a
+ * ByteString.
+ */
+ private final static class Decoder implements
+ ControlDecoder<ProxiedAuthV1Control>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public ProxiedAuthV1Control decode(boolean isCritical,
+ ByteString value, Schema schema) throws DecodeException
+ {
+ if (!isCritical)
+ {
+ Message message = ERR_PROXYAUTH1_CONTROL_NOT_CRITICAL.get();
+ throw DecodeException.error(message);
+ }
+
+ if (value == null)
+ {
+ Message message = ERR_PROXYAUTH1_NO_CONTROL_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ ASN1Reader reader = ASN1.getReader(value);
+ String authorizationDN;
+ try
+ {
+ reader.readStartSequence();
+ authorizationDN = reader.readOctetStringAsString();
+ reader.readEndSequence();
+ }
+ catch (IOException e)
+ {
+ StaticUtils.DEBUG_LOG.throwing("ProxiedAuthV1Control.Decoder",
+ "decode", e);
+
+ Message message = ERR_PROXYAUTH1_CANNOT_DECODE_VALUE
+ .get(getExceptionMessage(e));
+ throw DecodeException.error(message, e);
+ }
+
+ return new ProxiedAuthV1Control(authorizationDN);
+ }
+
+
+
+ public String getOID()
+ {
+ return OID_PROXIED_AUTH_V1;
+ }
+
+ }
+
+
+
+ /**
+ * The Control Decoder that can be used to decode this control.
+ */
+ public static final ControlDecoder<ProxiedAuthV1Control> DECODER = new Decoder();
+
+ // The raw, unprocessed authorization DN from the control value.
+ private String authorizationDN;
+
+
+
+ /**
+ * Creates a new instance of the proxied authorization v1 control with
+ * the provided information.
+ *
+ * @param authorizationDN
+ * The authorization DN from the control value. It must not
+ * be {@code null}.
+ */
+ public ProxiedAuthV1Control(DN authorizationDN)
+ {
+ super(OID_PROXIED_AUTH_V1, true);
+
+ Validator.ensureNotNull(authorizationDN);
+ this.authorizationDN = authorizationDN.toString();
+ }
+
+
+
+ /**
+ * Creates a new instance of the proxied authorization v1 control with
+ * the provided information.
+ *
+ * @param authorizationDN
+ * The raw, unprocessed authorization DN from the control
+ * value. It must not be {@code null}.
+ */
+ public ProxiedAuthV1Control(String authorizationDN)
+ {
+ super(OID_PROXIED_AUTH_V1, true);
+
+ Validator.ensureNotNull(authorizationDN);
+ this.authorizationDN = authorizationDN;
+ }
+
+
+
+ /**
+ * Retrieves the raw, unprocessed authorization DN from the control
+ * value.
+ *
+ * @return The raw, unprocessed authorization DN from the control
+ * value.
+ */
+ public String getAuthorizationDN()
+ {
+ return authorizationDN;
+ }
+
+
+
+ @Override
+ public ByteString getValue()
+ {
+ return ByteString.valueOf(authorizationDN);
+ }
+
+
+
+ @Override
+ public boolean hasValue()
+ {
+ return true;
+ }
+
+
+
+ public ProxiedAuthV1Control setAuthorizationDN(DN authorizationDN)
+ {
+ Validator.ensureNotNull(authorizationDN);
+ this.authorizationDN = authorizationDN.toString();
+ return this;
+ }
+
+
+
+ public ProxiedAuthV1Control setAuthorizationDN(String authorizationDN)
+ {
+ Validator.ensureNotNull(authorizationDN);
+ this.authorizationDN = authorizationDN;
+ return this;
+ }
+
+
+
+ /**
+ * Appends a string representation of this proxied auth v1 control to
+ * the provided buffer.
+ *
+ * @param buffer
+ * The buffer to which the information should be appended.
+ */
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("ProxiedAuthorizationV1Control(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+ buffer.append(", authorizationDN=\"");
+ buffer.append(authorizationDN);
+ buffer.append("\")");
+ }
+}
diff --git a/sdk/src/org/opends/sdk/controls/ProxiedAuthV2Control.java b/sdk/src/org/opends/sdk/controls/ProxiedAuthV2Control.java
new file mode 100644
index 0000000..3a1e2ec
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/ProxiedAuthV2Control.java
@@ -0,0 +1,216 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_PROXYAUTH2_CANNOT_DECODE_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_PROXYAUTH2_CONTROL_NOT_CRITICAL;
+import static org.opends.messages.ProtocolMessages.ERR_PROXYAUTH2_NO_CONTROL_VALUE;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DN;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class implements version 2 of the proxied authorization control
+ * as defined in RFC 4370. It makes it possible for one user to request
+ * that an operation be performed under the authorization of another.
+ * The target user is specified using an authorization ID, which may be
+ * in the form "dn:" immediately followed by the DN of that user, or
+ * "u:" followed by a user ID string.
+ */
+public class ProxiedAuthV2Control extends Control
+{
+ /**
+ * The OID for the proxied authorization v2 control.
+ */
+ public static final String OID_PROXIED_AUTH_V2 = "2.16.840.1.113730.3.4.18";
+
+
+
+ /**
+ * ControlDecoder implentation to decode this control from a
+ * ByteString.
+ */
+ private static final class Decoder implements
+ ControlDecoder<ProxiedAuthV2Control>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public ProxiedAuthV2Control decode(boolean isCritical,
+ ByteString value, Schema schema) throws DecodeException
+ {
+ if (!isCritical)
+ {
+ Message message = ERR_PROXYAUTH2_CONTROL_NOT_CRITICAL.get();
+ throw DecodeException.error(message);
+ }
+
+ if (value == null)
+ {
+ Message message = ERR_PROXYAUTH2_NO_CONTROL_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ ASN1Reader reader = ASN1.getReader(value);
+ String authorizationID;
+
+ try
+ {
+ if (reader.elementAvailable())
+ {
+ // Try the legacy encoding where the value is wrapped by an
+ // extra octet string
+ authorizationID = reader.readOctetStringAsString();
+ }
+ else
+ {
+ authorizationID = value.toString();
+ }
+ }
+ catch (IOException e)
+ {
+ StaticUtils.DEBUG_LOG.throwing("ProxiedAuthV2Control.Decoder",
+ "decode", e);
+
+ Message message = ERR_PROXYAUTH2_CANNOT_DECODE_VALUE
+ .get(getExceptionMessage(e));
+ throw DecodeException.error(message, e);
+ }
+
+ return new ProxiedAuthV2Control(authorizationID);
+ }
+
+
+
+ public String getOID()
+ {
+ return OID_PROXIED_AUTH_V2;
+ }
+
+ }
+
+
+
+ /**
+ * The Control Decoder that can be used to decode this control.
+ */
+ public static final ControlDecoder<ProxiedAuthV2Control> DECODER = new Decoder();
+
+ // The authorization ID from the control value.
+ private String authorizationID;
+
+
+
+ /**
+ * Creates a new instance of the proxied authorization v2 control with
+ * the provided information.
+ *
+ * @param authorizationDN
+ * The authorization DN.
+ */
+ public ProxiedAuthV2Control(DN authorizationDN)
+ {
+ super(OID_PROXIED_AUTH_V2, true);
+
+ Validator.ensureNotNull(authorizationID);
+ this.authorizationID = "dn:" + authorizationDN.toString();
+ }
+
+
+
+ /**
+ * Creates a new instance of the proxied authorization v2 control with
+ * the provided information.
+ *
+ * @param authorizationID
+ * The authorization ID.
+ */
+ public ProxiedAuthV2Control(String authorizationID)
+ {
+ super(OID_PROXIED_AUTH_V2, true);
+
+ Validator.ensureNotNull(authorizationID);
+ this.authorizationID = authorizationID;
+ }
+
+
+
+ /**
+ * Retrieves the authorization ID for this proxied authorization V2
+ * control.
+ *
+ * @return The authorization ID for this proxied authorization V2
+ * control.
+ */
+ public String getAuthorizationID()
+ {
+ return authorizationID;
+ }
+
+
+
+ @Override
+ public ByteString getValue()
+ {
+ return ByteString.valueOf(authorizationID);
+ }
+
+
+
+ @Override
+ public boolean hasValue()
+ {
+ return true;
+ }
+
+
+
+ public ProxiedAuthV2Control setAuthorizationID(DN authorizationDN)
+ {
+ Validator.ensureNotNull(authorizationDN);
+ this.authorizationID = "dn:" + authorizationDN.toString();
+ return this;
+ }
+
+
+
+ public ProxiedAuthV2Control setAuthorizationID(String authorizationID)
+ {
+ Validator.ensureNotNull(authorizationID);
+ this.authorizationID = authorizationID;
+ return this;
+ }
+
+
+
+ /**
+ * Appends a string representation of this proxied auth v2 control to
+ * the provided buffer.
+ *
+ * @param buffer
+ * The buffer to which the information should be appended.
+ */
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("ProxiedAuthorizationV2Control(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+ buffer.append(", authorizationDN=\"");
+ buffer.append(authorizationID);
+ buffer.append("\")");
+ }
+}
diff --git a/sdk/src/org/opends/sdk/controls/ServerSideSortControl.java b/sdk/src/org/opends/sdk/controls/ServerSideSortControl.java
new file mode 100644
index 0000000..f62e780
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/ServerSideSortControl.java
@@ -0,0 +1,549 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.*;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class implements the server-side sort control as defined in RFC
+ * 2891.
+ */
+public class ServerSideSortControl
+{
+ /**
+ * The OID for the server-side sort request control.
+ */
+ public static final String OID_SERVER_SIDE_SORT_REQUEST_CONTROL = "1.2.840.113556.1.4.473";
+
+ /**
+ * The OID for the server-side sort response control.
+ */
+ public static final String OID_SERVER_SIDE_SORT_RESPONSE_CONTROL = "1.2.840.113556.1.4.474";
+
+
+
+ /**
+ * This class implements the server-side sort request control as
+ * defined in RFC 2891 section 1.1. The ASN.1 description for the
+ * control value is: <BR>
+ * <BR>
+ *
+ * <PRE>
+ * SortKeyList ::= SEQUENCE OF SEQUENCE {
+ * attributeType AttributeDescription,
+ * orderingRule [0] MatchingRuleId OPTIONAL,
+ * reverseOrder [1] BOOLEAN DEFAULT FALSE }
+ * </PRE>
+ */
+ public static class Request extends Control
+ {
+ private final List<SortKey> sortKeys = new ArrayList<SortKey>();
+
+
+
+ public Request(boolean isCritical, SortKey... sortKeys)
+ {
+ super(OID_SERVER_SIDE_SORT_REQUEST_CONTROL, isCritical);
+ addSortKey(sortKeys);
+ }
+
+
+
+ public Request(boolean isCritical, String sortOrderString)
+ throws DecodeException
+ {
+ super(OID_SERVER_SIDE_SORT_REQUEST_CONTROL, isCritical);
+
+ decodeSortOrderString(sortOrderString, sortKeys);
+ }
+
+
+
+ public Request(SortKey... sortKeys)
+ {
+ this(false, sortKeys);
+ }
+
+
+
+ public Request(String sortOrderString) throws DecodeException
+ {
+ this(false, sortOrderString);
+ }
+
+
+
+ public Request addSortKey(SortKey... sortKeys)
+ {
+ if (sortKeys != null)
+ {
+ for (SortKey sortKey : sortKeys)
+ {
+ Validator.ensureNotNull(sortKey);
+ this.sortKeys.add(sortKey);
+ }
+ }
+ return this;
+ }
+
+
+
+ public Request addSortKey(String sortOrderString)
+ throws DecodeException
+ {
+ decodeSortOrderString(sortOrderString, sortKeys);
+ return this;
+ }
+
+
+
+ public Iterable<SortKey> getSortKeys()
+ {
+ return sortKeys;
+ }
+
+
+
+ @Override
+ public ByteString getValue()
+ {
+ ByteStringBuilder buffer = new ByteStringBuilder();
+ ASN1Writer writer = ASN1.getWriter(buffer);
+ try
+ {
+ writer.writeStartSequence();
+ for (SortKey sortKey : sortKeys)
+ {
+ writer.writeStartSequence();
+ writer.writeOctetString(sortKey.getAttributeDescription());
+
+ if (sortKey.getOrderingRule() != null)
+ {
+ writer.writeOctetString(TYPE_ORDERING_RULE_ID, sortKey
+ .getOrderingRule());
+ }
+
+ if (!sortKey.isReverseOrder())
+ {
+ writer.writeBoolean(TYPE_REVERSE_ORDER, true);
+ }
+
+ writer.writeEndSequence();
+ }
+ writer.writeEndSequence();
+ return buffer.toByteString();
+ }
+ catch (IOException ioe)
+ {
+ // This should never happen unless there is a bug somewhere.
+ throw new RuntimeException(ioe);
+ }
+ }
+
+
+
+ @Override
+ public boolean hasValue()
+ {
+ return true;
+ }
+
+
+
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("ServerSideSortRequestControl(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+ buffer.append(", sortKeys=");
+ buffer.append(sortKeys);
+ buffer.append(")");
+ }
+ }
+
+
+
+ /**
+ * This class implements the server-side sort response control as
+ * defined in RFC 2891 section 1.2. The ASN.1 description for the
+ * control value is: <BR>
+ * <BR>
+ *
+ * <PRE>
+ * SortResult ::= SEQUENCE {
+ * sortResult ENUMERATED {
+ * success (0), -- results are sorted
+ * operationsError (1), -- server internal failure
+ * timeLimitExceeded (3), -- timelimit reached before
+ * -- sorting was completed
+ * strongAuthRequired (8), -- refused to return sorted
+ * -- results via insecure
+ * -- protocol
+ * adminLimitExceeded (11), -- too many matching entries
+ * -- for the server to sort
+ * noSuchAttribute (16), -- unrecognized attribute
+ * -- type in sort key
+ * inappropriateMatching (18), -- unrecognized or
+ * -- inappropriate matching
+ * -- rule in sort key
+ * insufficientAccessRights (50), -- refused to return sorted
+ * -- results to this client
+ * busy (51), -- too busy to process
+ * unwillingToPerform (53), -- unable to sort
+ * other (80)
+ * },
+ * attributeType [0] AttributeDescription OPTIONAL }
+ * </PRE>
+ */
+ public static class Response extends Control
+ {
+ private SortResult sortResult;
+
+ private String attributeDescription;
+
+
+
+ public Response(boolean isCritical, SortResult sortResult)
+ {
+ this(isCritical, sortResult, null);
+ }
+
+
+
+ public Response(boolean isCritical, SortResult sortResult,
+ String attributeDescription)
+ {
+ super(OID_SERVER_SIDE_SORT_RESPONSE_CONTROL, isCritical);
+ Validator.ensureNotNull(sortResult);
+ this.sortResult = sortResult;
+ this.attributeDescription = attributeDescription;
+ }
+
+
+
+ public Response(SortResult sortResult)
+ {
+ this(false, sortResult, null);
+ }
+
+
+
+ public Response(SortResult sortResult, String attributeDescription)
+ {
+ this(false, sortResult, attributeDescription);
+ }
+
+
+
+ public String getAttributeDescription()
+ {
+ return attributeDescription;
+ }
+
+
+
+ public SortResult getSortResult()
+ {
+ return sortResult;
+ }
+
+
+
+ @Override
+ public ByteString getValue()
+ {
+ ByteStringBuilder buffer = new ByteStringBuilder();
+ ASN1Writer writer = ASN1.getWriter(buffer);
+ try
+ {
+ writer.writeStartSequence();
+ writer.writeEnumerated(sortResult.intValue());
+ if (attributeDescription != null)
+ {
+ writer.writeOctetString(TYPE_ATTRIBUTE_TYPE,
+ attributeDescription);
+ }
+ writer.writeEndSequence();
+ return buffer.toByteString();
+ }
+ catch (IOException ioe)
+ {
+ // This should never happen unless there is a bug somewhere.
+ throw new RuntimeException(ioe);
+ }
+ }
+
+
+
+ @Override
+ public boolean hasValue()
+ {
+ return true;
+ }
+
+
+
+ public Response setAttributeDescription(String attributeDescription)
+ {
+ this.attributeDescription = attributeDescription;
+ return this;
+ }
+
+
+
+ public Response setSortResult(SortResult sortResult)
+ {
+ Validator.ensureNotNull(sortResult);
+ this.sortResult = sortResult;
+ return this;
+ }
+
+
+
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("ServerSideSortResponseControl(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+ buffer.append(", sortResult=");
+ buffer.append(sortResult);
+ buffer.append(", attributeDescription=");
+ buffer.append(attributeDescription);
+ buffer.append(")");
+ }
+ }
+
+
+
+ /**
+ * ControlDecoder implentation to decode this control from a
+ * ByteString.
+ */
+ private final static class RequestDecoder implements
+ ControlDecoder<Request>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public Request decode(boolean isCritical, ByteString value, Schema schema)
+ throws DecodeException
+ {
+ if (value == null)
+ {
+ Message message = INFO_SORTREQ_CONTROL_NO_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ ASN1Reader reader = ASN1.getReader(value);
+ try
+ {
+ reader.readStartSequence();
+ if (!reader.hasNextElement())
+ {
+ Message message = INFO_SORTREQ_CONTROL_NO_SORT_KEYS.get();
+ throw DecodeException.error(message);
+ }
+
+ Request request = new Request();
+ while (reader.hasNextElement())
+ {
+ reader.readStartSequence();
+ String attrName = reader.readOctetStringAsString();
+
+ String orderingRule = null;
+ boolean reverseOrder = false;
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_ORDERING_RULE_ID))
+ {
+ orderingRule = reader.readOctetStringAsString();
+ }
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_REVERSE_ORDER))
+ {
+ reverseOrder = reader.readBoolean();
+ }
+ reader.readEndSequence();
+
+ request.addSortKey(new SortKey(attrName, orderingRule,
+ reverseOrder));
+ }
+ reader.readEndSequence();
+
+ return request;
+ }
+ catch (IOException e)
+ {
+ Message message = INFO_SORTREQ_CONTROL_CANNOT_DECODE_VALUE
+ .get(getExceptionMessage(e));
+ throw DecodeException.error(message, e);
+ }
+ }
+
+
+
+ public String getOID()
+ {
+ return OID_SERVER_SIDE_SORT_REQUEST_CONTROL;
+ }
+
+ }
+
+
+
+ /**
+ * ControlDecoder implentation to decode this control from a
+ * ByteString.
+ */
+ private final static class ResponseDecoder implements
+ ControlDecoder<Response>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public Response decode(boolean isCritical, ByteString value, Schema schema)
+ throws DecodeException
+ {
+ if (value == null)
+ {
+ Message message = INFO_SORTRES_CONTROL_NO_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ ASN1Reader reader = ASN1.getReader(value);
+ try
+ {
+ reader.readStartSequence();
+ SortResult sortResult = SortResult.valueOf(reader
+ .readEnumerated());
+
+ String attributeType = null;
+ if (reader.hasNextElement())
+ {
+ attributeType = reader.readOctetStringAsString();
+ }
+
+ return new Response(isCritical, sortResult, attributeType);
+ }
+ catch (IOException e)
+ {
+ Message message = INFO_SORTRES_CONTROL_CANNOT_DECODE_VALUE
+ .get(getExceptionMessage(e));
+ throw DecodeException.error(message, e);
+ }
+ }
+
+
+
+ public String getOID()
+ {
+ return OID_SERVER_SIDE_SORT_RESPONSE_CONTROL;
+ }
+
+ }
+
+
+
+ /**
+ * The BER type to use when encoding the orderingRule element.
+ */
+ private static final byte TYPE_ORDERING_RULE_ID = (byte) 0x80;
+
+ /**
+ * The BER type to use when encoding the reverseOrder element.
+ */
+ private static final byte TYPE_REVERSE_ORDER = (byte) 0x81;
+
+ /**
+ * The BER type to use when encoding the attribute type element.
+ */
+ private static final byte TYPE_ATTRIBUTE_TYPE = (byte) 0x80;
+
+ /**
+ * The Control Decoder that can be used to decode the request control.
+ */
+ public static final ControlDecoder<Request> REQUEST_DECODER = new RequestDecoder();
+
+ /**
+ * The Control Decoder that can be used to decode the response
+ * control.
+ */
+ public static final ControlDecoder<Response> RESPONSE_DECODER = new ResponseDecoder();
+
+
+
+ private static void decodeSortOrderString(String sortOrderString,
+ List<SortKey> sortKeys) throws DecodeException
+ {
+ StringTokenizer tokenizer = new StringTokenizer(sortOrderString,
+ ",");
+
+ while (tokenizer.hasMoreTokens())
+ {
+ String token = tokenizer.nextToken().trim();
+ boolean reverseOrder = false;
+ if (token.startsWith("-"))
+ {
+ reverseOrder = true;
+ token = token.substring(1);
+ }
+ else if (token.startsWith("+"))
+ {
+ token = token.substring(1);
+ }
+
+ int colonPos = token.indexOf(':');
+ if (colonPos < 0)
+ {
+ if (token.length() == 0)
+ {
+ Message message = INFO_SORTREQ_CONTROL_NO_ATTR_NAME
+ .get(sortOrderString);
+ throw DecodeException.error(message);
+ }
+
+ sortKeys.add(new SortKey(token, null, reverseOrder));
+ }
+ else if (colonPos == 0)
+ {
+ Message message = INFO_SORTREQ_CONTROL_NO_ATTR_NAME
+ .get(sortOrderString);
+ throw DecodeException.error(message);
+ }
+ else if (colonPos == (token.length() - 1))
+ {
+ Message message = INFO_SORTREQ_CONTROL_NO_MATCHING_RULE
+ .get(sortOrderString);
+ throw DecodeException.error(message);
+ }
+ else
+ {
+ String attrName = token.substring(0, colonPos);
+ String ruleID = token.substring(colonPos + 1);
+
+ sortKeys.add(new SortKey(attrName, ruleID, reverseOrder));
+ }
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/controls/SortKey.java b/sdk/src/org/opends/sdk/controls/SortKey.java
new file mode 100644
index 0000000..9ba4460
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/SortKey.java
@@ -0,0 +1,144 @@
+package org.opends.sdk.controls;
+
+
+
+import org.opends.sdk.AttributeDescription;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class defines a data structure that may be used as a sort key.
+ * It includes an attribute description and a boolean value that
+ * indicates whether the sort should be ascending or descending. It may
+ * also contain a specific ordering matching rule that should be used
+ * for the sorting process, although if none is provided it will use the
+ * default ordering matching rule for the attribute type.
+ */
+public class SortKey
+{
+ private String attributeDescription;
+
+ private String orderingRule;
+
+ boolean reverseOrder;
+
+
+
+ public SortKey(AttributeDescription attributeDescription)
+ {
+ this(attributeDescription.toString(), null, false);
+ }
+
+
+
+ public SortKey(AttributeDescription attributeDescription,
+ String orderingRule)
+ {
+ this(attributeDescription.toString(), orderingRule, false);
+ }
+
+
+
+ public SortKey(AttributeDescription attributeDescription,
+ String orderingRule, boolean reverseOrder)
+ {
+ this(attributeDescription.toString(), orderingRule, reverseOrder);
+ }
+
+
+
+ public SortKey(String attributeDescription)
+ {
+ this(attributeDescription, null, false);
+ }
+
+
+
+ public SortKey(String attributeDescription, String orderingRule)
+ {
+ this(attributeDescription, orderingRule, false);
+ }
+
+
+
+ public SortKey(String attributeDescription, String orderingRule,
+ boolean reverseOrder)
+ {
+ Validator.ensureNotNull(attributeDescription);
+ this.attributeDescription = attributeDescription;
+ this.orderingRule = orderingRule;
+ this.reverseOrder = reverseOrder;
+ }
+
+
+
+ public String getAttributeDescription()
+ {
+ return attributeDescription;
+ }
+
+
+
+ public String getOrderingRule()
+ {
+ return orderingRule;
+ }
+
+
+
+ public boolean isReverseOrder()
+ {
+ return reverseOrder;
+ }
+
+
+
+ public SortKey setAttributeDescription(String attributeDescription)
+ {
+ Validator.ensureNotNull(attributeDescription);
+ this.attributeDescription = attributeDescription;
+ return this;
+ }
+
+
+
+ public SortKey setOrderingRule(String orderingRule)
+ {
+ this.orderingRule = orderingRule;
+ return this;
+ }
+
+
+
+ public void setReverseOrder(boolean reverseOrder)
+ {
+ this.reverseOrder = reverseOrder;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ StringBuilder buffer = new StringBuilder();
+ toString(buffer);
+ return buffer.toString();
+ }
+
+
+
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("SortKey(attributeDescription=");
+ buffer.append(attributeDescription);
+ buffer.append(", orderingRule=");
+ buffer.append(orderingRule);
+ buffer.append(", reverseOrder=");
+ buffer.append(reverseOrder);
+ buffer.append(")");
+ }
+}
diff --git a/sdk/src/org/opends/sdk/controls/SortResult.java b/sdk/src/org/opends/sdk/controls/SortResult.java
new file mode 100644
index 0000000..ec35dca
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/SortResult.java
@@ -0,0 +1,113 @@
+package org.opends.sdk.controls;
+
+
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.opends.sdk.ResultCode;
+
+
+
+/**
+ * Created by IntelliJ IDEA. User: boli Date: Jun 30, 2009 Time: 4:33:56
+ * PM To change this template use File | Settings | File Templates.
+ */
+public class SortResult
+{
+ private static final SortResult[] ELEMENTS = new SortResult[81];
+
+ public static final SortResult SUCCESS = register(ResultCode.SUCCESS);
+
+ public static final SortResult OPERATIONS_ERROR = register(ResultCode.OPERATIONS_ERROR);
+
+ public static final SortResult TIME_LIMIT_EXCEEDED = register(ResultCode.TIME_LIMIT_EXCEEDED);
+
+ public static final SortResult STRONG_AUTH_REQUIRED = register(ResultCode.STRONG_AUTH_REQUIRED);
+
+ public static final SortResult ADMIN_LIMIT_EXCEEDED = register(ResultCode.ADMIN_LIMIT_EXCEEDED);
+
+ public static final SortResult NO_SUCH_ATTRIBUTE = register(ResultCode.NO_SUCH_ATTRIBUTE);
+
+ public static final SortResult INAPPROPRIATE_MATCHING = register(ResultCode.INAPPROPRIATE_MATCHING);
+
+ public static final SortResult INSUFFICIENT_ACCESS_RIGHTS = register(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+
+ public static final SortResult BUSY = register(ResultCode.BUSY);
+
+ public static final SortResult UNWILLING_TO_PERFORM = register(ResultCode.UNWILLING_TO_PERFORM);
+
+ public static final SortResult OTHER = register(ResultCode.OTHER);
+
+
+
+ public static SortResult register(ResultCode resultCode)
+ {
+ SortResult t = new SortResult(resultCode);
+ ELEMENTS[resultCode.intValue()] = t;
+ return t;
+ }
+
+
+
+ public static SortResult valueOf(int intValue)
+ {
+ SortResult e = ELEMENTS[intValue];
+ if (e == null)
+ {
+ e = new SortResult(ResultCode.valueOf(intValue));
+ }
+ return e;
+ }
+
+
+
+ public static List<SortResult> values()
+ {
+ return Arrays.asList(ELEMENTS);
+ }
+
+
+
+ private final ResultCode resultCode;
+
+
+
+ private SortResult(ResultCode resultCode)
+ {
+ this.resultCode = resultCode;
+ }
+
+
+
+ @Override
+ public boolean equals(Object o)
+ {
+ return (this == o)
+ || ((o instanceof SortResult) && resultCode.equals(o));
+
+ }
+
+
+
+ @Override
+ public int hashCode()
+ {
+ return resultCode.hashCode();
+ }
+
+
+
+ public int intValue()
+ {
+ return resultCode.intValue();
+ }
+
+
+
+ @Override
+ public String toString()
+ {
+ return resultCode.toString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/controls/SubtreeDeleteControl.java b/sdk/src/org/opends/sdk/controls/SubtreeDeleteControl.java
new file mode 100644
index 0000000..1777b79
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/SubtreeDeleteControl.java
@@ -0,0 +1,112 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_SUBTREE_DELETE_INVALID_CONTROL_VALUE;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the subtree delete control defined in
+ * draft-armijo-ldap-treedelete. It makes it possible for clients to
+ * delete subtrees of entries.
+ */
+public class SubtreeDeleteControl extends Control
+{
+ /**
+ * The OID for the subtree delete control.
+ */
+ public static final String OID_SUBTREE_DELETE_CONTROL = "1.2.840.113556.1.4.805";
+
+
+
+ /**
+ * ControlDecoder implementation to decode this control from a
+ * ByteString.
+ */
+ private final static class Decoder implements
+ ControlDecoder<SubtreeDeleteControl>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public SubtreeDeleteControl decode(boolean isCritical,
+ ByteString value, Schema schema) throws DecodeException
+ {
+ if (value != null)
+ {
+ Message message = ERR_SUBTREE_DELETE_INVALID_CONTROL_VALUE
+ .get();
+ throw DecodeException.error(message);
+ }
+
+ return new SubtreeDeleteControl(isCritical);
+ }
+
+
+
+ public String getOID()
+ {
+ return OID_SUBTREE_DELETE_CONTROL;
+ }
+
+ }
+
+
+
+ /**
+ * The Control Decoder that can be used to decode this control.
+ */
+ public static final ControlDecoder<SubtreeDeleteControl> DECODER = new Decoder();
+
+
+
+ /**
+ * Creates a new subtree delete control.
+ *
+ * @param isCritical
+ * Indicates whether the control should be considered
+ * critical for the operation processing.
+ */
+ public SubtreeDeleteControl(boolean isCritical)
+ {
+ super(OID_SUBTREE_DELETE_CONTROL, isCritical);
+ }
+
+
+
+ @Override
+ public ByteString getValue()
+ {
+ return null;
+ }
+
+
+
+ @Override
+ public boolean hasValue()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("SubtreeDeleteControl(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+ buffer.append(")");
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/controls/VLVControl.java b/sdk/src/org/opends/sdk/controls/VLVControl.java
new file mode 100644
index 0000000..c04f353
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/VLVControl.java
@@ -0,0 +1,654 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.INFO_VLVREQ_CONTROL_CANNOT_DECODE_VALUE;
+import static org.opends.messages.ProtocolMessages.INFO_VLVREQ_CONTROL_NO_VALUE;
+import static org.opends.messages.ProtocolMessages.INFO_VLVRES_CONTROL_CANNOT_DECODE_VALUE;
+import static org.opends.messages.ProtocolMessages.INFO_VLVRES_CONTROL_NO_VALUE;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class implements the virtual list view controls as defined in
+ * draft-ietf-ldapext-ldapv3-vlv.
+ */
+public class VLVControl
+{
+ /**
+ * The OID for the virtual list view request control.
+ */
+ public static final String OID_VLV_REQUEST_CONTROL = "2.16.840.1.113730.3.4.9";
+
+ /**
+ * The OID for the virtual list view request control.
+ */
+ public static final String OID_VLV_RESPONSE_CONTROL = "2.16.840.1.113730.3.4.10";
+
+
+
+ public static class Request extends Control
+ {
+ private int beforeCount;
+
+ private int afterCount;
+
+ private VLVTarget target;
+
+ private ByteString contextID;
+
+
+
+ /**
+ * Creates a new VLV request control that will identify the target
+ * entry by an assertion value.
+ *
+ * @param isCritical
+ * Indicates whether the control should be considered
+ * critical.
+ * @param beforeCount
+ * The number of entries before the target assertion value.
+ * @param afterCount
+ * The number of entries after the target assertion value.
+ * @param assertionValue
+ * The greaterThanOrEqual target assertion value that
+ * indicates where to start the page of results.
+ * @param contextID
+ * The context ID provided by the server in the last VLV
+ * response for the same set of criteria, or {@code null}
+ * if there was no previous VLV response or the server did
+ * not include a context ID in the last response.
+ */
+ public Request(boolean isCritical, int beforeCount, int afterCount,
+ ByteString assertionValue, ByteString contextID)
+ {
+ this(isCritical, beforeCount, afterCount, VLVTarget
+ .greaterThanOrEqual(assertionValue), contextID);
+ }
+
+
+
+ /**
+ * Creates a new VLV request control that will identify the target
+ * entry by offset.
+ *
+ * @param isCritical
+ * Indicates whether or not the control is critical.
+ * @param beforeCount
+ * The number of entries before the target offset to
+ * retrieve in the results page.
+ * @param afterCount
+ * The number of entries after the target offset to
+ * retrieve in the results page.
+ * @param offset
+ * The offset in the result set to target for the beginning
+ * of the page of results.
+ * @param contentCount
+ * The content count returned by the server in the last
+ * phase of the VLV request, or zero for a new VLV request
+ * session.
+ * @param contextID
+ * The context ID provided by the server in the last VLV
+ * response for the same set of criteria, or {@code null}
+ * if there was no previous VLV response or the server did
+ * not include a context ID in the last response.
+ */
+ public Request(boolean isCritical, int beforeCount, int afterCount,
+ int offset, int contentCount, ByteString contextID)
+ {
+ this(isCritical, beforeCount, afterCount, VLVTarget.byOffset(
+ offset, contentCount), contextID);
+ }
+
+
+
+ /**
+ * Creates a new VLV request control that will identify the target
+ * entry by an assertion value.
+ *
+ * @param beforeCount
+ * The number of entries before the target offset to
+ * retrieve in the results page.
+ * @param afterCount
+ * The number of entries after the target offset to
+ * retrieve in the results page.
+ * @param greaterThanOrEqual
+ * The greaterThanOrEqual target assertion value that
+ * indicates where to start the page of results.
+ */
+ public Request(int beforeCount, int afterCount,
+ ByteString greaterThanOrEqual)
+ {
+ this(false, beforeCount, afterCount, greaterThanOrEqual, null);
+ }
+
+
+
+ /**
+ * Creates a new VLV request control that will identify the target
+ * entry by offset.
+ *
+ * @param beforeCount
+ * The number of entries before the target offset to
+ * retrieve in the results page.
+ * @param afterCount
+ * The number of entries after the target offset to
+ * retrieve in the results page.
+ * @param offset
+ * The offset in the result set to target for the beginning
+ * of the page of results.
+ * @param contentCount
+ * The content count returned by the server in the last
+ * phase of the VLV request, or zero for a new VLV request
+ * session.
+ */
+ public Request(int beforeCount, int afterCount, int offset,
+ int contentCount)
+ {
+ this(false, beforeCount, afterCount, offset, contentCount, null);
+ }
+
+
+
+ private Request(boolean isCritical, int beforeCount,
+ int afterCount, VLVTarget target, ByteString contextID)
+ {
+ super(OID_VLV_REQUEST_CONTROL, isCritical);
+
+ this.beforeCount = beforeCount;
+ this.afterCount = afterCount;
+ this.target = target;
+ this.contextID = contextID;
+ }
+
+
+
+ /**
+ * Retrieves the number of entries after the target offset or
+ * assertion value to include in the results page.
+ *
+ * @return The number of entries after the target offset to include
+ * in the results page.
+ */
+ public int getAfterCount()
+ {
+ return afterCount;
+ }
+
+
+
+ /**
+ * Retrieves the number of entries before the target offset or
+ * assertion value to include in the results page.
+ *
+ * @return The number of entries before the target offset to include
+ * in the results page.
+ */
+ public int getBeforeCount()
+ {
+ return beforeCount;
+ }
+
+
+
+ /**
+ * Retrieves a context ID value that should be used to resume a
+ * previous VLV results session.
+ *
+ * @return A context ID value that should be used to resume a
+ * previous VLV results session, or {@code null} if none is
+ * available.
+ */
+ public ByteString getContextID()
+ {
+ return contextID;
+ }
+
+
+
+ public VLVTarget getTarget()
+ {
+ return target;
+ }
+
+
+
+ @Override
+ public ByteString getValue()
+ {
+ ByteStringBuilder buffer = new ByteStringBuilder();
+ ASN1Writer writer = ASN1.getWriter(buffer);
+ try
+ {
+ writer.writeStartSequence();
+ writer.writeInteger(beforeCount);
+ writer.writeInteger(afterCount);
+ target.encode(writer);
+ if (contextID != null)
+ {
+ writer.writeOctetString(contextID);
+ }
+ writer.writeEndSequence();
+ return buffer.toByteString();
+ }
+ catch (IOException ioe)
+ {
+ // This should never happen unless there is a bug somewhere.
+ throw new RuntimeException(ioe);
+ }
+ }
+
+
+
+ @Override
+ public boolean hasValue()
+ {
+ return true;
+ }
+
+
+
+ public Request setAfterCount(int afterCount)
+ {
+ this.afterCount = afterCount;
+ return this;
+ }
+
+
+
+ public Request setBeforeCount(int beforeCount)
+ {
+ this.beforeCount = beforeCount;
+ return this;
+ }
+
+
+
+ public Request setContextID(ByteString contextID)
+ {
+ this.contextID = contextID;
+ return this;
+ }
+
+
+
+ public Request setTarget(ByteString assertionValue)
+ {
+ Validator.ensureNotNull(assertionValue);
+ target = VLVTarget.greaterThanOrEqual(assertionValue);
+ return this;
+ }
+
+
+
+ public Request setTarget(int offset, int contentCount)
+ {
+ target = VLVTarget.byOffset(offset, contentCount);
+ return this;
+ }
+
+
+
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("VLVRequestControl(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+ buffer.append(", beforeCount=");
+ buffer.append(beforeCount);
+ buffer.append(", afterCount=");
+ buffer.append(afterCount);
+ buffer.append(", target=");
+ target.toString(buffer);
+ buffer.append(", contextID=");
+ buffer.append(contextID);
+ buffer.append(")");
+ }
+ }
+
+
+
+ /**
+ * This class implements the virtual list view response controls as
+ * defined in draft-ietf-ldapext-ldapv3-vlv. The ASN.1 description for
+ * the control value is: <BR>
+ * <BR>
+ *
+ * <PRE>
+ * VirtualListViewResponse ::= SEQUENCE {
+ * targetPosition INTEGER (0 .. maxInt),
+ * contentCount INTEGER (0 .. maxInt),
+ * virtualListViewResult ENUMERATED {
+ * success (0),
+ * operationsError (1),
+ * protocolError (3),
+ * unwillingToPerform (53),
+ * insufficientAccessRights (50),
+ * timeLimitExceeded (3),
+ * adminLimitExceeded (11),
+ * innapropriateMatching (18),
+ * sortControlMissing (60),
+ * offsetRangeError (61),
+ * other(80),
+ * ... },
+ * contextID OCTET STRING OPTIONAL }
+ * </PRE>
+ */
+ public static class Response extends Control
+ {
+ private final int targetPosition;
+
+ private final int contentCount;
+
+ private final VLVResult vlvResult;
+
+ private final ByteString contextID;
+
+
+
+ /**
+ * Creates a new VLV response control with the provided information.
+ *
+ * @param isCritical
+ * Indicates whether the control should be considered
+ * critical.
+ * @param targetPosition
+ * The position of the target entry in the result set.
+ * @param contentCount
+ * The content count estimating the total number of entries
+ * in the result set.
+ * @param vlvResult
+ * The result code for the VLV operation.
+ * @param contextID
+ * The context ID for this VLV response control.
+ */
+ public Response(boolean isCritical, int targetPosition,
+ int contentCount, VLVResult vlvResult, ByteString contextID)
+ {
+ super(OID_VLV_RESPONSE_CONTROL, isCritical);
+
+ this.targetPosition = targetPosition;
+ this.contentCount = contentCount;
+ this.vlvResult = vlvResult;
+ this.contextID = contextID;
+ }
+
+
+
+ /**
+ * Creates a new VLV response control with the provided information.
+ *
+ * @param targetPosition
+ * The position of the target entry in the result set.
+ * @param contentCount
+ * The content count estimating the total number of entries
+ * in the result set.
+ * @param vlvResult
+ * The result code for the VLV operation.
+ */
+ public Response(int targetPosition, int contentCount,
+ VLVResult vlvResult)
+ {
+ this(false, targetPosition, contentCount, vlvResult, null);
+ }
+
+
+
+ /**
+ * Retrieves the estimated total number of entries in the result
+ * set.
+ *
+ * @return The estimated total number of entries in the result set.
+ */
+ public int getContentCount()
+ {
+ return contentCount;
+ }
+
+
+
+ /**
+ * Retrieves a context ID value that should be included in the next
+ * request to retrieve a page of the same result set.
+ *
+ * @return A context ID value that should be included in the next
+ * request to retrieve a page of the same result set, or
+ * {@code null} if there is no context ID.
+ */
+ public ByteString getContextID()
+ {
+ return contextID;
+ }
+
+
+
+ /**
+ * Retrieves the position of the target entry in the result set.
+ *
+ * @return The position of the target entry in the result set.
+ */
+ public int getTargetPosition()
+ {
+ return targetPosition;
+ }
+
+
+
+ @Override
+ public ByteString getValue()
+ {
+ ByteStringBuilder buffer = new ByteStringBuilder();
+ ASN1Writer writer = ASN1.getWriter(buffer);
+ try
+ {
+ writer.writeStartSequence();
+ writer.writeInteger(targetPosition);
+ writer.writeInteger(contentCount);
+ writer.writeEnumerated(vlvResult.intValue());
+ if (contextID != null)
+ {
+ writer.writeOctetString(contextID);
+ }
+ writer.writeEndSequence();
+ return buffer.toByteString();
+ }
+ catch (IOException ioe)
+ {
+ // This should never happen unless there is a bug somewhere.
+ throw new RuntimeException(ioe);
+ }
+ }
+
+
+
+ /**
+ * Retrieves the result code for the VLV operation.
+ *
+ * @return The result code for the VLV operation.
+ */
+ public VLVResult getVLVResult()
+ {
+ return vlvResult;
+ }
+
+
+
+ @Override
+ public boolean hasValue()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Appends a string representation of this VLV request control to
+ * the provided buffer.
+ *
+ * @param buffer
+ * The buffer to which the information should be appended.
+ */
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("VLVResponseControl(oid=");
+ buffer.append(getOID());
+ buffer.append(", criticality=");
+ buffer.append(isCritical());
+ buffer.append(", targetPosition=");
+ buffer.append(targetPosition);
+ buffer.append(", contentCount=");
+ buffer.append(contentCount);
+ buffer.append(", vlvResult=");
+ buffer.append(vlvResult);
+
+ if (contextID != null)
+ {
+ buffer.append(", contextID=");
+ buffer.append(contextID);
+ }
+
+ buffer.append(")");
+ }
+ }
+
+
+
+ /**
+ * ControlDecoder implentation to decode this control from a
+ * ByteString.
+ */
+ private final static class RequestDecoder implements
+ ControlDecoder<Request>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public Request decode(boolean isCritical, ByteString value, Schema schema)
+ throws DecodeException
+ {
+ if (value == null)
+ {
+ Message message = INFO_VLVREQ_CONTROL_NO_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ ASN1Reader reader = ASN1.getReader(value);
+ try
+ {
+ reader.readStartSequence();
+
+ int beforeCount = (int) reader.readInteger();
+ int afterCount = (int) reader.readInteger();
+ VLVTarget target = VLVTarget.decode(reader);
+ ByteString contextID = null;
+ if (reader.hasNextElement())
+ {
+ contextID = reader.readOctetString();
+ }
+
+ return new Request(isCritical, beforeCount, afterCount, target,
+ contextID);
+ }
+ catch (IOException e)
+ {
+ Message message = INFO_VLVREQ_CONTROL_CANNOT_DECODE_VALUE
+ .get(getExceptionMessage(e));
+ throw DecodeException.error(message, e);
+ }
+ }
+
+
+
+ public String getOID()
+ {
+ return OID_VLV_REQUEST_CONTROL;
+ }
+ }
+
+
+
+ /**
+ * ControlDecoder implentation to decode this control from a
+ * ByteString.
+ */
+ private final static class ResponseDecoder implements
+ ControlDecoder<Response>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public Response decode(boolean isCritical, ByteString value, Schema schema)
+ throws DecodeException
+ {
+ if (value == null)
+ {
+ Message message = INFO_VLVRES_CONTROL_NO_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ ASN1Reader reader = ASN1.getReader(value);
+ try
+ {
+ reader.readStartSequence();
+
+ int targetPosition = (int) reader.readInteger();
+ int contentCount = (int) reader.readInteger();
+ VLVResult vlvResult = VLVResult
+ .valueOf(reader.readEnumerated());
+ ByteString contextID = null;
+ if (reader.hasNextElement())
+ {
+ contextID = reader.readOctetString();
+ }
+
+ return new Response(isCritical, targetPosition, contentCount,
+ vlvResult, contextID);
+ }
+ catch (IOException e)
+ {
+ Message message = INFO_VLVRES_CONTROL_CANNOT_DECODE_VALUE
+ .get(getExceptionMessage(e));
+ throw DecodeException.error(message, e);
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getOID()
+ {
+ return OID_VLV_RESPONSE_CONTROL;
+ }
+ }
+
+
+
+ /**
+ * The Control Decoder that can be used to decode the request control.
+ */
+ public static final ControlDecoder<Request> REQUEST_DECODER = new RequestDecoder();
+
+ /**
+ * The Control Decoder that can be used to decode the response
+ * control.
+ */
+ public static final ControlDecoder<Response> RESPONSE_DECODER = new ResponseDecoder();
+}
diff --git a/sdk/src/org/opends/sdk/controls/VLVResult.java b/sdk/src/org/opends/sdk/controls/VLVResult.java
new file mode 100644
index 0000000..a8eb7bb
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/VLVResult.java
@@ -0,0 +1,113 @@
+package org.opends.sdk.controls;
+
+
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.opends.sdk.ResultCode;
+
+
+
+/**
+ * Created by IntelliJ IDEA. User: boli Date: Jun 30, 2009 Time: 5:25:55
+ * PM To change this template use File | Settings | File Templates.
+ */
+public class VLVResult
+{
+ private static final VLVResult[] ELEMENTS = new VLVResult[81];
+
+ public static final VLVResult SUCCESS = register(ResultCode.SUCCESS);
+
+ public static final VLVResult OPERATIONS_ERROR = register(ResultCode.OPERATIONS_ERROR);
+
+ public static final VLVResult PROTOCOL_ERROR = register(ResultCode.PROTOCOL_ERROR);
+
+ public static final VLVResult TIME_LIMIT_EXCEEDED = register(ResultCode.TIME_LIMIT_EXCEEDED);
+
+ public static final VLVResult ADMIN_LIMIT_EXCEEDED = register(ResultCode.ADMIN_LIMIT_EXCEEDED);
+
+ public static final VLVResult INAPPROPRIATE_MATCHING = register(ResultCode.INAPPROPRIATE_MATCHING);
+
+ public static final VLVResult INSUFFICIENT_ACCESS_RIGHTS = register(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+
+ public static final VLVResult UNWILLING_TO_PERFORM = register(ResultCode.UNWILLING_TO_PERFORM);
+
+ public static final VLVResult SORT_CONTROL_MISSING = register(ResultCode.SORT_CONTROL_MISSING);
+
+ public static final VLVResult OFFSET_RANGE_ERROR = register(ResultCode.OFFSET_RANGE_ERROR);
+
+ public static final VLVResult OTHER = register(ResultCode.OTHER);
+
+
+
+ public static VLVResult register(ResultCode resultCode)
+ {
+ VLVResult t = new VLVResult(resultCode);
+ ELEMENTS[resultCode.intValue()] = t;
+ return t;
+ }
+
+
+
+ public static VLVResult valueOf(int intValue)
+ {
+ VLVResult e = ELEMENTS[intValue];
+ if (e == null)
+ {
+ e = new VLVResult(ResultCode.valueOf(intValue));
+ }
+ return e;
+ }
+
+
+
+ public static List<VLVResult> values()
+ {
+ return Arrays.asList(ELEMENTS);
+ }
+
+
+
+ private final ResultCode resultCode;
+
+
+
+ private VLVResult(ResultCode resultCode)
+ {
+ this.resultCode = resultCode;
+ }
+
+
+
+ @Override
+ public boolean equals(Object o)
+ {
+ return (this == o)
+ || ((o instanceof VLVResult) && resultCode.equals(o));
+
+ }
+
+
+
+ @Override
+ public int hashCode()
+ {
+ return resultCode.hashCode();
+ }
+
+
+
+ public int intValue()
+ {
+ return resultCode.intValue();
+ }
+
+
+
+ @Override
+ public String toString()
+ {
+ return resultCode.toString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/controls/VLVTarget.java b/sdk/src/org/opends/sdk/controls/VLVTarget.java
new file mode 100644
index 0000000..8aef176
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/VLVTarget.java
@@ -0,0 +1,191 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.INFO_VLVREQ_CONTROL_INVALID_TARGET_TYPE;
+import static org.opends.sdk.util.StaticUtils.byteToHex;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Created by IntelliJ IDEA. User: boli Date: Jun 30, 2009 Time: 5:40:28
+ * PM To change this template use File | Settings | File Templates.
+ */
+public abstract class VLVTarget
+{
+ public static final class ByOffset extends VLVTarget
+ {
+ private final int offset;
+
+ private final int contentCount;
+
+
+
+ private ByOffset(int offset, int contentCount)
+ {
+ this.offset = offset;
+ this.contentCount = contentCount;
+ }
+
+
+
+ public int getContentCount()
+ {
+ return contentCount;
+ }
+
+
+
+ public int getOffset()
+ {
+ return offset;
+ }
+
+
+
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("byOffset(offset=");
+ buffer.append(offset);
+ buffer.append(", contentCount=");
+ buffer.append(contentCount);
+ buffer.append(")");
+ }
+
+
+
+ @Override
+ void encode(ASN1Writer writer) throws IOException
+ {
+ writer.writeStartSequence(TYPE_TARGET_BYOFFSET);
+ writer.writeInteger(offset);
+ writer.writeInteger(contentCount);
+ writer.writeEndSequence();
+ }
+ }
+
+
+
+ public static final class GreaterThanOrEqual extends VLVTarget
+ {
+ private final ByteString assertionValue;
+
+
+
+ private GreaterThanOrEqual(ByteString assertionValue)
+ {
+ this.assertionValue = assertionValue;
+ }
+
+
+
+ public ByteString getAssertionValue()
+ {
+ return assertionValue;
+ }
+
+
+
+ @Override
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("greaterThanOrEqual(assertionValue=");
+ buffer.append(assertionValue);
+ buffer.append(")");
+ }
+
+
+
+ @Override
+ void encode(ASN1Writer writer) throws IOException
+ {
+ writer.writeOctetString(TYPE_TARGET_GREATERTHANOREQUAL,
+ assertionValue);
+ }
+ }
+
+
+
+ /**
+ * The BER type to use when encoding the byOffset target element.
+ */
+ private static final byte TYPE_TARGET_BYOFFSET = (byte) 0xA0;
+
+ /**
+ * The BER type to use when encoding the greaterThanOrEqual target
+ * element.
+ */
+ private static final byte TYPE_TARGET_GREATERTHANOREQUAL = (byte) 0x81;
+
+
+
+ static VLVTarget byOffset(int offset, int contentCount)
+ {
+ return new ByOffset(offset, contentCount);
+ }
+
+
+
+ static VLVTarget decode(ASN1Reader reader) throws IOException,
+ DecodeException
+ {
+ byte targetType = reader.peekType();
+ switch (targetType)
+ {
+ case TYPE_TARGET_BYOFFSET:
+ reader.readStartSequence();
+ int offset = (int) reader.readInteger();
+ int contentCount = (int) reader.readInteger();
+ reader.readEndSequence();
+ return new ByOffset(offset, contentCount);
+
+ case TYPE_TARGET_GREATERTHANOREQUAL:
+ ByteString assertionValue = reader.readOctetString();
+ return new GreaterThanOrEqual(assertionValue);
+
+ default:
+ Message message = INFO_VLVREQ_CONTROL_INVALID_TARGET_TYPE
+ .get(byteToHex(targetType));
+ throw DecodeException.error(message);
+ }
+ }
+
+
+
+ static VLVTarget greaterThanOrEqual(ByteString assertionValue)
+ {
+ Validator.ensureNotNull(assertionValue);
+ return new GreaterThanOrEqual(assertionValue);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ StringBuilder buffer = new StringBuilder();
+ toString(buffer);
+ return buffer.toString();
+ }
+
+
+
+ public abstract void toString(StringBuilder buffer);
+
+
+
+ abstract void encode(ASN1Writer writer) throws IOException;
+}
diff --git a/sdk/src/org/opends/sdk/extensions/CancelRequest.java b/sdk/src/org/opends/sdk/extensions/CancelRequest.java
new file mode 100644
index 0000000..4e85fe2
--- /dev/null
+++ b/sdk/src/org/opends/sdk/extensions/CancelRequest.java
@@ -0,0 +1,171 @@
+package org.opends.sdk.extensions;
+
+
+
+import static org.opends.messages.ExtensionMessages.ERR_EXTOP_CANCEL_CANNOT_DECODE_REQUEST_VALUE;
+import static org.opends.messages.ExtensionMessages.ERR_EXTOP_CANCEL_NO_REQUEST_VALUE;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.requests.AbstractExtendedRequest;
+import org.opends.sdk.responses.Responses;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+
+
+
+/**
+ * Created by IntelliJ IDEA. User: boli Date: Jun 22, 2009 Time: 4:44:51
+ * PM To change this template use File | Settings | File Templates.
+ */
+public final class CancelRequest extends
+ AbstractExtendedRequest<CancelRequest, Result>
+{
+ /**
+ * The request OID for the cancel extended operation.
+ */
+ static final String OID_CANCEL_REQUEST = "1.3.6.1.1.8";
+
+ private int cancelID;
+
+
+
+ public CancelRequest(int cancelID)
+ {
+ this.cancelID = cancelID;
+ }
+
+
+
+ public int getCancelID()
+ {
+ return cancelID;
+ }
+
+
+
+ public Operation getExtendedOperation()
+ {
+ return OPERATION;
+ }
+
+
+
+ public ByteString getRequestValue()
+ {
+ ByteStringBuilder buffer = new ByteStringBuilder(6);
+ ASN1Writer writer = ASN1.getWriter(buffer);
+
+ try
+ {
+ writer.writeStartSequence();
+ writer.writeInteger(cancelID);
+ writer.writeEndSequence();
+ }
+ catch (IOException ioe)
+ {
+ // This should never happen unless there is a bug somewhere.
+ throw new RuntimeException(ioe);
+ }
+
+ return buffer.toByteString();
+ }
+
+
+
+ public CancelRequest setCancelID(int cancelID)
+ {
+ this.cancelID = cancelID;
+ return this;
+ }
+
+
+
+ public StringBuilder toString(StringBuilder builder)
+ {
+ builder.append("CancelExtendedRequest(requestName=");
+ builder.append(getRequestName());
+ builder.append(", cancelID=");
+ builder.append(cancelID);
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder;
+ }
+
+
+
+ private static final class Operation implements
+ ExtendedOperation<CancelRequest, Result>
+ {
+
+ public CancelRequest decodeRequest(String requestName,
+ ByteString requestValue) throws DecodeException
+ {
+ if ((requestValue == null) || (requestValue.length() <= 0))
+ {
+ throw DecodeException
+ .error(ERR_EXTOP_CANCEL_NO_REQUEST_VALUE.get());
+ }
+
+ try
+ {
+ ASN1Reader reader = ASN1.getReader(requestValue);
+ reader.readStartSequence();
+ int idToCancel = (int) reader.readInteger();
+ reader.readEndSequence();
+ return new CancelRequest(idToCancel);
+ }
+ catch (IOException e)
+ {
+ Message message = ERR_EXTOP_CANCEL_CANNOT_DECODE_REQUEST_VALUE
+ .get(getExceptionMessage(e));
+ throw DecodeException.error(message, e);
+ }
+ }
+
+
+
+ public Result decodeResponse(ResultCode resultCode,
+ String matchedDN, String diagnosticMessage)
+ {
+ return Responses.newResult(resultCode).setMatchedDN(matchedDN)
+ .setDiagnosticMessage(diagnosticMessage);
+ }
+
+
+
+ public Result decodeResponse(ResultCode resultCode,
+ String matchedDN, String diagnosticMessage,
+ String responseName, ByteString responseValue)
+ throws DecodeException
+ {
+ // TODO: Should we check to make sure OID and value is null?
+ return Responses.newResult(resultCode).setMatchedDN(matchedDN)
+ .setDiagnosticMessage(diagnosticMessage);
+ }
+ }
+
+
+
+ // Singleton instance.
+ private static final Operation OPERATION = new Operation();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getRequestName()
+ {
+ return OID_CANCEL_REQUEST;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/extensions/ExtendedOperation.java b/sdk/src/org/opends/sdk/extensions/ExtendedOperation.java
new file mode 100644
index 0000000..28d8ee4
--- /dev/null
+++ b/sdk/src/org/opends/sdk/extensions/ExtendedOperation.java
@@ -0,0 +1,34 @@
+package org.opends.sdk.extensions;
+
+
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.requests.ExtendedRequest;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * Created by IntelliJ IDEA. User: digitalperk Date: Jun 19, 2009 Time:
+ * 8:39:52 PM To change this template use File | Settings | File
+ * Templates.
+ */
+public interface ExtendedOperation<R extends ExtendedRequest<S>, S extends Result>
+{
+ R decodeRequest(String requestName, ByteString requestValue)
+ throws DecodeException;
+
+
+
+ S decodeResponse(ResultCode resultCode, String matchedDN,
+ String diagnosticMessage);
+
+
+
+ S decodeResponse(ResultCode resultCode, String matchedDN,
+ String diagnosticMessage, String responseName,
+ ByteString responseValue) throws DecodeException;
+
+}
diff --git a/sdk/src/org/opends/sdk/extensions/GetConnectionIDRequest.java b/sdk/src/org/opends/sdk/extensions/GetConnectionIDRequest.java
new file mode 100644
index 0000000..308b62a
--- /dev/null
+++ b/sdk/src/org/opends/sdk/extensions/GetConnectionIDRequest.java
@@ -0,0 +1,140 @@
+package org.opends.sdk.extensions;
+
+
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.requests.AbstractExtendedRequest;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * Created by IntelliJ IDEA. User: boli Date: Jun 23, 2009 Time:
+ * 11:43:53 AM To change this template use File | Settings | File
+ * Templates.
+ */
+public final class GetConnectionIDRequest
+ extends
+ AbstractExtendedRequest<GetConnectionIDRequest, GetConnectionIDResult>
+{
+ /**
+ * The OID for the extended operation that can be used to get the
+ * client connection ID. It will be both the request and response OID.
+ */
+ static final String OID_GET_CONNECTION_ID_EXTOP = "1.3.6.1.4.1.26027.1.6.2";
+
+
+
+ public GetConnectionIDRequest()
+ {
+ }
+
+
+
+ public Operation getExtendedOperation()
+ {
+ return OPERATION;
+ }
+
+
+
+ public ByteString getRequestValue()
+ {
+ return null;
+ }
+
+
+
+ public StringBuilder toString(StringBuilder builder)
+ {
+ builder.append("GetConnectionIDExtendedRequest(requestName=");
+ builder.append(getRequestName());
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder;
+ }
+
+
+
+ private static final class Operation implements
+ ExtendedOperation<GetConnectionIDRequest, GetConnectionIDResult>
+ {
+
+ public GetConnectionIDRequest decodeRequest(String requestName,
+ ByteString requestValue) throws DecodeException
+ {
+ return new GetConnectionIDRequest();
+ }
+
+
+
+ public GetConnectionIDResult decodeResponse(ResultCode resultCode,
+ String matchedDN, String diagnosticMessage,
+ String responseName, ByteString responseValue)
+ throws DecodeException
+ {
+ if (!resultCode.isExceptional()
+ && ((responseValue == null) || (responseValue.length() <= 0)))
+ {
+ throw DecodeException.error(Message
+ .raw("Empty response value"));
+ }
+
+ try
+ {
+ ASN1Reader reader = ASN1.getReader(responseValue);
+ int connectionID = (int) reader.readInteger();
+ return new GetConnectionIDResult(resultCode, connectionID)
+ .setMatchedDN(matchedDN).setDiagnosticMessage(
+ diagnosticMessage);
+ }
+ catch (IOException e)
+ {
+ throw DecodeException.error(Message
+ .raw("Error decoding response value"), e);
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public GetConnectionIDResult decodeResponse(ResultCode resultCode,
+ String matchedDN, String diagnosticMessage)
+ {
+ if (!resultCode.isExceptional())
+ {
+ // A successful response must contain a response name and
+ // value.
+ throw new IllegalArgumentException(
+ "No response name and value for result code "
+ + resultCode.intValue());
+ }
+ return new GetConnectionIDResult(resultCode, -1).setMatchedDN(
+ matchedDN).setDiagnosticMessage(diagnosticMessage);
+ }
+ }
+
+
+
+ // Singleton instance.
+ private static final Operation OPERATION = new Operation();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getRequestName()
+ {
+ return OID_GET_CONNECTION_ID_EXTOP;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/extensions/GetConnectionIDResult.java b/sdk/src/org/opends/sdk/extensions/GetConnectionIDResult.java
new file mode 100644
index 0000000..7fb1be0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/extensions/GetConnectionIDResult.java
@@ -0,0 +1,122 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.extensions;
+
+
+
+import java.io.IOException;
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.responses.AbstractExtendedResult;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+
+
+
+public class GetConnectionIDResult extends
+ AbstractExtendedResult<GetConnectionIDResult>
+{
+ private int connectionID;
+
+
+
+ public GetConnectionIDResult(ResultCode resultCode, int connectionID)
+ {
+ super(resultCode);
+ this.connectionID = connectionID;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getResponseName()
+ {
+ return GetConnectionIDRequest.OID_GET_CONNECTION_ID_EXTOP;
+ }
+
+
+
+ public int getConnectionID()
+ {
+ return connectionID;
+ }
+
+
+
+ public ByteString getResponseValue()
+ {
+ ByteStringBuilder buffer = new ByteStringBuilder(6);
+ ASN1Writer writer = ASN1.getWriter(buffer);
+
+ try
+ {
+ writer.writeInteger(connectionID);
+ }
+ catch (IOException ioe)
+ {
+ // This should never happen unless there is a bug somewhere.
+ throw new RuntimeException(ioe);
+ }
+
+ return buffer.toByteString();
+ }
+
+
+
+ public GetConnectionIDResult setConnectionID(int connectionID)
+ {
+ this.connectionID = connectionID;
+ return this;
+ }
+
+
+
+ public StringBuilder toString(StringBuilder builder)
+ {
+ builder.append("GetConnectionIDExtendedResponse(resultCode=");
+ builder.append(getResultCode());
+ builder.append(", matchedDN=");
+ builder.append(getMatchedDN());
+ builder.append(", diagnosticMessage=");
+ builder.append(getDiagnosticMessage());
+ builder.append(", referrals=");
+ builder.append(getReferralURIs());
+ builder.append(", responseName=");
+ builder.append(getResponseName());
+ builder.append(", connectionID=");
+ builder.append(connectionID);
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/extensions/GetSymmetricKeyRequest.java b/sdk/src/org/opends/sdk/extensions/GetSymmetricKeyRequest.java
new file mode 100644
index 0000000..8d0c3e9
--- /dev/null
+++ b/sdk/src/org/opends/sdk/extensions/GetSymmetricKeyRequest.java
@@ -0,0 +1,231 @@
+package org.opends.sdk.extensions;
+
+
+
+import static org.opends.messages.ExtensionMessages.ERR_GET_SYMMETRIC_KEY_ASN1_DECODE_EXCEPTION;
+import static org.opends.messages.ExtensionMessages.ERR_GET_SYMMETRIC_KEY_NO_VALUE;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.requests.AbstractExtendedRequest;
+import org.opends.sdk.responses.Responses;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * Created by IntelliJ IDEA. User: boli Date: Jun 23, 2009 Time:
+ * 12:10:59 PM To change this template use File | Settings | File
+ * Templates.
+ */
+public final class GetSymmetricKeyRequest extends
+ AbstractExtendedRequest<GetSymmetricKeyRequest, Result>
+{
+ /**
+ * The request OID for the get symmetric key extended operation.
+ */
+ static final String OID_GET_SYMMETRIC_KEY_EXTENDED_OP = "1.3.6.1.4.1.26027.1.6.3";
+
+ private String requestSymmetricKey = null;
+
+ private String instanceKeyID = null;
+
+
+
+ public GetSymmetricKeyRequest()
+ {
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getRequestName()
+ {
+ return OID_GET_SYMMETRIC_KEY_EXTENDED_OP;
+ }
+
+
+
+ public Operation getExtendedOperation()
+ {
+ return OPERATION;
+ }
+
+
+
+ public String getInstanceKeyID()
+ {
+ return instanceKeyID;
+ }
+
+
+
+ public String getRequestSymmetricKey()
+ {
+ return requestSymmetricKey;
+ }
+
+
+
+ public ByteString getRequestValue()
+ {
+ ByteStringBuilder buffer = new ByteStringBuilder();
+ ASN1Writer writer = ASN1.getWriter(buffer);
+
+ try
+ {
+ writer.writeStartSequence();
+ if (requestSymmetricKey != null)
+ {
+ writer.writeOctetString(TYPE_SYMMETRIC_KEY_ELEMENT,
+ requestSymmetricKey);
+ }
+ if (instanceKeyID != null)
+ {
+ writer.writeOctetString(TYPE_INSTANCE_KEY_ID_ELEMENT,
+ instanceKeyID);
+ }
+ writer.writeEndSequence();
+ }
+ catch (IOException ioe)
+ {
+ // This should never happen unless there is a bug somewhere.
+ throw new RuntimeException(ioe);
+ }
+
+ return buffer.toByteString();
+ }
+
+
+
+ public GetSymmetricKeyRequest setInstanceKeyID(String instanceKeyID)
+ {
+ this.instanceKeyID = instanceKeyID;
+ return this;
+ }
+
+
+
+ public GetSymmetricKeyRequest setRequestSymmetricKey(
+ String requestSymmetricKey)
+ {
+ this.requestSymmetricKey = requestSymmetricKey;
+ return this;
+ }
+
+
+
+ public StringBuilder toString(StringBuilder builder)
+ {
+ builder.append("GetSymmetricKeyExtendedRequest(requestName=");
+ builder.append(getRequestName());
+ builder.append(", requestSymmetricKey=");
+ builder.append(requestSymmetricKey);
+ builder.append(", instanceKeyID=");
+ builder.append(instanceKeyID);
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder;
+ }
+
+
+
+ /**
+ * The BER type value for the symmetric key element of the operation
+ * value.
+ */
+ private static final byte TYPE_SYMMETRIC_KEY_ELEMENT = (byte) 0x80;
+
+ /**
+ * The BER type value for the instance key ID element of the operation
+ * value.
+ */
+ private static final byte TYPE_INSTANCE_KEY_ID_ELEMENT = (byte) 0x81;
+
+
+
+ private static final class Operation implements
+ ExtendedOperation<GetSymmetricKeyRequest, Result>
+ {
+
+ public GetSymmetricKeyRequest decodeRequest(String requestName,
+ ByteString requestValue) throws DecodeException
+ {
+ if (requestValue == null)
+ {
+ // The request must always have a value.
+ Message message = ERR_GET_SYMMETRIC_KEY_NO_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ String requestSymmetricKey = null;
+ String instanceKeyID = null;
+
+ try
+ {
+ ASN1Reader reader = ASN1.getReader(requestValue);
+ reader.readStartSequence();
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_SYMMETRIC_KEY_ELEMENT))
+ {
+ requestSymmetricKey = reader.readOctetStringAsString();
+ }
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_INSTANCE_KEY_ID_ELEMENT))
+ {
+ instanceKeyID = reader.readOctetStringAsString();
+ }
+ reader.readEndSequence();
+ return new GetSymmetricKeyRequest().setRequestSymmetricKey(
+ requestSymmetricKey).setInstanceKeyID(instanceKeyID);
+ }
+ catch (IOException ae)
+ {
+ StaticUtils.DEBUG_LOG.throwing(
+ "GetSymmetricKeyRequest.Operation", "decodeRequest", ae);
+
+ Message message = ERR_GET_SYMMETRIC_KEY_ASN1_DECODE_EXCEPTION
+ .get(ae.getMessage());
+ throw DecodeException.error(message, ae);
+ }
+ }
+
+
+
+ public Result decodeResponse(ResultCode resultCode,
+ String matchedDN, String diagnosticMessage)
+ {
+ return Responses.newResult(resultCode).setMatchedDN(matchedDN)
+ .setDiagnosticMessage(diagnosticMessage);
+ }
+
+
+
+ public Result decodeResponse(ResultCode resultCode,
+ String matchedDN, String diagnosticMessage,
+ String responseName, ByteString responseValue)
+ throws DecodeException
+ {
+ // TODO: Should we check to make sure OID and value is null?
+ return Responses.newResult(resultCode).setMatchedDN(matchedDN)
+ .setDiagnosticMessage(diagnosticMessage);
+ }
+ }
+
+
+
+ // Singleton instance.
+ private static final Operation OPERATION = new Operation();
+}
diff --git a/sdk/src/org/opends/sdk/extensions/PasswordModifyRequest.java b/sdk/src/org/opends/sdk/extensions/PasswordModifyRequest.java
new file mode 100644
index 0000000..fc4dff1
--- /dev/null
+++ b/sdk/src/org/opends/sdk/extensions/PasswordModifyRequest.java
@@ -0,0 +1,278 @@
+package org.opends.sdk.extensions;
+
+
+
+import static org.opends.messages.ExtensionMessages.ERR_EXTOP_PASSMOD_CANNOT_DECODE_REQUEST;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.requests.AbstractExtendedRequest;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+
+
+
+/**
+ * This class implements the password modify extended operation response
+ * defined in RFC 3062. It includes support for requiring the user's
+ * current password as well as for generating a new password if none was
+ * provided.
+ */
+public final class PasswordModifyRequest extends
+ AbstractExtendedRequest<PasswordModifyRequest, PasswordModifyResult>
+{
+ /**
+ * The request OID for the password modify extended operation.
+ */
+ public static final String OID_PASSWORD_MODIFY_REQUEST = "1.3.6.1.4.1.4203.1.11.1";
+
+ /**
+ * The ASN.1 element type that will be used to encode the userIdentity
+ * component in a password modify extended request.
+ */
+ static final byte TYPE_PASSWORD_MODIFY_USER_ID = (byte) 0x80;
+
+ /**
+ * The ASN.1 element type that will be used to encode the oldPasswd
+ * component in a password modify extended request.
+ */
+ static final byte TYPE_PASSWORD_MODIFY_OLD_PASSWORD = (byte) 0x81;
+
+ /**
+ * The ASN.1 element type that will be used to encode the newPasswd
+ * component in a password modify extended request.
+ */
+ static final byte TYPE_PASSWORD_MODIFY_NEW_PASSWORD = (byte) 0x82;
+
+ /**
+ * The ASN.1 element type that will be used to encode the genPasswd
+ * component in a password modify extended response.
+ */
+ static final byte TYPE_PASSWORD_MODIFY_GENERATED_PASSWORD = (byte) 0x80;
+
+ private String userIdentity;
+
+ private ByteString oldPassword;
+
+ private ByteString newPassword;
+
+
+
+ public PasswordModifyRequest()
+ {
+
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getRequestName()
+ {
+ return OID_PASSWORD_MODIFY_REQUEST;
+ }
+
+
+ public ExtendedOperation<PasswordModifyRequest, PasswordModifyResult>
+ getExtendedOperation()
+ {
+ return OPERATION;
+ }
+
+ public ByteString getNewPassword()
+ {
+ return newPassword;
+ }
+
+
+
+ public ByteString getOldPassword()
+ {
+ return oldPassword;
+ }
+
+
+
+ public ByteString getRequestValue()
+ {
+ ByteStringBuilder buffer = new ByteStringBuilder();
+ ASN1Writer writer = ASN1.getWriter(buffer);
+
+ try
+ {
+ writer.writeStartSequence();
+ if (userIdentity != null)
+ {
+ writer.writeOctetString(TYPE_PASSWORD_MODIFY_USER_ID,
+ userIdentity);
+ }
+ if (oldPassword != null)
+ {
+ writer.writeOctetString(TYPE_PASSWORD_MODIFY_OLD_PASSWORD,
+ oldPassword);
+ }
+ if (newPassword != null)
+ {
+ writer.writeOctetString(TYPE_PASSWORD_MODIFY_NEW_PASSWORD,
+ newPassword);
+ }
+ writer.writeEndSequence();
+ }
+ catch (IOException ioe)
+ {
+ // This should never happen unless there is a bug somewhere.
+ throw new RuntimeException(ioe);
+ }
+
+ return buffer.toByteString();
+ }
+
+
+
+ public String getUserIdentity()
+ {
+ return userIdentity;
+ }
+
+
+
+ public PasswordModifyRequest setNewPassword(ByteString newPassword)
+ {
+ this.newPassword = newPassword;
+ return this;
+ }
+
+
+
+ public PasswordModifyRequest setOldPassword(ByteString oldPassword)
+ {
+ this.oldPassword = oldPassword;
+ return this;
+ }
+
+
+
+ public PasswordModifyRequest setUserIdentity(String userIdentity)
+ {
+ this.userIdentity = userIdentity;
+ return this;
+ }
+
+
+
+ public StringBuilder toString(StringBuilder builder)
+ {
+ builder.append("PasswordModifyExtendedRequest(requestName=");
+ builder.append(getRequestName());
+ builder.append(", userIdentity=");
+ builder.append(userIdentity);
+ builder.append(", oldPassword=");
+ builder.append(oldPassword);
+ builder.append(", newPassword=");
+ builder.append(newPassword);
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder;
+ }
+
+
+
+ private static final class Operation implements
+ ExtendedOperation<PasswordModifyRequest, PasswordModifyResult>
+ {
+
+ public PasswordModifyRequest decodeRequest(String requestName,
+ ByteString requestValue) throws DecodeException
+ {
+ PasswordModifyRequest request = new PasswordModifyRequest();
+ if (requestValue != null)
+ {
+ try
+ {
+ ASN1Reader reader = ASN1.getReader(requestValue);
+ reader.readStartSequence();
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_PASSWORD_MODIFY_USER_ID))
+ {
+ request.setUserIdentity(reader.readOctetStringAsString());
+ }
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_PASSWORD_MODIFY_OLD_PASSWORD))
+ {
+ request.setOldPassword(reader.readOctetString());
+ }
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_PASSWORD_MODIFY_NEW_PASSWORD))
+ {
+ request.setNewPassword(reader.readOctetString());
+ }
+ reader.readEndSequence();
+ }
+ catch (IOException e)
+ {
+ Message message = ERR_EXTOP_PASSMOD_CANNOT_DECODE_REQUEST
+ .get(getExceptionMessage(e));
+ throw DecodeException.error(message, e);
+ }
+ }
+ return request;
+ }
+
+
+
+ public PasswordModifyResult decodeResponse(ResultCode resultCode,
+ String matchedDN, String diagnosticMessage,
+ String responseName, ByteString responseValue)
+ throws DecodeException
+ {
+ // TODO: Should we check to make sure OID is null?
+ PasswordModifyResult result = new PasswordModifyResult(resultCode)
+ .setMatchedDN(matchedDN).setDiagnosticMessage(
+ diagnosticMessage);
+ if (resultCode == ResultCode.SUCCESS && responseValue != null)
+ {
+ try
+ {
+ ASN1Reader asn1Reader = ASN1.getReader(responseValue);
+ asn1Reader.readStartSequence();
+ if (asn1Reader.peekType() == TYPE_PASSWORD_MODIFY_GENERATED_PASSWORD)
+ {
+ result.setGenPassword(asn1Reader.readOctetString());
+ }
+ asn1Reader.readEndSequence();
+ }
+ catch (IOException e)
+ {
+ Message message = ERR_EXTOP_PASSMOD_CANNOT_DECODE_REQUEST
+ .get(getExceptionMessage(e));
+ throw DecodeException.error(message, e);
+ }
+ }
+ return result;
+ }
+
+
+
+ public PasswordModifyResult decodeResponse(ResultCode resultCode,
+ String matchedDN, String diagnosticMessage)
+ {
+ return new PasswordModifyResult(resultCode).setMatchedDN(
+ matchedDN).setDiagnosticMessage(diagnosticMessage);
+ }
+ }
+
+
+
+ // Singleton instance.
+ private static final Operation OPERATION = new Operation();
+}
diff --git a/sdk/src/org/opends/sdk/extensions/PasswordModifyResult.java b/sdk/src/org/opends/sdk/extensions/PasswordModifyResult.java
new file mode 100644
index 0000000..b73354b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/extensions/PasswordModifyResult.java
@@ -0,0 +1,119 @@
+package org.opends.sdk.extensions;
+
+
+
+import java.io.IOException;
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.responses.AbstractExtendedResult;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+
+
+
+/**
+ * This class implements the password modify extended operation response
+ * defined in RFC 3062. It includes support for requiring the user's
+ * current password as well as for generating a new password if none was
+ * provided.
+ */
+public class PasswordModifyResult extends
+ AbstractExtendedResult<PasswordModifyResult>
+{
+ private ByteString genPassword;
+
+
+
+ public PasswordModifyResult(ResultCode resultCode)
+ {
+ super(resultCode);
+ }
+
+
+
+ /**
+ * Get the newly generated password.
+ *
+ * @return The password generated by the server or <code>null</code>
+ * if it is not available.
+ */
+ public ByteString getGenPassword()
+ {
+ return genPassword;
+ }
+
+
+
+ public PasswordModifyResult setGenPassword(ByteString genPassword)
+ {
+ this.genPassword = genPassword;
+ return this;
+ }
+
+
+
+ public ByteString getResponseValue()
+ {
+ if (genPassword != null)
+ {
+ ByteStringBuilder buffer = new ByteStringBuilder();
+ ASN1Writer writer = ASN1.getWriter(buffer);
+
+ try
+ {
+ writer.writeStartSequence();
+ writer
+ .writeOctetString(
+ PasswordModifyRequest.TYPE_PASSWORD_MODIFY_GENERATED_PASSWORD,
+ genPassword);
+ writer.writeEndSequence();
+ }
+ catch (IOException ioe)
+ {
+ // This should never happen unless there is a bug somewhere.
+ throw new RuntimeException(ioe);
+ }
+
+ return buffer.toByteString();
+ }
+ return null;
+ }
+
+
+
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append("PasswordModifyExtendedResponse(resultCode=");
+ builder.append(getResultCode());
+ builder.append(", matchedDN=");
+ builder.append(getMatchedDN());
+ builder.append(", diagnosticMessage=");
+ builder.append(getDiagnosticMessage());
+ builder.append(", referrals=");
+ builder.append(getReferralURIs());
+ if (genPassword != null)
+ {
+ builder.append(", genPassword=");
+ builder.append(genPassword);
+ }
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getResponseName()
+ {
+ // No response name defined.
+ return null;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/extensions/PasswordPolicyStateExtendedOperation.java b/sdk/src/org/opends/sdk/extensions/PasswordPolicyStateExtendedOperation.java
new file mode 100644
index 0000000..aefdd21
--- /dev/null
+++ b/sdk/src/org/opends/sdk/extensions/PasswordPolicyStateExtendedOperation.java
@@ -0,0 +1,1077 @@
+package org.opends.sdk.extensions;
+
+
+
+import static org.opends.messages.ExtensionMessages.ERR_PWPSTATE_EXTOP_DECODE_FAILURE;
+import static org.opends.messages.ExtensionMessages.ERR_PWPSTATE_EXTOP_NO_REQUEST_VALUE;
+import static org.opends.messages.ExtensionMessages.ERR_PWPSTATE_EXTOP_UNKNOWN_OP_TYPE;
+import static org.opends.sdk.util.StaticUtils.formatAsGeneralizedTime;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DN;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.requests.AbstractExtendedRequest;
+import org.opends.sdk.responses.AbstractExtendedResult;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class implements an LDAP extended operation that can be used to
+ * query and update elements of the Directory Server password policy
+ * state for a given user. The ASN.1 definition for the value of the
+ * extended request is: <BR>
+ *
+ * <PRE>
+ * PasswordPolicyStateValue ::= SEQUENCE {
+ * targetUser LDAPDN
+ * operations SEQUENCE OF PasswordPolicyStateOperation OPTIONAL }
+ * PasswordPolicyStateOperation ::= SEQUENCE {
+ * opType ENUMERATED {
+ * getPasswordPolicyDN (0),
+ * getAccountDisabledState (1),
+ * setAccountDisabledState (2),
+ * clearAccountDisabledState (3),
+ * getAccountExpirationTime (4),
+ * setAccountExpirationTime (5),
+ * clearAccountExpirationTime (6),
+ * getSecondsUntilAccountExpiration (7),
+ * getPasswordChangedTime (8),
+ * setPasswordChangedTime (9),
+ * clearPasswordChangedTime (10),
+ * getPasswordExpirationWarnedTime (11),
+ * setPasswordExpirationWarnedTime (12),
+ * clearPasswordExpirationWarnedTime (13),
+ * getSecondsUntilPasswordExpiration (14),
+ * getSecondsUntilPasswordExpirationWarning (15),
+ * getAuthenticationFailureTimes (16),
+ * addAuthenticationFailureTime (17),
+ * setAuthenticationFailureTimes (18),
+ * clearAuthenticationFailureTimes (19),
+ * getSecondsUntilAuthenticationFailureUnlock (20),
+ * getRemainingAuthenticationFailureCount (21),
+ * getLastLoginTime (22),
+ * setLastLoginTime (23),
+ * clearLastLoginTime (24),
+ * getSecondsUntilIdleLockout (25),
+ * getPasswordResetState (26),
+ * setPasswordResetState (27),
+ * clearPasswordResetState (28),
+ * getSecondsUntilPasswordResetLockout (29),
+ * getGraceLoginUseTimes (30),
+ * addGraceLoginUseTime (31),
+ * setGraceLoginUseTimes (32),
+ * clearGraceLoginUseTimes (33),
+ * getRemainingGraceLoginCount (34),
+ * getPasswordChangedByRequiredTime (35),
+ * setPasswordChangedByRequiredTime (36),
+ * clearPasswordChangedByRequiredTime (37),
+ * getSecondsUntilRequiredChangeTime (38),
+ * getPasswordHistory (39),
+ * clearPasswordHistory (40),
+ * ... },
+ * opValues SEQUENCE OF OCTET STRING OPTIONAL }
+ * </PRE>
+ *
+ * <BR>
+ * Both the request and response values use the same encoded form, and
+ * they both use the same OID of "1.3.6.1.4.1.26027.1.6.1". The response
+ * value will only include get* elements. If the request did not include
+ * any operations, then the response will include all get* elements;
+ * otherwise, the response will only include the get* elements that
+ * correspond to the state fields referenced in the request (regardless
+ * of whether that operation was included in a get*, set*, add*,
+ * remove*, or clear* operation).
+ */
+public final class PasswordPolicyStateExtendedOperation
+{
+ /**
+ * The OID for the password policy state extended operation (both the
+ * request and response types).
+ */
+ static final String OID_PASSWORD_POLICY_STATE_EXTOP = "1.3.6.1.4.1.26027.1.6.1";
+
+
+
+ public interface Operation
+ {
+ public OperationType getOperationType();
+
+
+
+ public Iterable<ByteString> getValues();
+ }
+
+
+
+ public static enum OperationType implements Operation
+ {
+ GET_PASSWORD_POLICY_DN(PASSWORD_POLICY_DN_NAME),
+
+ GET_ACCOUNT_DISABLED_STATE(ACCOUNT_DISABLED_STATE_NAME), SET_ACCOUNT_DISABLED_STATE(
+ ACCOUNT_DISABLED_STATE_NAME), CLEAR_ACCOUNT_DISABLED_STATE(
+ ACCOUNT_DISABLED_STATE_NAME),
+
+ GET_ACCOUNT_EXPIRATION_TIME(ACCOUNT_EXPIRATION_TIME_NAME), SET_ACCOUNT_EXPIRATION_TIME(
+ ACCOUNT_EXPIRATION_TIME_NAME), CLEAR_ACCOUNT_EXPIRATION_TIME(
+ ACCOUNT_EXPIRATION_TIME_NAME),
+
+ GET_SECONDS_UNTIL_ACCOUNT_EXPIRATION(
+ SECONDS_UNTIL_ACCOUNT_EXPIRATION_NAME),
+
+ GET_PASSWORD_CHANGED_TIME(PASSWORD_CHANGED_TIME_NAME), SET_PASSWORD_CHANGED_TIME(
+ PASSWORD_CHANGED_TIME_NAME), CLEAR_PASSWORD_CHANGED_TIME(
+ PASSWORD_CHANGED_TIME_NAME),
+
+ GET_PASSWORD_EXPIRATION_WARNED_TIME(
+ PASSWORD_EXPIRATION_WARNED_TIME_NAME), SET_PASSWORD_EXPIRATION_WARNED_TIME(
+ PASSWORD_EXPIRATION_WARNED_TIME_NAME), CLEAR_PASSWORD_EXPIRATION_WARNED_TIME(
+ PASSWORD_EXPIRATION_WARNED_TIME_NAME),
+
+ GET_SECONDS_UNTIL_PASSWORD_EXPIRATION(
+ SECONDS_UNTIL_PASSWORD_EXPIRATION_NAME),
+
+ GET_SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING(
+ SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING_NAME),
+
+ GET_AUTHENTICATION_FAILURE_TIMES(AUTHENTICATION_FAILURE_TIMES_NAME), ADD_AUTHENTICATION_FAILURE_TIMES(
+ AUTHENTICATION_FAILURE_TIMES_NAME), SET_AUTHENTICATION_FAILURE_TIMES(
+ AUTHENTICATION_FAILURE_TIMES_NAME), CLEAR_AUTHENTICATION_FAILURE_TIMES(
+ AUTHENTICATION_FAILURE_TIMES_NAME),
+
+ GET_SECONDS_UNTIL_AUTHENTICATION_FAILURE_UNLOCK(
+ SECONDS_UNTIL_AUTHENTICATION_FAILURE_UNLOCK_NAME),
+
+ GET_REMAINING_AUTHENTICATION_FAILURE_COUNT(
+ REMAINING_AUTHENTICATION_FAILURE_COUNT_NAME),
+
+ GET_LAST_LOGIN_TIME(LAST_LOGIN_TIME_NAME), SET_LAST_LOGIN_TIME(
+ LAST_LOGIN_TIME_NAME), CLEAR_LAST_LOGIN_TIME(
+ LAST_LOGIN_TIME_NAME),
+
+ GET_SECONDS_UNTIL_IDLE_LOCKOUT(SECONDS_UNTIL_IDLE_LOCKOUT_NAME),
+
+ GET_PASSWORD_RESET_STATE(PASSWORD_RESET_STATE_NAME), SET_PASSWORD_RESET_STATE(
+ PASSWORD_RESET_STATE_NAME), CLEAR_PASSWORD_RESET_STATE(
+ PASSWORD_RESET_STATE_NAME),
+
+ GET_SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT(
+ SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT_NAME),
+
+ GET_GRACE_LOGIN_USE_TIMES(GRACE_LOGIN_USE_TIMES_NAME), ADD_GRACE_LOGIN_USE_TIME(
+ GRACE_LOGIN_USE_TIMES_NAME), SET_GRACE_LOGIN_USE_TIMES(
+ GRACE_LOGIN_USE_TIMES_NAME), CLEAR_GRACE_LOGIN_USE_TIMES(
+ GRACE_LOGIN_USE_TIMES_NAME),
+
+ GET_REMAINING_GRACE_LOGIN_COUNT(REMAINING_GRACE_LOGIN_COUNT_NAME),
+
+ GET_PASSWORD_CHANGED_BY_REQUIRED_TIME(
+ PASSWORD_CHANGED_BY_REQUIRED_TIME_NAME), SET_PASSWORD_CHANGED_BY_REQUIRED_TIME(
+ PASSWORD_CHANGED_BY_REQUIRED_TIME_NAME), CLEAR_PASSWORD_CHANGED_BY_REQUIRED_TIME(
+ PASSWORD_CHANGED_BY_REQUIRED_TIME_NAME),
+
+ GET_SECONDS_UNTIL_REQUIRED_CHANGE_TIME(
+ SECONDS_UNTIL_REQUIRED_CHANGE_TIME_NAME),
+
+ GET_PASSWORD_HISTORY(PASSWORD_HISTORY_NAME), CLEAR_PASSWORD_HISTORY(
+ PASSWORD_HISTORY_NAME);
+
+ private String propertyName;
+
+
+
+ OperationType(String propertyName)
+ {
+ this.propertyName = propertyName;
+ }
+
+
+
+ public OperationType getOperationType()
+ {
+ return this;
+ }
+
+
+
+ public String getPropertyName()
+ {
+ return propertyName;
+ }
+
+
+
+ public Iterable<ByteString> getValues()
+ {
+ return null;
+ }
+
+
+
+ @Override
+ public String toString()
+ {
+ return propertyName;
+ }
+ }
+
+
+
+ public static class Request extends
+ AbstractExtendedRequest<Request, Response> implements
+ OperationContainer
+ {
+ private String targetUser;
+
+ private List<Operation> operations = new ArrayList<Operation>();
+
+
+
+ public Request(DN targetUser)
+ {
+ Validator.ensureNotNull(targetUser);
+ this.targetUser = targetUser.toString();
+ }
+
+
+
+ public Request(String targetUser)
+ {
+ Validator.ensureNotNull(targetUser);
+ this.targetUser = targetUser;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getRequestName()
+ {
+ return OID_PASSWORD_POLICY_STATE_EXTOP;
+ }
+
+
+
+ public void addAuthenticationFailureTime(Date date)
+ {
+ if (date == null)
+ {
+ operations.add(OperationType.ADD_AUTHENTICATION_FAILURE_TIMES);
+ }
+ else
+ {
+ operations.add(new MultiValueOperation(
+ OperationType.ADD_AUTHENTICATION_FAILURE_TIMES, ByteString
+ .valueOf(formatAsGeneralizedTime(date))));
+ }
+ }
+
+
+
+ public void addGraceLoginUseTime(Date date)
+ {
+ if (date == null)
+ {
+ operations.add(OperationType.ADD_GRACE_LOGIN_USE_TIME);
+ }
+ else
+ {
+ operations.add(new MultiValueOperation(
+ OperationType.ADD_GRACE_LOGIN_USE_TIME, ByteString
+ .valueOf(formatAsGeneralizedTime(date))));
+ }
+ }
+
+
+
+ public void addOperation(Operation operation)
+ {
+ operations.add(operation);
+ }
+
+
+
+ public void clearAccountDisabledState()
+ {
+ operations.add(OperationType.CLEAR_ACCOUNT_DISABLED_STATE);
+ }
+
+
+
+ public void clearAccountExpirationTime()
+ {
+ operations.add(OperationType.CLEAR_ACCOUNT_EXPIRATION_TIME);
+ }
+
+
+
+ public void clearAuthenticationFailureTimes()
+ {
+ operations.add(OperationType.CLEAR_AUTHENTICATION_FAILURE_TIMES);
+ }
+
+
+
+ public void clearGraceLoginUseTimes()
+ {
+ operations.add(OperationType.CLEAR_GRACE_LOGIN_USE_TIMES);
+ }
+
+
+
+ public void clearLastLoginTime()
+ {
+ operations.add(OperationType.CLEAR_LAST_LOGIN_TIME);
+ }
+
+
+
+ public void clearPasswordChangedByRequiredTime()
+ {
+ operations
+ .add(OperationType.CLEAR_PASSWORD_CHANGED_BY_REQUIRED_TIME);
+ }
+
+
+
+ public void clearPasswordChangedTime()
+ {
+ operations.add(OperationType.CLEAR_PASSWORD_CHANGED_TIME);
+ }
+
+
+
+ public void clearPasswordExpirationWarnedTime()
+ {
+ operations
+ .add(OperationType.CLEAR_PASSWORD_EXPIRATION_WARNED_TIME);
+ }
+
+
+
+ public void clearPasswordHistory()
+ {
+ operations.add(OperationType.CLEAR_PASSWORD_HISTORY);
+ }
+
+
+
+ public void clearPasswordResetState()
+ {
+ operations.add(OperationType.CLEAR_PASSWORD_RESET_STATE);
+ }
+
+
+
+ @Override
+ public OperationImpl getExtendedOperation()
+ {
+ return OPERATION_IMPL;
+ }
+
+
+
+ public Iterable<Operation> getOperations()
+ {
+ return operations;
+ }
+
+
+
+ public ByteString getRequestValue()
+ {
+ return encode(targetUser, operations);
+ }
+
+
+
+ public void requestAccountDisabledState()
+ {
+ operations.add(OperationType.GET_ACCOUNT_DISABLED_STATE);
+ }
+
+
+
+ public void requestAccountExpirationTime()
+ {
+ operations.add(OperationType.GET_ACCOUNT_EXPIRATION_TIME);
+ }
+
+
+
+ public void requestAuthenticationFailureTimes()
+ {
+ operations.add(OperationType.GET_AUTHENTICATION_FAILURE_TIMES);
+ }
+
+
+
+ public void requestGraceLoginUseTimes()
+ {
+ operations.add(OperationType.GET_GRACE_LOGIN_USE_TIMES);
+ }
+
+
+
+ public void requestLastLoginTime()
+ {
+ operations.add(OperationType.GET_LAST_LOGIN_TIME);
+ }
+
+
+
+ public void requestPasswordChangedByRequiredTime()
+ {
+ operations
+ .add(OperationType.GET_PASSWORD_CHANGED_BY_REQUIRED_TIME);
+ }
+
+
+
+ public void requestPasswordChangedTime()
+ {
+ operations.add(OperationType.GET_PASSWORD_CHANGED_TIME);
+ }
+
+
+
+ public void requestPasswordExpirationWarnedTime()
+ {
+ operations.add(OperationType.GET_PASSWORD_EXPIRATION_WARNED_TIME);
+ }
+
+
+
+ public void requestPasswordHistory()
+ {
+ operations.add(OperationType.GET_PASSWORD_HISTORY);
+ }
+
+
+
+ public void requestPasswordPolicyDN()
+ {
+ operations.add(OperationType.GET_PASSWORD_POLICY_DN);
+ }
+
+
+
+ public void requestPasswordResetState()
+ {
+ operations.add(OperationType.GET_PASSWORD_RESET_STATE);
+ }
+
+
+
+ public void requestRemainingAuthenticationFailureCount()
+ {
+ operations
+ .add(OperationType.GET_REMAINING_AUTHENTICATION_FAILURE_COUNT);
+ }
+
+
+
+ public void requestRemainingGraceLoginCount()
+ {
+ operations.add(OperationType.GET_REMAINING_GRACE_LOGIN_COUNT);
+ }
+
+
+
+ public void requestSecondsUntilAccountExpiration()
+ {
+ operations
+ .add(OperationType.GET_SECONDS_UNTIL_ACCOUNT_EXPIRATION);
+ }
+
+
+
+ public void requestSecondsUntilAuthenticationFailureUnlock()
+ {
+ operations
+ .add(OperationType.GET_SECONDS_UNTIL_AUTHENTICATION_FAILURE_UNLOCK);
+ }
+
+
+
+ public void requestSecondsUntilIdleLockout()
+ {
+ operations.add(OperationType.GET_SECONDS_UNTIL_IDLE_LOCKOUT);
+ }
+
+
+
+ public void requestSecondsUntilPasswordExpiration()
+ {
+ operations
+ .add(OperationType.GET_SECONDS_UNTIL_PASSWORD_EXPIRATION);
+ }
+
+
+
+ public void requestSecondsUntilPasswordExpirationWarning()
+ {
+ operations
+ .add(OperationType.GET_SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING);
+ }
+
+
+
+ public void requestSecondsUntilPasswordResetLockout()
+ {
+ operations
+ .add(OperationType.GET_SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT);
+ }
+
+
+
+ public void requestSecondsUntilRequiredChangeTime()
+ {
+ operations
+ .add(OperationType.GET_SECONDS_UNTIL_REQUIRED_CHANGE_TIME);
+ }
+
+
+
+ public void setAccountDisabledState(boolean state)
+ {
+ operations.add(new MultiValueOperation(
+ OperationType.SET_ACCOUNT_DISABLED_STATE, ByteString
+ .valueOf(String.valueOf(state))));
+ }
+
+
+
+ public void setAccountExpirationTime(Date date)
+ {
+ if (date == null)
+ {
+ operations.add(OperationType.SET_ACCOUNT_EXPIRATION_TIME);
+ }
+ else
+ {
+ operations.add(new MultiValueOperation(
+ OperationType.SET_ACCOUNT_EXPIRATION_TIME, ByteString
+ .valueOf(formatAsGeneralizedTime(date))));
+ }
+ }
+
+
+
+ public void setAuthenticationFailureTimes(Date... dates)
+ {
+ if (dates == null)
+ {
+ operations.add(OperationType.SET_AUTHENTICATION_FAILURE_TIMES);
+ }
+ else
+ {
+ ArrayList<ByteString> times = new ArrayList<ByteString>(
+ dates.length);
+ for (Date date : dates)
+ {
+ times.add(ByteString.valueOf(formatAsGeneralizedTime(date)));
+ }
+ operations.add(new MultiValueOperation(
+ OperationType.SET_AUTHENTICATION_FAILURE_TIMES, times));
+ }
+ }
+
+
+
+ public void setGraceLoginUseTimes(Date... dates)
+ {
+ if (dates == null)
+ {
+ operations.add(OperationType.SET_GRACE_LOGIN_USE_TIMES);
+ }
+ else
+ {
+ ArrayList<ByteString> times = new ArrayList<ByteString>(
+ dates.length);
+ for (Date date : dates)
+ {
+ times.add(ByteString.valueOf(formatAsGeneralizedTime(date)));
+ }
+ operations.add(new MultiValueOperation(
+ OperationType.SET_GRACE_LOGIN_USE_TIMES, times));
+ }
+ }
+
+
+
+ public void setLastLoginTime(Date date)
+ {
+ if (date == null)
+ {
+ operations.add(OperationType.SET_LAST_LOGIN_TIME);
+
+ }
+ else
+ {
+ operations.add(new MultiValueOperation(
+ OperationType.SET_LAST_LOGIN_TIME, ByteString
+ .valueOf(formatAsGeneralizedTime(date))));
+ }
+ }
+
+
+
+ public void setPasswordChangedByRequiredTime(boolean state)
+ {
+ operations.add(new MultiValueOperation(
+ OperationType.SET_PASSWORD_CHANGED_BY_REQUIRED_TIME,
+ ByteString.valueOf(String.valueOf(state))));
+ }
+
+
+
+ public void setPasswordChangedTime(Date date)
+ {
+ if (date == null)
+ {
+ operations.add(OperationType.SET_PASSWORD_CHANGED_TIME);
+ }
+ else
+ {
+ operations.add(new MultiValueOperation(
+ OperationType.SET_PASSWORD_CHANGED_TIME, ByteString
+ .valueOf(formatAsGeneralizedTime(date))));
+ }
+ }
+
+
+
+ public void setPasswordExpirationWarnedTime(Date date)
+ {
+ if (date == null)
+ {
+ operations
+ .add(OperationType.SET_PASSWORD_EXPIRATION_WARNED_TIME);
+
+ }
+ else
+ {
+ operations.add(new MultiValueOperation(
+ OperationType.SET_PASSWORD_EXPIRATION_WARNED_TIME,
+ ByteString.valueOf(formatAsGeneralizedTime(date))));
+ }
+ }
+
+
+
+ public void setPasswordResetState(boolean state)
+ {
+ operations.add(new MultiValueOperation(
+ OperationType.SET_LAST_LOGIN_TIME, ByteString.valueOf(String
+ .valueOf(state))));
+ }
+
+
+
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append("PasswordPolicyStateExtendedRequest(requestName=");
+ builder.append(getRequestName());
+ builder.append(", targetUser=");
+ builder.append(targetUser);
+ builder.append(", operations=");
+ builder.append(operations);
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+ }
+
+
+
+ public static class Response extends AbstractExtendedResult<Response>
+ implements OperationContainer
+ {
+ private String targetUser;
+
+ private List<Operation> operations = new ArrayList<Operation>();
+
+
+
+ public Response(ResultCode resultCode, DN targetUser)
+ {
+ this(resultCode, String.valueOf(targetUser));
+ }
+
+
+
+ public Response(ResultCode resultCode, String targetUser)
+ {
+ super(resultCode);
+ this.targetUser = targetUser;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getResponseName()
+ {
+ // No response name defined.
+ return OID_PASSWORD_POLICY_STATE_EXTOP;
+ }
+
+
+
+ public void addOperation(Operation operation)
+ {
+ operations.add(operation);
+ }
+
+
+
+ public Iterable<Operation> getOperations()
+ {
+ return operations;
+ }
+
+
+
+ public ByteString getResponseValue()
+ {
+ return encode(targetUser, operations);
+ }
+
+
+
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append("PasswordPolicyStateExtendedResponse(resultCode=");
+ builder.append(getResultCode());
+ builder.append(", matchedDN=");
+ builder.append(getMatchedDN());
+ builder.append(", diagnosticMessage=");
+ builder.append(getDiagnosticMessage());
+ builder.append(", referrals=");
+ builder.append(getReferralURIs());
+ builder.append(", responseName=");
+ builder.append(getResponseName());
+ builder.append(", targetUser=");
+ builder.append(targetUser);
+ builder.append(", operations=");
+ builder.append(operations);
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+ }
+
+
+
+ private static class MultiValueOperation implements Operation
+ {
+ private OperationType property;
+
+ private List<ByteString> values;
+
+
+
+ private MultiValueOperation(OperationType property, ByteString value)
+ {
+ this.property = property;
+ this.values = Collections.singletonList(value);
+ }
+
+
+
+ private MultiValueOperation(OperationType property,
+ List<ByteString> values)
+ {
+ this.property = property;
+ this.values = values;
+ }
+
+
+
+ public OperationType getOperationType()
+ {
+ return property;
+ }
+
+
+
+ public Iterable<ByteString> getValues()
+ {
+ return values;
+ }
+
+
+
+ @Override
+ public String toString()
+ {
+ return property.getPropertyName() + ": " + values;
+ }
+ }
+
+
+
+ private interface OperationContainer
+ {
+ public void addOperation(Operation operation);
+
+
+
+ public Iterable<Operation> getOperations();
+ }
+
+
+
+ private static final String PASSWORD_POLICY_DN_NAME = "Password Policy DN";
+
+ private static final String ACCOUNT_DISABLED_STATE_NAME = "Account Disabled State";
+
+ private static final String ACCOUNT_EXPIRATION_TIME_NAME = "Account Expiration Time";
+
+ private static final String SECONDS_UNTIL_ACCOUNT_EXPIRATION_NAME = "Seconds Until Account Expiration";
+
+ private static final String PASSWORD_CHANGED_TIME_NAME = "Password Changed Time";
+
+ private static final String PASSWORD_EXPIRATION_WARNED_TIME_NAME = "Password Expiration Warned Time";
+
+ private static final String SECONDS_UNTIL_PASSWORD_EXPIRATION_NAME = "Seconds Until Password Expiration";
+
+ private static final String SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING_NAME = "Seconds Until Password Expiration Warning";
+
+ private static final String AUTHENTICATION_FAILURE_TIMES_NAME = "Authentication Failure Times";
+
+ private static final String SECONDS_UNTIL_AUTHENTICATION_FAILURE_UNLOCK_NAME = "Seconds Until Authentication Failure Unlock";
+
+ private static final String REMAINING_AUTHENTICATION_FAILURE_COUNT_NAME = "Remaining Authentication Failure Count";
+
+ private static final String LAST_LOGIN_TIME_NAME = "Last Login Time";
+
+ private static final String SECONDS_UNTIL_IDLE_LOCKOUT_NAME = "Seconds Until Idle Lockout";
+
+ private static final String PASSWORD_RESET_STATE_NAME = "Password Reset State";
+
+ private static final String SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT_NAME = "Seconds Until Password Reset Lockout";
+
+ private static final String GRACE_LOGIN_USE_TIMES_NAME = "Grace Login Use Times";
+
+ private static final String REMAINING_GRACE_LOGIN_COUNT_NAME = "Remaining Grace Login Count";
+
+ private static final String PASSWORD_CHANGED_BY_REQUIRED_TIME_NAME = "Password Changed By Required Time";
+
+ private static final String SECONDS_UNTIL_REQUIRED_CHANGE_TIME_NAME = "Seconds Until Required Change Time";
+
+ private static final String PASSWORD_HISTORY_NAME = "Password History";
+
+
+
+ private static void decodeOperations(ASN1Reader reader,
+ OperationContainer container) throws IOException, DecodeException
+ {
+ // See if we have operations
+ if (reader.hasNextElement())
+ {
+ reader.readStartSequence();
+ int opType;
+ OperationType type;
+ while (reader.hasNextElement())
+ {
+ reader.readStartSequence();
+ // Read the opType
+ opType = reader.readEnumerated();
+ try
+ {
+ type = OperationType.values()[opType];
+ }
+ catch (IndexOutOfBoundsException iobe)
+ {
+ throw DecodeException.error(
+ ERR_PWPSTATE_EXTOP_UNKNOWN_OP_TYPE.get(String
+ .valueOf(opType)), iobe);
+ }
+
+ // See if we have any values
+ if (reader.hasNextElement())
+ {
+ reader.readStartSequence();
+ ArrayList<ByteString> values = new ArrayList<ByteString>();
+ while (reader.hasNextElement())
+ {
+ values.add(reader.readOctetString());
+ }
+ reader.readEndSequence();
+ container.addOperation(new MultiValueOperation(type, values));
+ }
+ else
+ {
+ container.addOperation(type);
+ }
+ reader.readEndSequence();
+ }
+ reader.readEndSequence();
+ }
+ }
+
+
+
+ private static ByteString encode(String targetUser,
+ List<Operation> operations)
+ {
+ ByteStringBuilder buffer = new ByteStringBuilder(6);
+ ASN1Writer writer = ASN1.getWriter(buffer);
+
+ try
+ {
+ writer.writeStartSequence();
+ writer.writeOctetString(targetUser);
+ if (!operations.isEmpty())
+ {
+ writer.writeStartSequence();
+ for (Operation operation : operations)
+ {
+ writer.writeStartSequence();
+ writer
+ .writeEnumerated(operation.getOperationType().ordinal());
+ if (operation.getValues() != null)
+ {
+ writer.writeStartSequence();
+ for (ByteString value : operation.getValues())
+ {
+ writer.writeOctetString(value);
+ }
+ writer.writeEndSequence();
+ }
+ writer.writeEndSequence();
+ }
+ writer.writeEndSequence();
+ }
+ writer.writeEndSequence();
+ }
+ catch (IOException ioe)
+ {
+ // This should never happen unless there is a bug somewhere.
+ throw new RuntimeException(ioe);
+ }
+
+ return buffer.toByteString();
+ }
+
+
+
+ private static final class OperationImpl implements
+ ExtendedOperation<Request, Response>
+ {
+
+ public Request decodeRequest(String requestName,
+ ByteString requestValue) throws DecodeException
+ {
+ if ((requestValue == null) || (requestValue.length() <= 0))
+ {
+ throw DecodeException
+ .error(ERR_PWPSTATE_EXTOP_NO_REQUEST_VALUE.get());
+ }
+
+ try
+ {
+ ASN1Reader reader = ASN1.getReader(requestValue);
+ reader.readStartSequence();
+
+ // Read the target user DN
+ Request request = new Request(reader.readOctetStringAsString());
+
+ decodeOperations(reader, request);
+ reader.readEndSequence();
+ return request;
+ }
+ catch (IOException ioe)
+ {
+ Message message = ERR_PWPSTATE_EXTOP_DECODE_FAILURE
+ .get(getExceptionMessage(ioe));
+ throw DecodeException.error(message, ioe);
+ }
+ }
+
+
+
+ public Response decodeResponse(ResultCode resultCode,
+ String matchedDN, String diagnosticMessage,
+ String responseName, ByteString responseValue)
+ throws DecodeException
+ {
+ if (!resultCode.isExceptional()
+ && ((responseValue == null) || (responseValue.length() <= 0)))
+ {
+ throw DecodeException
+ .error(ERR_PWPSTATE_EXTOP_NO_REQUEST_VALUE.get());
+ }
+
+ try
+ {
+ ASN1Reader reader = ASN1.getReader(responseValue);
+ reader.readStartSequence();
+
+ // Read the target user DN
+ Response response = new Response(resultCode, reader
+ .readOctetStringAsString()).setMatchedDN(matchedDN)
+ .setDiagnosticMessage(diagnosticMessage);
+
+ decodeOperations(reader, response);
+ reader.readEndSequence();
+ return response;
+ }
+ catch (IOException ioe)
+ {
+ Message message = ERR_PWPSTATE_EXTOP_DECODE_FAILURE
+ .get(getExceptionMessage(ioe));
+ throw DecodeException.error(message, ioe);
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Response decodeResponse(ResultCode resultCode,
+ String matchedDN, String diagnosticMessage)
+ {
+ if (!resultCode.isExceptional())
+ {
+ // A successful response must contain a response name and
+ // value.
+ throw new IllegalArgumentException(
+ "No response name and value for result code "
+ + resultCode.intValue());
+ }
+
+ return new Response(resultCode, (String) null).setMatchedDN(
+ matchedDN).setDiagnosticMessage(diagnosticMessage);
+ }
+ }
+
+
+
+ // Singleton instance.
+ private static final OperationImpl OPERATION_IMPL = new OperationImpl();
+}
diff --git a/sdk/src/org/opends/sdk/extensions/StartTLSRequest.java b/sdk/src/org/opends/sdk/extensions/StartTLSRequest.java
new file mode 100644
index 0000000..5b05138
--- /dev/null
+++ b/sdk/src/org/opends/sdk/extensions/StartTLSRequest.java
@@ -0,0 +1,120 @@
+package org.opends.sdk.extensions;
+
+
+
+import javax.net.ssl.SSLContext;
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.requests.AbstractExtendedRequest;
+import org.opends.sdk.responses.Responses;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * Created by IntelliJ IDEA. User: boli Date: Jun 22, 2009 Time: 6:21:44
+ * PM To change this template use File | Settings | File Templates.
+ */
+public final class StartTLSRequest extends
+ AbstractExtendedRequest<StartTLSRequest, Result>
+{
+ private final SSLContext sslContext;
+
+ /**
+ * The request OID for the StartTLS extended operation.
+ */
+ public static final String OID_START_TLS_REQUEST = "1.3.6.1.4.1.1466.20037";
+
+
+
+ public StartTLSRequest(SSLContext sslContext)
+ {
+ this.sslContext = sslContext;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getRequestName()
+ {
+ return OID_START_TLS_REQUEST;
+ }
+
+
+
+ public Operation getExtendedOperation()
+ {
+ return OPERATION;
+ }
+
+
+
+ public ByteString getRequestValue()
+ {
+ return null;
+ }
+
+
+
+ public SSLContext getSSLContext()
+ {
+ return sslContext;
+ }
+
+
+
+ public StringBuilder toString(StringBuilder builder)
+ {
+ builder.append("StartTLSExtendedRequest(requestName=");
+ builder.append(getRequestName());
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder;
+ }
+
+
+
+ private static final class Operation implements
+ ExtendedOperation<StartTLSRequest, Result>
+ {
+
+ public StartTLSRequest decodeRequest(String requestName,
+ ByteString requestValue) throws DecodeException
+ {
+ return new StartTLSRequest(null);
+ }
+
+
+
+ public Result decodeResponse(ResultCode resultCode,
+ String matchedDN, String diagnosticMessage,
+ String responseName, ByteString responseValue)
+ throws DecodeException
+ {
+ // TODO: Should we check oid is NOT null and matches but
+ // value is null?
+ return Responses.newResult(resultCode).setMatchedDN(matchedDN)
+ .setDiagnosticMessage(diagnosticMessage);
+ }
+
+
+
+ public Result decodeResponse(ResultCode resultCode,
+ String matchedDN, String diagnosticMessage)
+ {
+ return Responses.newResult(resultCode).setMatchedDN(matchedDN)
+ .setDiagnosticMessage(diagnosticMessage);
+ }
+ }
+
+
+
+ // Singleton instance.
+ private static final Operation OPERATION = new Operation();
+
+}
diff --git a/sdk/src/org/opends/sdk/extensions/WhoAmIRequest.java b/sdk/src/org/opends/sdk/extensions/WhoAmIRequest.java
new file mode 100644
index 0000000..c9a33c8
--- /dev/null
+++ b/sdk/src/org/opends/sdk/extensions/WhoAmIRequest.java
@@ -0,0 +1,110 @@
+package org.opends.sdk.extensions;
+
+
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.requests.AbstractExtendedRequest;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * Created by IntelliJ IDEA. User: boli Date: Jun 22, 2009 Time: 6:40:06
+ * PM To change this template use File | Settings | File Templates.
+ */
+public final class WhoAmIRequest extends
+ AbstractExtendedRequest<WhoAmIRequest, WhoAmIResult>
+{
+ /**
+ * The request OID for the "Who Am I?" extended operation.
+ */
+ static final String OID_WHO_AM_I_REQUEST = "1.3.6.1.4.1.4203.1.11.3";
+
+
+
+ public WhoAmIRequest()
+ {
+
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getRequestName()
+ {
+ return OID_WHO_AM_I_REQUEST;
+ }
+
+
+
+ public Operation getExtendedOperation()
+ {
+ return OPERATION;
+ }
+
+
+
+ public ByteString getRequestValue()
+ {
+ return null;
+ }
+
+
+
+ public StringBuilder toString(StringBuilder builder)
+ {
+ builder.append("WhoAmIExtendedRequest(requestName=");
+ builder.append(getRequestName());
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder;
+ }
+
+
+
+ private static final class Operation implements
+ ExtendedOperation<WhoAmIRequest, WhoAmIResult>
+ {
+
+ public WhoAmIRequest decodeRequest(String requestName,
+ ByteString requestValue) throws DecodeException
+ {
+ return new WhoAmIRequest();
+ }
+
+
+
+ public WhoAmIResult decodeResponse(ResultCode resultCode,
+ String matchedDN, String diagnosticMessage,
+ String responseName, ByteString responseValue)
+ throws DecodeException
+ {
+ // TODO: Should we check oid is null?
+ String authzId = null;
+ if (responseValue != null)
+ {
+ authzId = responseValue.toString();
+ }
+ return new WhoAmIResult(resultCode).setMatchedDN(matchedDN)
+ .setDiagnosticMessage(diagnosticMessage).setAuthzId(authzId);
+ }
+
+
+
+ public WhoAmIResult decodeResponse(ResultCode resultCode,
+ String matchedDN, String diagnosticMessage)
+ {
+ return new WhoAmIResult(resultCode).setMatchedDN(matchedDN)
+ .setDiagnosticMessage(diagnosticMessage);
+ }
+ }
+
+
+
+ // Singleton instance.
+ private static final Operation OPERATION = new Operation();
+}
diff --git a/sdk/src/org/opends/sdk/extensions/WhoAmIResult.java b/sdk/src/org/opends/sdk/extensions/WhoAmIResult.java
new file mode 100644
index 0000000..a59a45b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/extensions/WhoAmIResult.java
@@ -0,0 +1,113 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.extensions;
+
+
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.responses.AbstractExtendedResult;
+import org.opends.sdk.util.ByteString;
+
+
+
+public class WhoAmIResult extends AbstractExtendedResult<WhoAmIResult>
+{
+ private String authzId;
+
+
+
+ public WhoAmIResult(ResultCode resultCode)
+ {
+ super(resultCode);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getResponseName()
+ {
+ // No response name defined.
+ return null;
+ }
+
+
+
+ /**
+ * Get the authzId to return or <code>null</code> if it is not
+ * available.
+ *
+ * @return The authzID or <code>null</code>.
+ */
+ public String getAuthzId()
+ {
+ return authzId;
+ }
+
+
+
+ public ByteString getResponseValue()
+ {
+ if (authzId != null)
+ {
+ ByteString.valueOf(authzId);
+ }
+ return null;
+ }
+
+
+
+ public WhoAmIResult setAuthzId(String authzId)
+ {
+ this.authzId = authzId;
+ return this;
+ }
+
+
+
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append("WhoAmIExtendedResponse(resultCode=");
+ builder.append(getResultCode());
+ builder.append(", matchedDN=");
+ builder.append(getMatchedDN());
+ builder.append(", diagnosticMessage=");
+ builder.append(getDiagnosticMessage());
+ builder.append(", referrals=");
+ builder.append(getReferralURIs());
+ builder.append(", authzId=");
+ builder.append(authzId);
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/ASN1StreamReader.java b/sdk/src/org/opends/sdk/ldap/ASN1StreamReader.java
new file mode 100644
index 0000000..aab8ef0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/ASN1StreamReader.java
@@ -0,0 +1,838 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.ldap;
+
+
+
+import static org.opends.messages.ProtocolMessages.*;
+import static org.opends.sdk.ldap.LDAPConstants.ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES;
+import static org.opends.sdk.ldap.LDAPConstants.ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE;
+import static org.opends.sdk.ldap.LDAPConstants.ELEMENT_READ_STATE_NEED_TYPE;
+import static org.opends.sdk.ldap.LDAPConstants.ELEMENT_READ_STATE_NEED_VALUE_BYTES;
+
+import java.io.IOException;
+import java.nio.BufferUnderflowException;
+import java.util.logging.Level;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.AbstractASN1Reader;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.StaticUtils;
+
+import com.sun.grizzly.streams.StreamReader;
+import com.sun.grizzly.utils.PoolableObject;
+
+
+
+/**
+ * Grizzly ASN1 reader implementation.
+ */
+final class ASN1StreamReader extends AbstractASN1Reader implements
+ PoolableObject, ASN1Reader
+{
+ class ChildSequenceLimiter implements SequenceLimiter
+ {
+ private SequenceLimiter parent;
+
+ private ChildSequenceLimiter child;
+
+ private int readLimit;
+
+ private int bytesRead;
+
+
+
+ public void checkLimit(int readSize) throws IOException,
+ BufferUnderflowException
+ {
+ if ((readLimit > 0) && (bytesRead + readSize > readLimit))
+ {
+ throw new BufferUnderflowException();
+ }
+
+ parent.checkLimit(readSize);
+
+ bytesRead += readSize;
+ }
+
+
+
+ public SequenceLimiter endSequence() throws IOException
+ {
+ parent.checkLimit(remaining());
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINE)
+ && remaining() > 0)
+ {
+ StaticUtils.DEBUG_LOG.fine(String.format(
+ "Ignoring %d unused trailing bytes in ASN.1 SEQUENCE",
+ remaining()));
+ }
+
+ for (int i = 0; i < remaining(); i++)
+ {
+ streamReader.readByte();
+ }
+
+ return parent;
+ }
+
+
+
+ public int remaining()
+ {
+ return readLimit - bytesRead;
+ }
+
+
+
+ public ChildSequenceLimiter startSequence(int readLimit)
+ {
+ if (child == null)
+ {
+ child = new ChildSequenceLimiter();
+ child.parent = this;
+ }
+
+ child.readLimit = readLimit;
+ child.bytesRead = 0;
+
+ return child;
+ }
+ }
+
+
+
+ class RootSequenceLimiter implements SequenceLimiter
+ {
+ private ChildSequenceLimiter child;
+
+
+
+ public void checkLimit(int readSize)
+ {
+ }
+
+
+
+ public ChildSequenceLimiter endSequence() throws DecodeException
+ {
+ Message message = ERR_ASN1_SEQUENCE_READ_NOT_STARTED.get();
+ throw new IllegalStateException(message.toString());
+ }
+
+
+
+ public int remaining()
+ {
+ return streamReader.availableDataSize();
+ }
+
+
+
+ public ChildSequenceLimiter startSequence(int readLimit)
+ {
+ if (child == null)
+ {
+ child = new ChildSequenceLimiter();
+ child.parent = this;
+ }
+
+ child.readLimit = readLimit;
+ child.bytesRead = 0;
+
+ return child;
+ }
+ }
+
+
+
+ private interface SequenceLimiter
+ {
+ public void checkLimit(int readSize) throws IOException,
+ BufferUnderflowException;
+
+
+
+ public SequenceLimiter endSequence() throws IOException;
+
+
+
+ public int remaining();
+
+
+
+ public SequenceLimiter startSequence(int readLimit);
+ }
+
+
+
+ private static final int MAX_STRING_BUFFER_SIZE = 1024;
+
+ private int state = ELEMENT_READ_STATE_NEED_TYPE;
+
+ private byte peekType = 0;
+
+ private int peekLength = -1;
+
+ private int lengthBytesNeeded = 0;
+
+ private final int maxElementSize;
+
+ private StreamReader streamReader;
+
+ private final RootSequenceLimiter rootLimiter;
+
+ private SequenceLimiter readLimiter;
+
+ private final byte[] buffer;
+
+
+
+ /**
+ * Creates a new ASN1 reader whose source is the provided input stream
+ * and having a user defined maximum BER element size.
+ *
+ * @param maxElementSize
+ * The maximum BER element size, or <code>0</code> to
+ * indicate that there is no limit.
+ */
+ public ASN1StreamReader(int maxElementSize)
+ {
+ this.readLimiter = this.rootLimiter = new RootSequenceLimiter();
+ this.buffer = new byte[MAX_STRING_BUFFER_SIZE];
+ this.maxElementSize = maxElementSize;
+ }
+
+
+
+ /**
+ * Closes this ASN.1 reader and the underlying stream.
+ *
+ * @throws IOException
+ * if an I/O error occurs
+ */
+ public void close() throws IOException
+ {
+ // close the stream reader.
+ streamReader.close();
+ }
+
+
+
+ /**
+ * Determines if a complete ASN.1 element is ready to be read from the
+ * stream reader.
+ *
+ * @return <code>true</code> if another complete element is available
+ * or <code>false</code> otherwise.
+ * @throws IOException
+ * If an error occurs while trying to decode an ASN1
+ * element.
+ */
+ public boolean elementAvailable() throws IOException
+ {
+ if ((state == ELEMENT_READ_STATE_NEED_TYPE) && !needTypeState(true))
+ {
+ return false;
+ }
+ if ((state == ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE)
+ && !needFirstLengthByteState(true))
+ {
+ return false;
+ }
+ if ((state == ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES)
+ && !needAdditionalLengthBytesState(true))
+ {
+ return false;
+ }
+
+ return peekLength <= readLimiter.remaining();
+ }
+
+
+
+ /**
+ * Determines if the input stream contains at least one ASN.1 element
+ * to be read.
+ *
+ * @return <code>true</code> if another element is available or
+ * <code>false</code> otherwise.
+ * @throws IOException
+ * If an error occurs while trying to decode an ASN1
+ * element.
+ */
+ public boolean hasNextElement() throws IOException
+ {
+ return (state != ELEMENT_READ_STATE_NEED_TYPE)
+ || needTypeState(true);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int peekLength() throws IOException
+ {
+ peekType();
+
+ switch (state)
+ {
+ case ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE:
+ needFirstLengthByteState(false);
+ break;
+
+ case ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES:
+ needAdditionalLengthBytesState(false);
+ }
+
+ return peekLength;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public byte peekType() throws IOException
+ {
+ if (state == ELEMENT_READ_STATE_NEED_TYPE)
+ {
+ needTypeState(false);
+ }
+
+ return peekType;
+ }
+
+
+
+ public void prepare()
+ {
+ // Nothing to do
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean readBoolean() throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ if (peekLength != 1)
+ {
+ Message message = ERR_ASN1_BOOLEAN_INVALID_LENGTH.get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+
+ readLimiter.checkLimit(peekLength);
+ byte readByte = streamReader.readByte();
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "READ ASN.1 BOOLEAN(type=0x%x, length=%d, value=%s)",
+ peekType, peekLength, String.valueOf(readByte != 0x00)));
+ }
+
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ return readByte != 0x00;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void readEndSequence() throws IOException,
+ IllegalStateException
+ {
+ readLimiter = readLimiter.endSequence();
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String
+ .format("READ ASN.1 END SEQUENCE"));
+ }
+
+ // Reset the state
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void readEndSet() throws IOException
+ {
+ // From an implementation point of view, a set is equivalent to a
+ // sequence.
+ readEndSequence();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int readEnumerated() throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ if ((peekLength < 1) || (peekLength > 4))
+ {
+ Message message = ERR_ASN1_INTEGER_INVALID_LENGTH.get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+
+ // From an implementation point of view, an enumerated value is
+ // equivalent to an integer.
+ return (int) readInteger();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public long readInteger() throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ if ((peekLength < 1) || (peekLength > 8))
+ {
+ Message message = ERR_ASN1_INTEGER_INVALID_LENGTH.get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+
+ readLimiter.checkLimit(peekLength);
+ if (peekLength > 4)
+ {
+ long longValue = 0;
+ for (int i = 0; i < peekLength; i++)
+ {
+ int readByte = streamReader.readByte();
+ if ((i == 0) && (((byte) readByte) < 0))
+ {
+ longValue = 0xFFFFFFFFFFFFFFFFL;
+ }
+ longValue = (longValue << 8) | (readByte & 0xFF);
+ }
+
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ return longValue;
+ }
+ else
+ {
+ int intValue = 0;
+ for (int i = 0; i < peekLength; i++)
+ {
+ int readByte = streamReader.readByte();
+ if ((i == 0) && (((byte) readByte) < 0))
+ {
+ intValue = 0xFFFFFFFF;
+ }
+ intValue = (intValue << 8) | (readByte & 0xFF);
+ }
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "READ ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ peekType, peekLength, intValue));
+ }
+
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ return intValue;
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void readNull() throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ // Make sure that the decoded length is exactly zero byte.
+ if (peekLength != 0)
+ {
+ Message message = ERR_ASN1_NULL_INVALID_LENGTH.get(peekLength);
+ throw DecodeException.fatalError(message);
+ }
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String
+ .format("READ ASN.1 NULL(type=0x%x, length=%d)", peekType,
+ peekLength));
+ }
+
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteString readOctetString() throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ if (peekLength == 0)
+ {
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ return ByteString.empty();
+ }
+
+ readLimiter.checkLimit(peekLength);
+ // Copy the value and construct the element to return.
+ byte[] value = new byte[peekLength];
+ streamReader.readByteArray(value);
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "READ ASN.1 OCTETSTRING(type=0x%x, length=%d)", peekType,
+ peekLength));
+ }
+
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ return ByteString.wrap(value);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteStringBuilder readOctetString(ByteStringBuilder builder)
+ throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ if (peekLength == 0)
+ {
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ return builder;
+ }
+
+ readLimiter.checkLimit(peekLength);
+ // Copy the value and construct the element to return.
+ // TODO: Is there a more efficient way to do this?
+ for (int i = 0; i < peekLength; i++)
+ {
+ builder.append(streamReader.readByte());
+ }
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "READ ASN.1 OCTETSTRING(type=0x%x, length=%d)", peekType,
+ peekLength));
+ }
+
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ return builder;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String readOctetStringAsString() throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ if (peekLength == 0)
+ {
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ return "";
+ }
+
+ byte[] readBuffer;
+ if (peekLength <= buffer.length)
+ {
+ readBuffer = buffer;
+ }
+ else
+ {
+ readBuffer = new byte[peekLength];
+ }
+
+ readLimiter.checkLimit(peekLength);
+ streamReader.readByteArray(readBuffer, 0, peekLength);
+
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+
+ String str;
+ try
+ {
+ str = new String(readBuffer, 0, peekLength, "UTF-8");
+ }
+ catch (Exception e)
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ StaticUtils.DEBUG_LOG
+ .warning("Unable to decode ASN.1 OCTETSTRING bytes as UTF-8 string: "
+ + e.toString());
+ }
+
+ str = new String(buffer, 0, peekLength);
+ }
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "READ ASN.1 OCTETSTRING(type=0x%x, length=%d, value=%s)",
+ peekType, peekLength, str));
+ }
+
+ return str;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void readStartSequence() throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ readLimiter = readLimiter.startSequence(peekLength);
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "READ ASN.1 START SEQUENCE(type=0x%x, length=%d)", peekType,
+ peekLength));
+ }
+
+ // Reset the state
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void readStartSet() throws IOException
+ {
+ // From an implementation point of view, a set is equivalent to a
+ // sequence.
+ readStartSequence();
+ }
+
+
+
+ public void release()
+ {
+ streamReader = null;
+ peekLength = -1;
+ peekType = 0;
+ readLimiter = rootLimiter;
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ }
+
+
+
+ public void setStreamReader(StreamReader streamReader)
+ {
+ this.streamReader = streamReader;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Reader skipElement() throws IOException
+ {
+ // Read the header if haven't done so already
+ peekLength();
+
+ readLimiter.checkLimit(peekLength);
+ for (int i = 0; i < peekLength; i++)
+ {
+ streamReader.readByte();
+ }
+ state = ELEMENT_READ_STATE_NEED_TYPE;
+ return this;
+ }
+
+
+
+ /**
+ * Internal helper method reading the additional ASN.1 length bytes
+ * and transition to the next state if successful.
+ *
+ * @param ensureRead
+ * <code>true</code> to check for availability first.
+ * @return <code>true</code> if the length bytes was successfully
+ * read.
+ * @throws IOException
+ * If an error occurs while reading from the stream.
+ */
+ private boolean needAdditionalLengthBytesState(boolean ensureRead)
+ throws IOException
+ {
+ if (ensureRead && (readLimiter.remaining() < lengthBytesNeeded))
+ {
+ return false;
+ }
+
+ byte readByte;
+ readLimiter.checkLimit(lengthBytesNeeded);
+ while (lengthBytesNeeded > 0)
+ {
+ readByte = streamReader.readByte();
+ peekLength = (peekLength << 8) | (readByte & 0xFF);
+ lengthBytesNeeded--;
+ }
+
+ // Make sure that the element is not larger than the maximum allowed
+ // message size.
+ if ((maxElementSize > 0) && (peekLength > maxElementSize))
+ {
+ Message m = ERR_LDAP_CLIENT_DECODE_MAX_REQUEST_SIZE_EXCEEDED.get(
+ peekLength, maxElementSize);
+ throw DecodeException.fatalError(m);
+ }
+ state = ELEMENT_READ_STATE_NEED_VALUE_BYTES;
+ return true;
+ }
+
+
+
+ /**
+ * Internal helper method reading the first length bytes and
+ * transition to the next state if successful.
+ *
+ * @param ensureRead
+ * <code>true</code> to check for availability first.
+ * @return <code>true</code> if the length bytes was successfully read
+ * @throws IOException
+ * If an error occurs while trying to decode an ASN1
+ * element.
+ */
+ private boolean needFirstLengthByteState(boolean ensureRead)
+ throws IOException
+ {
+ if (ensureRead && (readLimiter.remaining() <= 0))
+ {
+ return false;
+ }
+
+ readLimiter.checkLimit(1);
+ byte readByte = streamReader.readByte();
+ peekLength = (readByte & 0x7F);
+ if (peekLength != readByte)
+ {
+ lengthBytesNeeded = peekLength;
+ if (lengthBytesNeeded > 4)
+ {
+ Message message = ERR_ASN1_INVALID_NUM_LENGTH_BYTES
+ .get(lengthBytesNeeded);
+ throw DecodeException.fatalError(message);
+ }
+ peekLength = 0x00;
+
+ if (ensureRead && (readLimiter.remaining() < lengthBytesNeeded))
+ {
+ state = ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES;
+ return false;
+ }
+
+ readLimiter.checkLimit(lengthBytesNeeded);
+ while (lengthBytesNeeded > 0)
+ {
+ readByte = streamReader.readByte();
+ peekLength = (peekLength << 8) | (readByte & 0xFF);
+ lengthBytesNeeded--;
+ }
+ }
+
+ // Make sure that the element is not larger than the maximum allowed
+ // message size.
+ if ((maxElementSize > 0) && (peekLength > maxElementSize))
+ {
+ Message m = ERR_LDAP_CLIENT_DECODE_MAX_REQUEST_SIZE_EXCEEDED.get(
+ peekLength, maxElementSize);
+ throw DecodeException.fatalError(m);
+ }
+ state = ELEMENT_READ_STATE_NEED_VALUE_BYTES;
+ return true;
+ }
+
+
+
+ /**
+ * Internal helper method reading the ASN.1 type byte and transition
+ * to the next state if successful.
+ *
+ * @param ensureRead
+ * <code>true</code> to check for availability first.
+ * @return <code>true</code> if the type byte was successfully read
+ * @throws IOException
+ * If an error occurs while trying to decode an ASN1
+ * element.
+ */
+ private boolean needTypeState(boolean ensureRead) throws IOException
+ {
+ // Read just the type.
+ if (ensureRead && (readLimiter.remaining() <= 0))
+ {
+ return false;
+ }
+
+ readLimiter.checkLimit(1);
+ peekType = streamReader.readByte();
+ state = ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE;
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/ASN1StreamWriter.java b/sdk/src/org/opends/sdk/ldap/ASN1StreamWriter.java
new file mode 100644
index 0000000..9221029
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/ASN1StreamWriter.java
@@ -0,0 +1,675 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.ldap;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_ASN1_SEQUENCE_WRITE_NOT_STARTED;
+import static org.opends.sdk.asn1.ASN1Constants.BOOLEAN_VALUE_FALSE;
+import static org.opends.sdk.asn1.ASN1Constants.BOOLEAN_VALUE_TRUE;
+
+import java.io.IOException;
+import java.util.logging.Level;
+
+import org.opends.messages.Message;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.asn1.AbstractASN1Writer;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.StaticUtils;
+
+import com.sun.grizzly.streams.StreamWriter;
+import com.sun.grizzly.utils.PoolableObject;
+
+
+
+/**
+ * Grizzly ASN1 writer implementation.
+ */
+final class ASN1StreamWriter extends AbstractASN1Writer implements
+ ASN1Writer, PoolableObject
+{
+ private class ChildSequenceBuffer implements SequenceBuffer
+ {
+ private SequenceBuffer parent;
+
+ private ChildSequenceBuffer child;
+
+ private final ByteStringBuilder buffer = new ByteStringBuilder(
+ SUB_SEQUENCE_BUFFER_INIT_SIZE);
+
+
+
+ public SequenceBuffer endSequence() throws IOException
+ {
+ writeLength(parent, buffer.length());
+ parent.writeByteArray(buffer.getBackingArray(), 0, buffer
+ .length());
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 END SEQUENCE(length=%d)", buffer.length()));
+ }
+
+ return parent;
+ }
+
+
+
+ public SequenceBuffer startSequence(byte type) throws IOException
+ {
+ if (child == null)
+ {
+ child = new ChildSequenceBuffer();
+ child.parent = this;
+ }
+
+ buffer.append(type);
+ child.buffer.clear();
+
+ return child;
+ }
+
+
+
+ public void writeByte(byte b) throws IOException
+ {
+ buffer.append(b);
+ }
+
+
+
+ public void writeByteArray(byte[] bs, int offset, int length)
+ throws IOException
+ {
+ buffer.append(bs, offset, length);
+ }
+ }
+
+
+
+ private class RootSequenceBuffer implements SequenceBuffer
+ {
+ private ChildSequenceBuffer child;
+
+
+
+ public SequenceBuffer endSequence() throws IOException
+ {
+ Message message = ERR_ASN1_SEQUENCE_WRITE_NOT_STARTED.get();
+ throw new IllegalStateException(message.toString());
+ }
+
+
+
+ public SequenceBuffer startSequence(byte type) throws IOException
+ {
+ if (child == null)
+ {
+ child = new ChildSequenceBuffer();
+ child.parent = this;
+ }
+
+ streamWriter.writeByte(type);
+ child.buffer.clear();
+
+ return child;
+ }
+
+
+
+ public void writeByte(byte b) throws IOException
+ {
+ streamWriter.writeByte(b);
+ }
+
+
+
+ public void writeByteArray(byte[] bs, int offset, int length)
+ throws IOException
+ {
+ streamWriter.writeByteArray(bs, offset, length);
+ }
+ }
+
+
+
+ private interface SequenceBuffer
+ {
+ public SequenceBuffer endSequence() throws IOException;
+
+
+
+ public SequenceBuffer startSequence(byte type) throws IOException;
+
+
+
+ public void writeByte(byte b) throws IOException;
+
+
+
+ public void writeByteArray(byte[] bs, int offset, int length)
+ throws IOException;
+ }
+
+
+
+ private static final int SUB_SEQUENCE_BUFFER_INIT_SIZE = 1024;
+
+ private StreamWriter streamWriter;
+
+ private SequenceBuffer sequenceBuffer;
+
+ private final RootSequenceBuffer rootBuffer;
+
+
+
+ /**
+ * Creates a new ASN.1 writer that writes to a StreamWriter.
+ */
+ public ASN1StreamWriter()
+ {
+ this.sequenceBuffer = this.rootBuffer = new RootSequenceBuffer();
+ }
+
+
+
+ /**
+ * Closes this ASN.1 writer and the underlying outputstream. Any
+ * unfinished sequences will be ended.
+ *
+ * @throws IOException
+ * if an error occurs while closing the stream.
+ */
+ public void close() throws IOException
+ {
+ streamWriter.close();
+ }
+
+
+
+ /**
+ * Flushes the stream.
+ *
+ * @throws IOException
+ * If an I/O error occurs
+ */
+ public void flush() throws IOException
+ {
+ streamWriter.flush();
+ }
+
+
+
+ public void prepare()
+ {
+ // nothing to do
+ }
+
+
+
+ public void release()
+ {
+ streamWriter = null;
+ sequenceBuffer = rootBuffer;
+ }
+
+
+
+ public void setStreamWriter(StreamWriter streamWriter)
+ {
+ this.streamWriter = streamWriter;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeBoolean(byte type, boolean booleanValue)
+ throws IOException
+ {
+ sequenceBuffer.writeByte(type);
+ writeLength(sequenceBuffer, 1);
+ sequenceBuffer.writeByte(booleanValue ? BOOLEAN_VALUE_TRUE
+ : BOOLEAN_VALUE_FALSE);
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 BOOLEAN(type=0x%x, length=%d, value=%s)", type,
+ 1, String.valueOf(booleanValue)));
+ }
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeEndSequence() throws IOException,
+ IllegalStateException
+ {
+ sequenceBuffer = sequenceBuffer.endSequence();
+
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeEndSet() throws IOException
+ {
+ return writeEndSequence();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeEnumerated(byte type, int intValue)
+ throws IOException
+ {
+ return writeInteger(type, intValue);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeInteger(byte type, int intValue)
+ throws IOException
+ {
+ sequenceBuffer.writeByte(type);
+ if (((intValue < 0) && ((intValue & 0xFFFFFF80) == 0xFFFFFF80))
+ || ((intValue & 0x0000007F) == intValue))
+ {
+ writeLength(sequenceBuffer, 1);
+ sequenceBuffer.writeByte((byte) (intValue & 0xFF));
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ type, 1, intValue));
+ }
+ }
+ else if (((intValue < 0) && ((intValue & 0xFFFF8000) == 0xFFFF8000))
+ || ((intValue & 0x00007FFF) == intValue))
+ {
+ writeLength(sequenceBuffer, 2);
+ sequenceBuffer.writeByte((byte) ((intValue >> 8) & 0xFF));
+ sequenceBuffer.writeByte((byte) (intValue & 0xFF));
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ type, 2, intValue));
+ }
+ }
+ else if (((intValue < 0) && ((intValue & 0xFF800000) == 0xFF800000))
+ || ((intValue & 0x007FFFFF) == intValue))
+ {
+ writeLength(sequenceBuffer, 3);
+ sequenceBuffer.writeByte((byte) ((intValue >> 16) & 0xFF));
+ sequenceBuffer.writeByte((byte) ((intValue >> 8) & 0xFF));
+ sequenceBuffer.writeByte((byte) (intValue & 0xFF));
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ type, 3, intValue));
+ }
+ }
+ else
+ {
+ writeLength(sequenceBuffer, 4);
+ sequenceBuffer.writeByte((byte) ((intValue >> 24) & 0xFF));
+ sequenceBuffer.writeByte((byte) ((intValue >> 16) & 0xFF));
+ sequenceBuffer.writeByte((byte) ((intValue >> 8) & 0xFF));
+ sequenceBuffer.writeByte((byte) (intValue & 0xFF));
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ type, 4, intValue));
+ }
+ }
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeInteger(byte type, long longValue)
+ throws IOException
+ {
+ sequenceBuffer.writeByte(type);
+ if (((longValue < 0) && ((longValue & 0xFFFFFFFFFFFFFF80L) == 0xFFFFFFFFFFFFFF80L))
+ || ((longValue & 0x000000000000007FL) == longValue))
+ {
+ writeLength(sequenceBuffer, 1);
+ sequenceBuffer.writeByte((byte) (longValue & 0xFF));
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ type, 1, longValue));
+ }
+ }
+ else if (((longValue < 0) && ((longValue & 0xFFFFFFFFFFFF8000L) == 0xFFFFFFFFFFFF8000L))
+ || ((longValue & 0x0000000000007FFFL) == longValue))
+ {
+ writeLength(sequenceBuffer, 2);
+ sequenceBuffer.writeByte((byte) ((longValue >> 8) & 0xFF));
+ sequenceBuffer.writeByte((byte) (longValue & 0xFF));
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ type, 2, longValue));
+ }
+ }
+ else if (((longValue < 0) && ((longValue & 0xFFFFFFFFFF800000L) == 0xFFFFFFFFFF800000L))
+ || ((longValue & 0x00000000007FFFFFL) == longValue))
+ {
+ writeLength(sequenceBuffer, 3);
+ sequenceBuffer.writeByte((byte) ((longValue >> 16) & 0xFF));
+ sequenceBuffer.writeByte((byte) ((longValue >> 8) & 0xFF));
+ sequenceBuffer.writeByte((byte) (longValue & 0xFF));
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ type, 3, longValue));
+ }
+ }
+ else if (((longValue < 0) && ((longValue & 0xFFFFFFFF80000000L) == 0xFFFFFFFF80000000L))
+ || ((longValue & 0x000000007FFFFFFFL) == longValue))
+ {
+ writeLength(sequenceBuffer, 4);
+ sequenceBuffer.writeByte((byte) ((longValue >> 24) & 0xFF));
+ sequenceBuffer.writeByte((byte) ((longValue >> 16) & 0xFF));
+ sequenceBuffer.writeByte((byte) ((longValue >> 8) & 0xFF));
+ sequenceBuffer.writeByte((byte) (longValue & 0xFF));
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ type, 4, longValue));
+ }
+ }
+ else if (((longValue < 0) && ((longValue & 0xFFFFFF8000000000L) == 0xFFFFFF8000000000L))
+ || ((longValue & 0x0000007FFFFFFFFFL) == longValue))
+ {
+ writeLength(sequenceBuffer, 5);
+ sequenceBuffer.writeByte((byte) ((longValue >> 32) & 0xFF));
+ sequenceBuffer.writeByte((byte) ((longValue >> 24) & 0xFF));
+ sequenceBuffer.writeByte((byte) ((longValue >> 16) & 0xFF));
+ sequenceBuffer.writeByte((byte) ((longValue >> 8) & 0xFF));
+ sequenceBuffer.writeByte((byte) (longValue & 0xFF));
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ type, 5, longValue));
+ }
+ }
+ else if (((longValue < 0) && ((longValue & 0xFFFF800000000000L) == 0xFFFF800000000000L))
+ || ((longValue & 0x00007FFFFFFFFFFFL) == longValue))
+ {
+ writeLength(sequenceBuffer, 6);
+ sequenceBuffer.writeByte((byte) ((longValue >> 40) & 0xFF));
+ sequenceBuffer.writeByte((byte) ((longValue >> 32) & 0xFF));
+ sequenceBuffer.writeByte((byte) ((longValue >> 24) & 0xFF));
+ sequenceBuffer.writeByte((byte) ((longValue >> 16) & 0xFF));
+ sequenceBuffer.writeByte((byte) ((longValue >> 8) & 0xFF));
+ sequenceBuffer.writeByte((byte) (longValue & 0xFF));
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ type, 6, longValue));
+ }
+ }
+ else if (((longValue < 0) && ((longValue & 0xFF80000000000000L) == 0xFF80000000000000L))
+ || ((longValue & 0x007FFFFFFFFFFFFFL) == longValue))
+ {
+ writeLength(sequenceBuffer, 7);
+ sequenceBuffer.writeByte((byte) ((longValue >> 48) & 0xFF));
+ sequenceBuffer.writeByte((byte) ((longValue >> 40) & 0xFF));
+ sequenceBuffer.writeByte((byte) ((longValue >> 32) & 0xFF));
+ sequenceBuffer.writeByte((byte) ((longValue >> 24) & 0xFF));
+ sequenceBuffer.writeByte((byte) ((longValue >> 16) & 0xFF));
+ sequenceBuffer.writeByte((byte) ((longValue >> 8) & 0xFF));
+ sequenceBuffer.writeByte((byte) (longValue & 0xFF));
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ type, 7, longValue));
+ }
+ }
+ else
+ {
+ writeLength(sequenceBuffer, 8);
+ sequenceBuffer.writeByte((byte) ((longValue >> 56) & 0xFF));
+ sequenceBuffer.writeByte((byte) ((longValue >> 48) & 0xFF));
+ sequenceBuffer.writeByte((byte) ((longValue >> 40) & 0xFF));
+ sequenceBuffer.writeByte((byte) ((longValue >> 32) & 0xFF));
+ sequenceBuffer.writeByte((byte) ((longValue >> 24) & 0xFF));
+ sequenceBuffer.writeByte((byte) ((longValue >> 16) & 0xFF));
+ sequenceBuffer.writeByte((byte) ((longValue >> 8) & 0xFF));
+ sequenceBuffer.writeByte((byte) (longValue & 0xFF));
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+ type, 8, longValue));
+ }
+ }
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeNull(byte type) throws IOException
+ {
+ sequenceBuffer.writeByte(type);
+ writeLength(sequenceBuffer, 0);
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 NULL(type=0x%x, length=%d)", type, 0));
+ }
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeOctetString(byte type, byte[] value,
+ int offset, int length) throws IOException
+ {
+ sequenceBuffer.writeByte(type);
+ writeLength(sequenceBuffer, length);
+ sequenceBuffer.writeByteArray(value, offset, length);
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String
+ .format("WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d)",
+ type, length));
+ }
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeOctetString(byte type, ByteSequence value)
+ throws IOException
+ {
+ sequenceBuffer.writeByte(type);
+ writeLength(sequenceBuffer, value.length());
+ // TODO: Is there a more efficient way to do this?
+ for (int i = 0; i < value.length(); i++)
+ {
+ sequenceBuffer.writeByte(value.byteAt(i));
+ }
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d)", type, value
+ .length()));
+ }
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeOctetString(byte type, String value)
+ throws IOException
+ {
+ sequenceBuffer.writeByte(type);
+
+ if (value == null)
+ {
+ writeLength(sequenceBuffer, 0);
+ return this;
+ }
+
+ byte[] bytes = StaticUtils.getBytes(value);
+ writeLength(sequenceBuffer, bytes.length);
+ sequenceBuffer.writeByteArray(bytes, 0, bytes.length);
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d, "
+ + "value=%s)", type, bytes.length, value));
+ }
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeStartSequence(byte type) throws IOException
+ {
+ // Get a child sequence buffer
+ sequenceBuffer = sequenceBuffer.startSequence(type);
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+ {
+ StaticUtils.DEBUG_LOG.finest(String.format(
+ "WRITE ASN.1 START SEQUENCE(type=0x%x)", type));
+ }
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ASN1Writer writeStartSet(byte type) throws IOException
+ {
+ // From an implementation point of view, a set is equivalent to a
+ // sequence.
+ return writeStartSequence(type);
+ }
+
+
+
+ /**
+ * Writes the provided value for use as the length of an ASN.1
+ * element.
+ *
+ * @param buffer
+ * The sequence buffer to write to.
+ * @param length
+ * The length to encode for use in an ASN.1 element.
+ * @throws IOException
+ * if an error occurs while writing.
+ */
+ private void writeLength(SequenceBuffer buffer, int length)
+ throws IOException
+ {
+ if (length < 128)
+ {
+ buffer.writeByte((byte) length);
+ }
+ else if ((length & 0x000000FF) == length)
+ {
+ buffer.writeByte((byte) 0x81);
+ buffer.writeByte((byte) (length & 0xFF));
+ }
+ else if ((length & 0x0000FFFF) == length)
+ {
+ buffer.writeByte((byte) 0x82);
+ buffer.writeByte((byte) ((length >> 8) & 0xFF));
+ buffer.writeByte((byte) (length & 0xFF));
+ }
+ else if ((length & 0x00FFFFFF) == length)
+ {
+ buffer.writeByte((byte) 0x83);
+ buffer.writeByte((byte) ((length >> 16) & 0xFF));
+ buffer.writeByte((byte) ((length >> 8) & 0xFF));
+ buffer.writeByte((byte) (length & 0xFF));
+ }
+ else
+ {
+ buffer.writeByte((byte) 0x84);
+ buffer.writeByte((byte) ((length >> 24) & 0xFF));
+ buffer.writeByte((byte) ((length >> 16) & 0xFF));
+ buffer.writeByte((byte) ((length >> 8) & 0xFF));
+ buffer.writeByte((byte) (length & 0xFF));
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/AbstractLDAPMessageHandler.java b/sdk/src/org/opends/sdk/ldap/AbstractLDAPMessageHandler.java
new file mode 100644
index 0000000..925edbf
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/AbstractLDAPMessageHandler.java
@@ -0,0 +1,258 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.controls.GenericControl;
+import org.opends.sdk.requests.*;
+import org.opends.sdk.responses.*;
+import org.opends.sdk.sasl.SASLBindRequest;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * Abstract LDAP message handler.
+ */
+abstract class AbstractLDAPMessageHandler implements LDAPMessageHandler
+{
+ public void handleUnrecognizedMessage(int messageID, byte messageTag,
+ ByteString messageBytes) throws UnsupportedMessageException
+ {
+ throw new UnsupportedMessageException(messageID, messageTag,
+ messageBytes);
+ }
+
+
+
+ public void handleAbandonRequest(int messageID, AbandonRequest request)
+ throws UnexpectedRequestException
+ {
+ throw new UnexpectedRequestException(messageID, request);
+ }
+
+
+
+ public void handleAddRequest(int messageID, AddRequest request)
+ throws UnexpectedRequestException
+ {
+ throw new UnexpectedRequestException(messageID, request);
+ }
+
+
+
+ public void handleCompareRequest(int messageID, CompareRequest request)
+ throws UnexpectedRequestException
+ {
+ throw new UnexpectedRequestException(messageID, request);
+ }
+
+
+
+ public void handleDeleteRequest(int messageID, DeleteRequest request)
+ throws UnexpectedRequestException
+ {
+ throw new UnexpectedRequestException(messageID, request);
+ }
+
+
+
+ public void handleExtendedRequest(int messageID,
+ GenericExtendedRequest request) throws UnexpectedRequestException
+ {
+ throw new UnexpectedRequestException(messageID, request);
+ }
+
+
+
+ public void handleBindRequest(int messageID, int version,
+ GenericBindRequest request) throws UnexpectedRequestException
+ {
+ throw new UnexpectedRequestException(messageID, request);
+ }
+
+
+
+ public void handleBindRequest(int messageID, int version,
+ SASLBindRequest<?> request) throws UnexpectedRequestException
+ {
+ throw new UnexpectedRequestException(messageID, request);
+ }
+
+
+
+ public void handleBindRequest(int messageID, int version,
+ SimpleBindRequest request) throws UnexpectedRequestException
+ {
+ throw new UnexpectedRequestException(messageID, request);
+ }
+
+
+
+ public void handleModifyDNRequest(int messageID,
+ ModifyDNRequest request) throws UnexpectedRequestException
+ {
+ throw new UnexpectedRequestException(messageID, request);
+ }
+
+
+
+ public void handleModifyRequest(int messageID, ModifyRequest request)
+ throws UnexpectedRequestException
+ {
+ throw new UnexpectedRequestException(messageID, request);
+ }
+
+
+
+ public void handleSearchRequest(int messageID, SearchRequest request)
+ throws UnexpectedRequestException
+ {
+ throw new UnexpectedRequestException(messageID, request);
+ }
+
+
+
+ public void handleUnbindRequest(int messageID, UnbindRequest request)
+ throws UnexpectedRequestException
+ {
+ throw new UnexpectedRequestException(messageID, request);
+ }
+
+
+
+ public void handleAddResult(int messageID, Result result)
+ throws UnexpectedResponseException
+ {
+ throw new UnexpectedResponseException(messageID, result);
+ }
+
+
+
+ public void handleBindResult(int messageID, BindResult result)
+ throws UnexpectedResponseException
+ {
+ throw new UnexpectedResponseException(messageID, result);
+ }
+
+
+
+ public void handleCompareResult(int messageID, CompareResult result)
+ throws UnexpectedResponseException
+ {
+ throw new UnexpectedResponseException(messageID, result);
+ }
+
+
+
+ public void handleDeleteResult(int messageID, Result result)
+ throws UnexpectedResponseException
+ {
+ throw new UnexpectedResponseException(messageID, result);
+ }
+
+
+
+ public void handleExtendedResult(int messageID,
+ GenericExtendedResult result) throws UnexpectedResponseException
+ {
+ throw new UnexpectedResponseException(messageID, result);
+ }
+
+
+
+ public void handleIntermediateResponse(int messageID,
+ GenericIntermediateResponse response)
+ throws UnexpectedResponseException
+ {
+ throw new UnexpectedResponseException(messageID, response);
+ }
+
+
+
+ public void handleModifyDNResult(int messageID, Result result)
+ throws UnexpectedResponseException
+ {
+ throw new UnexpectedResponseException(messageID, result);
+ }
+
+
+
+ public void handleModifyResult(int messageID, Result result)
+ throws UnexpectedResponseException
+ {
+ throw new UnexpectedResponseException(messageID, result);
+ }
+
+
+
+ public void handleSearchResult(int messageID, Result result)
+ throws UnexpectedResponseException
+ {
+ throw new UnexpectedResponseException(messageID, result);
+ }
+
+
+
+ public void handleSearchResultEntry(int messageID,
+ SearchResultEntry entry) throws UnexpectedResponseException
+ {
+ throw new UnexpectedResponseException(messageID, entry);
+ }
+
+
+
+ public void handleSearchResultReference(int messageID,
+ SearchResultReference reference)
+ throws UnexpectedResponseException
+ {
+ throw new UnexpectedResponseException(messageID, reference);
+ }
+
+
+
+ public Control decodeResponseControl(int messageID, String oid,
+ boolean isCritical, ByteString value, Schema schema)
+ throws DecodeException
+ {
+ return new GenericControl(oid, isCritical, value);
+ }
+
+
+
+ public Control decodeRequestControl(int messageID, String oid,
+ boolean isCritical, ByteString value, Schema schema)
+ throws DecodeException
+ {
+ return new GenericControl(oid, isCritical, value);
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/AbstractLDAPTransport.java b/sdk/src/org/opends/sdk/ldap/AbstractLDAPTransport.java
new file mode 100644
index 0000000..91c77fd
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/AbstractLDAPTransport.java
@@ -0,0 +1,244 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.io.IOException;
+
+import com.sun.grizzly.Connection;
+import com.sun.grizzly.filterchain.*;
+import com.sun.grizzly.streams.StreamReader;
+import com.sun.grizzly.streams.StreamWriter;
+import com.sun.grizzly.utils.ConcurrentQueuePool;
+
+
+
+/**
+ * Abstract LDAP transport.
+ */
+abstract class AbstractLDAPTransport
+{
+ private class ASN1ReaderPool extends
+ ConcurrentQueuePool<ASN1StreamReader>
+ {
+ @Override
+ public ASN1StreamReader newInstance()
+ {
+ return new ASN1StreamReader(maxASN1ElementSize);
+ }
+ }
+
+
+
+ private class ASN1WriterPool extends
+ ConcurrentQueuePool<ASN1StreamWriter>
+ {
+ @Override
+ public ASN1StreamWriter newInstance()
+ {
+ return new ASN1StreamWriter();
+ }
+ }
+
+
+
+ private class DefaultFilterChainFactory implements
+ PatternFilterChainFactory
+ {
+ private FilterChain defaultFilterChain;
+
+
+
+ private DefaultFilterChainFactory()
+ {
+ this.defaultFilterChain = new DefaultFilterChain(this);
+ this.defaultFilterChain.add(new MonitorFilter());
+ this.defaultFilterChain.add(new TransportFilter());
+ this.defaultFilterChain.add(new LDAPFilter());
+ }
+
+
+
+ public FilterChain create()
+ {
+ FilterChain filterChain = new DefaultFilterChain(this);
+ filterChain.addAll(defaultFilterChain);
+ return filterChain;
+ }
+
+
+
+ public FilterChain getFilterChainPattern()
+ {
+ return defaultFilterChain;
+ }
+
+
+
+ public void release(FilterChain chain)
+ {
+ // TODO: Nothing yet.
+ }
+
+
+
+ public void setFilterChainPattern(FilterChain chain)
+ {
+ defaultFilterChain = chain;
+ }
+ }
+
+
+
+ private class LDAPFilter extends FilterAdapter
+ {
+ @Override
+ public NextAction handleRead(FilterChainContext ctx,
+ NextAction nextAction) throws IOException
+ {
+ Connection<?> connection = ctx.getConnection();
+ StreamReader streamReader = ctx.getStreamReader();
+ LDAPMessageHandler handler = getMessageHandler(connection);
+ ASN1StreamReader asn1Reader = getASN1Reader(streamReader);
+
+ try
+ {
+ do
+ {
+ LDAPDecoder.decode(asn1Reader, handler);
+ }
+ while (asn1Reader.hasNextElement());
+ }
+ finally
+ {
+ releaseASN1Reader(asn1Reader);
+ }
+
+ return nextAction;
+ }
+ }
+
+
+
+ private class MonitorFilter extends FilterAdapter
+ {
+ @Override
+ public void exceptionOccurred(FilterChainContext ctx,
+ Throwable error)
+ {
+ Connection<?> connection = ctx.getConnection();
+ if (!connection.isOpen())
+ {
+ // Grizzly doens't not deregister the read interest from the
+ // selector so closing the connection results in an
+ // EOFException.
+ // Just ignore errors on closed connections.
+ return;
+ }
+ LDAPMessageHandler handler = getMessageHandler(connection);
+ handler.handleException(error);
+ }
+
+
+
+ @Override
+ public NextAction handleClose(FilterChainContext ctx,
+ NextAction nextAction) throws IOException
+ {
+ Connection<?> connection = ctx.getConnection();
+ removeMessageHandler(connection);
+ return nextAction;
+ }
+ }
+
+ private final PatternFilterChainFactory defaultFilterChainFactory;
+
+ private final int maxASN1ElementSize = 0;
+
+ private final ASN1ReaderPool asn1ReaderPool;
+
+ private final ASN1WriterPool asn1WriterPool;
+
+
+
+ AbstractLDAPTransport()
+ {
+ this.defaultFilterChainFactory = new DefaultFilterChainFactory();
+
+ this.asn1ReaderPool = new ASN1ReaderPool();
+ this.asn1WriterPool = new ASN1WriterPool();
+ }
+
+
+
+ ASN1StreamWriter getASN1Writer(StreamWriter streamWriter)
+ {
+ ASN1StreamWriter asn1Writer = asn1WriterPool.poll();
+ asn1Writer.setStreamWriter(streamWriter);
+ return asn1Writer;
+ }
+
+
+
+ PatternFilterChainFactory getDefaultFilterChainFactory()
+ {
+ return defaultFilterChainFactory;
+ }
+
+
+
+ void releaseASN1Writer(ASN1StreamWriter asn1Writer)
+ {
+ asn1WriterPool.offer(asn1Writer);
+ }
+
+
+
+ abstract LDAPMessageHandler getMessageHandler(Connection<?> connection);
+
+
+
+ abstract void removeMessageHandler(Connection<?> connection);
+
+
+
+ private ASN1StreamReader getASN1Reader(StreamReader streamReader)
+ {
+ ASN1StreamReader asn1Reader = asn1ReaderPool.poll();
+ asn1Reader.setStreamReader(streamReader);
+ return asn1Reader;
+ }
+
+
+
+ private void releaseASN1Reader(ASN1StreamReader asn1Reader)
+ {
+ asn1ReaderPool.offer(asn1Reader);
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/AbstractResultFutureImpl.java b/sdk/src/org/opends/sdk/ldap/AbstractResultFutureImpl.java
new file mode 100644
index 0000000..6bd5050
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/AbstractResultFutureImpl.java
@@ -0,0 +1,259 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.util.concurrent.*;
+import java.util.logging.Level;
+
+import org.opends.sdk.ErrorResultException;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.ResultFuture;
+import org.opends.sdk.ResultHandler;
+import org.opends.sdk.requests.Requests;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * Abstract result future implementation.
+ */
+abstract class AbstractResultFutureImpl<R extends Result, P> implements
+ ResultFuture<R>, Runnable
+{
+ private final LDAPConnection connection;
+
+ private final ResultHandler<? super R, P> handler;
+
+ private final ExecutorService handlerExecutor;
+
+ private final int messageID;
+
+ private final Semaphore invokerLock;
+
+ private final CountDownLatch latch = new CountDownLatch(1);
+
+ private final P p;
+
+ private volatile boolean isCancelled = false;
+
+ private volatile R result = null;
+
+
+
+ AbstractResultFutureImpl(int messageID,
+ ResultHandler<? super R, P> handler, P p,
+ LDAPConnection connection, ExecutorService handlerExecutor)
+ {
+ this.messageID = messageID;
+ this.handler = handler;
+ this.p = p;
+ this.connection = connection;
+ this.handlerExecutor = handlerExecutor;
+ if (handlerExecutor == null)
+ {
+ invokerLock = null;
+ }
+ else
+ {
+ invokerLock = new Semaphore(1);
+ }
+ }
+
+
+
+ public synchronized boolean cancel(boolean b)
+ {
+ if (!isDone())
+ {
+ isCancelled = true;
+ connection.abandon(Requests.newAbandonRequest(messageID));
+ latch.countDown();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+
+ public R get() throws InterruptedException, ErrorResultException
+ {
+ latch.await();
+ return get0();
+ }
+
+
+
+ public R get(long timeout, TimeUnit unit)
+ throws InterruptedException, TimeoutException,
+ ErrorResultException
+ {
+ if (!latch.await(timeout, unit))
+ {
+ throw new TimeoutException();
+ }
+ return get0();
+ }
+
+
+
+ public int getMessageID()
+ {
+ return messageID;
+ }
+
+
+
+ public boolean isCancelled()
+ {
+ return isCancelled;
+ }
+
+
+
+ public boolean isDone()
+ {
+ return latch.getCount() == 0;
+ }
+
+
+
+ public void run()
+ {
+ if (result.getResultCode().isExceptional())
+ {
+ ErrorResultException e = ErrorResultException.wrap(result);
+ handler.handleErrorResult(p, e);
+ }
+ else
+ {
+ handler.handleResult(p, result);
+ }
+ }
+
+
+
+ synchronized void handleErrorResult(Result result)
+ {
+ R errorResult = newErrorResult(result.getResultCode(), result
+ .getDiagnosticMessage(), result.getCause());
+ handleResult(errorResult);
+ }
+
+
+
+ abstract R newErrorResult(ResultCode resultCode,
+ String diagnosticMessage, Throwable cause);
+
+
+
+ void handleResult(R result)
+ {
+ if (!isDone())
+ {
+ this.result = result;
+ if (handler != null)
+ {
+ invokeHandler(this);
+ }
+ latch.countDown();
+ }
+ }
+
+
+
+ protected void invokeHandler(final Runnable runnable)
+ {
+ try
+ {
+ if (handlerExecutor == null)
+ {
+ runnable.run();
+ }
+ else
+ {
+ invokerLock.acquire();
+
+ try
+ {
+ handlerExecutor.submit(new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ runnable.run();
+ }
+ finally
+ {
+ invokerLock.release();
+ }
+ }
+ });
+ }
+ catch (Exception e)
+ {
+ invokerLock.release();
+ }
+ }
+ }
+ catch (InterruptedException e)
+ {
+ // Thread has been interrupted so give up.
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ StaticUtils.DEBUG_LOG.warning(String.format(
+ "Invoke thread interrupted: %s", StaticUtils
+ .getExceptionMessage(e)));
+ }
+ }
+ }
+
+
+
+ private R get0() throws CancellationException, ErrorResultException
+ {
+ if (isCancelled())
+ {
+ throw new CancellationException();
+ }
+ else if (result.getResultCode().isExceptional())
+ {
+ throw ErrorResultException.wrap(result);
+ }
+ else
+ {
+ return result;
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/BindResultFutureImpl.java b/sdk/src/org/opends/sdk/ldap/BindResultFutureImpl.java
new file mode 100644
index 0000000..1a5c0c3
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/BindResultFutureImpl.java
@@ -0,0 +1,98 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.util.concurrent.ExecutorService;
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.ResultFuture;
+import org.opends.sdk.ResultHandler;
+import org.opends.sdk.requests.BindRequest;
+import org.opends.sdk.responses.BindResult;
+import org.opends.sdk.responses.Responses;
+import org.opends.sdk.sasl.SASLContext;
+
+
+
+/**
+ * Bind result future implementation.
+ */
+final class BindResultFutureImpl<P> extends
+ AbstractResultFutureImpl<BindResult, P> implements
+ ResultFuture<BindResult>
+{
+ private final BindRequest request;
+
+ private SASLContext saslContext;
+
+
+
+ BindResultFutureImpl(int messageID, BindRequest request,
+ ResultHandler<? super BindResult, P> handler, P p,
+ LDAPConnection connection, ExecutorService handlerExecutor)
+ {
+ super(messageID, handler, p, connection, handlerExecutor);
+ this.request = request;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ BindResult newErrorResult(ResultCode resultCode,
+ String diagnosticMessage, Throwable cause)
+ {
+ return Responses.newBindResult(resultCode).setDiagnosticMessage(
+ diagnosticMessage).setCause(cause);
+ }
+
+
+
+ BindRequest getRequest()
+ {
+ return request;
+ }
+
+
+
+ void setSASLContext(SASLContext saslContext)
+ {
+ this.saslContext = saslContext;
+ }
+
+
+
+ SASLContext getSASLContext()
+ {
+ return saslContext;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/CompareResultFutureImpl.java b/sdk/src/org/opends/sdk/ldap/CompareResultFutureImpl.java
new file mode 100644
index 0000000..b9704ea
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/CompareResultFutureImpl.java
@@ -0,0 +1,80 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.util.concurrent.ExecutorService;
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.ResultFuture;
+import org.opends.sdk.ResultHandler;
+import org.opends.sdk.requests.CompareRequest;
+import org.opends.sdk.responses.CompareResult;
+import org.opends.sdk.responses.Responses;
+
+
+
+/**
+ * Compare result future implementation.
+ */
+final class CompareResultFutureImpl<P> extends
+ AbstractResultFutureImpl<CompareResult, P> implements
+ ResultFuture<CompareResult>
+{
+ private final CompareRequest request;
+
+
+
+ CompareResultFutureImpl(int messageID, CompareRequest request,
+ ResultHandler<? super CompareResult, P> handler, P p,
+ LDAPConnection connection, ExecutorService handlerExecutor)
+ {
+ super(messageID, handler, p, connection, handlerExecutor);
+ this.request = request;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ CompareResult newErrorResult(ResultCode resultCode,
+ String diagnosticMessage, Throwable cause)
+ {
+ return Responses.newCompareResult(resultCode).setDiagnosticMessage(
+ diagnosticMessage).setCause(cause);
+ }
+
+
+
+ CompareRequest getRequest()
+ {
+ return request;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/ExtendedResultFutureImpl.java b/sdk/src/org/opends/sdk/ldap/ExtendedResultFutureImpl.java
new file mode 100644
index 0000000..6df167b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/ExtendedResultFutureImpl.java
@@ -0,0 +1,90 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.util.concurrent.ExecutorService;
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.ResultFuture;
+import org.opends.sdk.ResultHandler;
+import org.opends.sdk.requests.ExtendedRequest;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * Extended result future implementation.
+ */
+final class ExtendedResultFutureImpl<R extends Result, P> extends
+ AbstractResultFutureImpl<R, P> implements ResultFuture<R>
+{
+ private final ExtendedRequest<R> request;
+
+
+
+ ExtendedResultFutureImpl(int messageID, ExtendedRequest<R> request,
+ ResultHandler<? super R, P> handler, P p,
+ LDAPConnection connection, ExecutorService handlerExecutor)
+ {
+ super(messageID, handler, p, connection, handlerExecutor);
+ this.request = request;
+ }
+
+
+
+ R decodeResponse(ResultCode resultCode, String matchedDN,
+ String diagnosticMessage, String responseName,
+ ByteString responseValue) throws DecodeException
+ {
+ return request.getExtendedOperation().decodeResponse(resultCode,
+ matchedDN, diagnosticMessage, responseName, responseValue);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ R newErrorResult(ResultCode resultCode, String diagnosticMessage,
+ Throwable cause)
+ {
+ return request.getExtendedOperation().decodeResponse(resultCode,
+ "", diagnosticMessage);
+ }
+
+
+
+ ExtendedRequest<R> getRequest()
+ {
+ return request;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/LDAPConnection.java b/sdk/src/org/opends/sdk/ldap/LDAPConnection.java
new file mode 100644
index 0000000..b13e923
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/LDAPConnection.java
@@ -0,0 +1,1676 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import static org.opends.sdk.ldap.LDAPConstants.OID_NOTICE_OF_DISCONNECTION;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.net.ssl.SSLContext;
+import javax.security.sasl.SaslException;
+
+import org.opends.sdk.*;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.controls.ControlDecoder;
+import org.opends.sdk.extensions.StartTLSRequest;
+import org.opends.sdk.requests.*;
+import org.opends.sdk.responses.*;
+import org.opends.sdk.sasl.SASLBindRequest;
+import org.opends.sdk.sasl.SASLContext;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+import com.sun.grizzly.filterchain.Filter;
+import com.sun.grizzly.filterchain.FilterChain;
+import com.sun.grizzly.filterchain.StreamTransformerFilter;
+import com.sun.grizzly.ssl.*;
+import com.sun.grizzly.streams.StreamWriter;
+
+
+
+/**
+ * LDAP connection implementation.
+ * <p>
+ * TODO: handle illegal state exceptions.
+ */
+final class LDAPConnection implements AsynchronousConnection
+{
+
+ private final class LDAPMessageHandlerImpl extends
+ AbstractLDAPMessageHandler
+ {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void handleAddResult(int messageID, Result result)
+ {
+ AbstractResultFutureImpl<?, ?> pendingRequest = pendingRequests
+ .remove(messageID);
+ if (pendingRequest != null)
+ {
+ if (pendingRequest instanceof ResultFutureImpl<?>)
+ {
+ ResultFutureImpl<?> future = (ResultFutureImpl<?>) pendingRequest;
+ if (future.getRequest() instanceof AddRequest)
+ {
+ future.handleResult(result);
+ return;
+ }
+ }
+ handleIncorrectResponse(pendingRequest);
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void handleBindResult(int messageID, BindResult result)
+ {
+ AbstractResultFutureImpl<?, ?> pendingRequest = pendingRequests
+ .remove(messageID);
+ if (pendingRequest != null)
+ {
+ if (pendingRequest instanceof BindResultFutureImpl<?>)
+ {
+ BindResultFutureImpl<?> future = ((BindResultFutureImpl<?>) pendingRequest);
+ BindRequest request = future.getRequest();
+
+ if (request instanceof SASLBindRequest<?>)
+ {
+ SASLBindRequest<?> saslBind = (SASLBindRequest<?>) request;
+ SASLContext saslContext = future.getSASLContext();
+
+ if ((result.getResultCode() == ResultCode.SUCCESS || result
+ .getResultCode() == ResultCode.SASL_BIND_IN_PROGRESS)
+ && !saslContext.isComplete())
+ {
+ try
+ {
+ saslContext.evaluateCredentials(result
+ .getServerSASLCredentials());
+ }
+ catch (SaslException se)
+ {
+ pendingBindOrStartTLS = -1;
+
+ Result errorResult = adaptException(se);
+ future.handleErrorResult(errorResult);
+ return;
+ }
+ }
+
+ if (result.getResultCode() == ResultCode.SASL_BIND_IN_PROGRESS)
+ {
+ // The server is expecting a multi stage bind response.
+ messageID = nextMsgID.getAndIncrement();
+ ASN1StreamWriter asn1Writer = connFactory
+ .getASN1Writer(streamWriter);
+
+ try
+ {
+ synchronized (writeLock)
+ {
+ pendingRequests.put(messageID, future);
+ try
+ {
+ LDAPEncoder.encodeBindRequest(asn1Writer,
+ messageID, 3, saslBind, saslContext
+ .getSASLCredentials());
+ asn1Writer.flush();
+ }
+ catch (IOException e)
+ {
+ pendingRequests.remove(messageID);
+
+ Result errorResult = adaptException(e);
+ connectionErrorOccurred(errorResult);
+ future.handleErrorResult(errorResult);
+ }
+ }
+ }
+ finally
+ {
+ connFactory.releaseASN1Writer(asn1Writer);
+ }
+ return;
+ }
+
+ if ((result.getResultCode() == ResultCode.SUCCESS)
+ && saslContext.isSecure())
+ {
+ // The connection needs to be secured by the SASL
+ // mechanism.
+ installFilter(SASLFilter.getInstance(saslContext,
+ connection));
+ }
+ }
+ pendingBindOrStartTLS = -1;
+ future.handleResult(result);
+ }
+ else
+ {
+ handleIncorrectResponse(pendingRequest);
+ }
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void handleCompareResult(int messageID, CompareResult result)
+ {
+ AbstractResultFutureImpl<?, ?> pendingRequest = pendingRequests
+ .remove(messageID);
+ if (pendingRequest != null)
+ {
+ if (pendingRequest instanceof CompareResultFutureImpl<?>)
+ {
+ CompareResultFutureImpl<?> future = (CompareResultFutureImpl<?>) pendingRequest;
+ future.handleResult(result);
+ }
+ else
+ {
+ handleIncorrectResponse(pendingRequest);
+ }
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void handleDeleteResult(int messageID, Result result)
+ {
+ AbstractResultFutureImpl<?, ?> pendingRequest = pendingRequests
+ .remove(messageID);
+ if (pendingRequest != null)
+ {
+ if (pendingRequest instanceof ResultFutureImpl<?>)
+ {
+ ResultFutureImpl<?> future = (ResultFutureImpl<?>) pendingRequest;
+ if (future.getRequest() instanceof DeleteRequest)
+ {
+ future.handleResult(result);
+ return;
+ }
+ }
+ handleIncorrectResponse(pendingRequest);
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void handleException(Throwable throwable)
+ {
+ Result errorResult = adaptException(throwable);
+ connectionErrorOccurred(errorResult);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void handleExtendedResult(int messageID,
+ GenericExtendedResult result)
+ {
+ if (messageID == 0)
+ {
+ if ((result.getResponseName() != null)
+ && result.getResponseName().equals(
+ OID_NOTICE_OF_DISCONNECTION))
+ {
+
+ Result errorResult = Responses.newResult(result.getResultCode())
+ .setDiagnosticMessage(result.getDiagnosticMessage());
+ close(null, true, errorResult);
+ return;
+ }
+ else
+ {
+ // Unsolicited notification received.
+ synchronized (writeLock)
+ {
+ if (isClosed)
+ {
+ // Don't notify after connection is closed.
+ return;
+ }
+
+ for (ConnectionEventListener listener : listeners)
+ {
+ listener
+ .connectionReceivedUnsolicitedNotification(result);
+ }
+ }
+ }
+ }
+
+ AbstractResultFutureImpl<?, ?> pendingRequest = pendingRequests
+ .remove(messageID);
+
+ if (pendingRequest instanceof ExtendedResultFutureImpl<?, ?>)
+ {
+ ExtendedResultFutureImpl<?, ?> extendedFuture = ((ExtendedResultFutureImpl<?, ?>) pendingRequest);
+ try
+ {
+ handleExtendedResult0(extendedFuture, result);
+ }
+ catch (DecodeException de)
+ {
+ // FIXME: should the connection be closed as well?
+ Result errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_DECODING_ERROR)
+ .setDiagnosticMessage(de.getLocalizedMessage()).setCause(
+ de);
+ extendedFuture.handleErrorResult(errorResult);
+ }
+ }
+ else
+ {
+ handleIncorrectResponse(pendingRequest);
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void handleIntermediateResponse(int messageID,
+ GenericIntermediateResponse response)
+ {
+ AbstractResultFutureImpl<?, ?> pendingRequest = pendingRequests
+ .remove(messageID);
+ if (pendingRequest != null)
+ {
+ handleIncorrectResponse(pendingRequest);
+
+ // FIXME: intermediate responses can occur for all operations.
+
+ // if (pendingRequest instanceof ExtendedResultFutureImpl)
+ // {
+ // ExtendedResultFutureImpl extendedFuture =
+ // ((ExtendedResultFutureImpl) pendingRequest);
+ // ExtendedRequest request = extendedFuture.getRequest();
+ //
+ // try
+ // {
+ // IntermediateResponse decodedResponse =
+ // request.getExtendedOperation()
+ // .decodeIntermediateResponse(
+ // response.getResponseName(),
+ // response.getResponseValue());
+ // extendedFuture.handleIntermediateResponse(decodedResponse);
+ // }
+ // catch (DecodeException de)
+ // {
+ // pendingRequest.failure(de);
+ // }
+ // }
+ // else
+ // {
+ // handleIncorrectResponse(pendingRequest);
+ // }
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void handleModifyDNResult(int messageID, Result result)
+ {
+ AbstractResultFutureImpl<?, ?> pendingRequest = pendingRequests
+ .remove(messageID);
+ if (pendingRequest != null)
+ {
+ if (pendingRequest instanceof ResultFutureImpl<?>)
+ {
+ ResultFutureImpl<?> future = (ResultFutureImpl<?>) pendingRequest;
+ if (future.getRequest() instanceof ModifyDNRequest)
+ {
+ future.handleResult(result);
+ return;
+ }
+ }
+ handleIncorrectResponse(pendingRequest);
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void handleModifyResult(int messageID, Result result)
+ {
+ AbstractResultFutureImpl<?, ?> pendingRequest = pendingRequests
+ .remove(messageID);
+ if (pendingRequest != null)
+ {
+ if (pendingRequest instanceof ResultFutureImpl<?>)
+ {
+ ResultFutureImpl<?> future = (ResultFutureImpl<?>) pendingRequest;
+ if (future.getRequest() instanceof ModifyRequest)
+ {
+ future.handleResult(result);
+ return;
+ }
+ }
+ handleIncorrectResponse(pendingRequest);
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void handleSearchResult(int messageID, Result result)
+ {
+ AbstractResultFutureImpl<?, ?> pendingRequest = pendingRequests
+ .remove(messageID);
+ if (pendingRequest != null)
+ {
+ if (pendingRequest instanceof SearchResultFutureImpl<?>)
+ {
+ ((SearchResultFutureImpl<?>) pendingRequest)
+ .handleResult(result);
+ }
+ else
+ {
+ handleIncorrectResponse(pendingRequest);
+ }
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void handleSearchResultEntry(int messageID,
+ SearchResultEntry entry)
+ {
+ AbstractResultFutureImpl<?, ?> pendingRequest = pendingRequests
+ .get(messageID);
+ if (pendingRequest != null)
+ {
+ if (pendingRequest instanceof SearchResultFutureImpl<?>)
+ {
+ ((SearchResultFutureImpl<?>) pendingRequest)
+ .handleSearchResultEntry(entry);
+ }
+ else
+ {
+ handleIncorrectResponse(pendingRequest);
+ }
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void handleSearchResultReference(int messageID,
+ SearchResultReference reference)
+ {
+ AbstractResultFutureImpl<?, ?> pendingRequest = pendingRequests
+ .get(messageID);
+ if (pendingRequest != null)
+ {
+ if (pendingRequest instanceof SearchResultFutureImpl<?>)
+ {
+ ((SearchResultFutureImpl<?>) pendingRequest)
+ .handleSearchResultReference(reference);
+ }
+ else
+ {
+ handleIncorrectResponse(pendingRequest);
+ }
+ }
+ }
+
+
+
+ @Override
+ public Control decodeResponseControl(int messageID, String oid,
+ boolean isCritical, ByteString value, Schema schema)
+ throws DecodeException
+ {
+ ControlDecoder<?> decoder = connFactory.getControlDecoder(oid);
+ if (decoder != null)
+ {
+ return decoder.decode(isCritical, value, schema);
+ }
+ return super.decodeResponseControl(messageID, oid, isCritical,
+ value, schema);
+ }
+
+
+
+ public ResolvedSchema resolveSchema(String dn)
+ throws DecodeException
+ {
+ DN initialDN;
+
+ try
+ {
+ initialDN = DN.valueOf(dn, schema);
+ }
+ catch (LocalizedIllegalArgumentException e)
+ {
+ throw DecodeException.error(e.getMessageObject());
+ }
+
+ return new ResolvedSchemaImpl(schema, initialDN);
+ }
+
+
+
+ public Schema getDefaultSchema()
+ {
+ return schema;
+ }
+ }
+
+
+
+ private static final class ResolvedSchemaImpl implements
+ ResolvedSchema
+ {
+ private final DN initialDN;
+
+ private final Schema schema;
+
+
+
+ private ResolvedSchemaImpl(Schema schema, DN initialDN)
+ {
+ this.schema = schema;
+ this.initialDN = initialDN;
+ }
+
+
+
+ public AttributeDescription decodeAttributeDescription(
+ String attributeDescription) throws DecodeException
+ {
+ try
+ {
+ return AttributeDescription.valueOf(attributeDescription,
+ schema);
+ }
+ catch (LocalizedIllegalArgumentException e)
+ {
+ throw DecodeException.error(e.getMessageObject());
+ }
+ }
+
+
+
+ public DN decodeDN(String dn) throws DecodeException
+ {
+ try
+ {
+ return DN.valueOf(dn, schema);
+ }
+ catch (LocalizedIllegalArgumentException e)
+ {
+ throw DecodeException.error(e.getMessageObject());
+ }
+ }
+
+
+
+ public RDN decodeRDN(String rdn) throws DecodeException
+ {
+ try
+ {
+ return RDN.valueOf(rdn, schema);
+ }
+ catch (LocalizedIllegalArgumentException e)
+ {
+ throw DecodeException.error(e.getMessageObject());
+ }
+ }
+
+
+
+ public DN getInitialDN()
+ {
+ return initialDN;
+ }
+
+
+
+ public Schema getSchema()
+ {
+ return schema;
+ }
+
+ }
+
+
+
+ private final Schema schema;
+
+ private final com.sun.grizzly.Connection<?> connection;
+
+ private Result connectionInvalidReason;
+
+ private final LDAPConnectionFactoryImpl connFactory;
+
+ private FilterChain customFilterChain;
+
+ private final LDAPMessageHandler handler = new LDAPMessageHandlerImpl();
+
+ private boolean isClosed = false;
+
+ private final List<ConnectionEventListener> listeners = new LinkedList<ConnectionEventListener>();
+
+ private final AtomicInteger nextMsgID = new AtomicInteger(1);
+
+ private volatile int pendingBindOrStartTLS = -1;
+
+ private final ConcurrentHashMap<Integer, AbstractResultFutureImpl<?, ?>> pendingRequests = new ConcurrentHashMap<Integer, AbstractResultFutureImpl<?, ?>>();
+
+ private final InetSocketAddress serverAddress;
+
+ private StreamWriter streamWriter;
+
+ private final Object writeLock = new Object();
+
+
+
+ /**
+ * Creates a new LDAP connection.
+ *
+ * @param connection
+ * The Grizzly connection.
+ * @param serverAddress
+ * The address of the server.
+ * @param schema
+ * The schema which will be used to decode responses from the
+ * server.
+ * @param connFactory
+ * The associated connection factory.
+ */
+ LDAPConnection(com.sun.grizzly.Connection<?> connection,
+ InetSocketAddress serverAddress, Schema schema,
+ LDAPConnectionFactoryImpl connFactory)
+ {
+ this.connection = connection;
+ this.serverAddress = serverAddress;
+ this.schema = schema;
+ this.connFactory = connFactory;
+ this.streamWriter = getFilterChainStreamWriter();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void abandon(AbandonRequest request)
+ {
+ AbstractResultFutureImpl<?, ?> pendingRequest = pendingRequests
+ .remove(request.getMessageID());
+ if (pendingRequest != null)
+ {
+ pendingRequest.cancel(false);
+ int messageID = nextMsgID.getAndIncrement();
+ ASN1StreamWriter asn1Writer = connFactory
+ .getASN1Writer(streamWriter);
+
+ try
+ {
+ synchronized (writeLock)
+ {
+ if (connectionInvalidReason != null)
+ {
+ return;
+ }
+ if (pendingBindOrStartTLS > 0)
+ {
+ // This is not allowed. We will just ignore this
+ // abandon request.
+ }
+ try
+ {
+ LDAPEncoder.encodeAbandonRequest(asn1Writer, messageID,
+ request);
+ asn1Writer.flush();
+ }
+ catch (IOException e)
+ {
+ Result errorResult = adaptException(e);
+ connectionErrorOccurred(errorResult);
+ }
+ }
+ }
+ finally
+ {
+ connFactory.releaseASN1Writer(asn1Writer);
+ }
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <P> ResultFuture<Result> add(AddRequest request,
+ ResultHandler<Result, P> handler, P p)
+ {
+ int messageID = nextMsgID.getAndIncrement();
+ ResultFutureImpl<P> future = new ResultFutureImpl<P>(messageID,
+ request, handler, p, this, connFactory.getHandlerInvokers());
+ ASN1StreamWriter asn1Writer = connFactory
+ .getASN1Writer(streamWriter);
+
+ try
+ {
+ synchronized (writeLock)
+ {
+ if (connectionInvalidReason != null)
+ {
+ future.handleErrorResult(connectionInvalidReason);
+ return future;
+ }
+ if (pendingBindOrStartTLS > 0)
+ {
+ future
+ .handleResult(Responses.newResult(ResultCode.OPERATIONS_ERROR)
+ .setDiagnosticMessage("Bind or Start TLS operation in progress"));
+ return future;
+ }
+ pendingRequests.put(messageID, future);
+ try
+ {
+ LDAPEncoder.encodeAddRequest(asn1Writer, messageID, request);
+ asn1Writer.flush();
+ }
+ catch (IOException e)
+ {
+ pendingRequests.remove(messageID);
+
+ Result errorResult = adaptException(e);
+ connectionErrorOccurred(errorResult);
+ future.handleErrorResult(errorResult);
+ }
+ }
+ }
+ finally
+ {
+ connFactory.releaseASN1Writer(asn1Writer);
+ }
+
+ return future;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void addConnectionEventListener(
+ ConnectionEventListener listener) throws IllegalStateException,
+ NullPointerException
+ {
+ Validator.ensureNotNull(listener);
+
+ synchronized (writeLock)
+ {
+ if (isClosed)
+ {
+ throw new IllegalStateException();
+ }
+
+ listeners.add(listener);
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <P> ResultFuture<BindResult> bind(BindRequest request,
+ ResultHandler<? super BindResult, P> handler, P p)
+ {
+ int messageID = nextMsgID.getAndIncrement();
+ BindResultFutureImpl<P> future = new BindResultFutureImpl<P>(
+ messageID, request, handler, p, this, connFactory
+ .getHandlerInvokers());
+ ASN1StreamWriter asn1Writer = connFactory
+ .getASN1Writer(streamWriter);
+
+ try
+ {
+ synchronized (writeLock)
+ {
+ if (connectionInvalidReason != null)
+ {
+ future.handleErrorResult(connectionInvalidReason);
+ return future;
+ }
+ if (pendingBindOrStartTLS > 0)
+ {
+ future
+ .handleResult(Responses.newBindResult(ResultCode.OPERATIONS_ERROR)
+ .setDiagnosticMessage("Bind or Start TLS operation in progress"));
+ return future;
+ }
+ if (!pendingRequests.isEmpty())
+ {
+ future
+ .handleResult(Responses.newBindResult(ResultCode.OPERATIONS_ERROR)
+ .setDiagnosticMessage("There are other operations pending on this connection"));
+ return future;
+ }
+
+ pendingRequests.put(messageID, future);
+ pendingBindOrStartTLS = messageID;
+
+ try
+ {
+ if (request instanceof SASLBindRequest<?>)
+ {
+ try
+ {
+ SASLBindRequest<?> saslBind = (SASLBindRequest<?>) request;
+ SASLContext saslContext = saslBind
+ .getClientContext(serverAddress.getHostName());
+ future.setSASLContext(saslContext);
+ LDAPEncoder.encodeBindRequest(asn1Writer, messageID, 3,
+ saslBind, saslContext.getSASLCredentials());
+ }
+ catch (SaslException e)
+ {
+ Result errorResult = adaptException(e);
+ future.handleErrorResult(errorResult);
+ return future;
+ }
+ }
+ else if (request instanceof SimpleBindRequest)
+ {
+ LDAPEncoder.encodeBindRequest(asn1Writer, messageID, 3,
+ (SimpleBindRequest) request);
+ }
+ else
+ {
+ pendingRequests.remove(messageID);
+ future.handleResult(Responses.newBindResult(ResultCode.PROTOCOL_ERROR)
+ .setDiagnosticMessage("Auth type not supported"));
+ }
+ asn1Writer.flush();
+ }
+ catch (IOException e)
+ {
+ pendingRequests.remove(messageID);
+
+ Result errorResult = adaptException(e);
+ connectionErrorOccurred(errorResult);
+ future.handleErrorResult(errorResult);
+ }
+ }
+ }
+ finally
+ {
+ connFactory.releaseASN1Writer(asn1Writer);
+ }
+
+ return future;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void close()
+ {
+ close(Requests.newUnbindRequest());
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void close(UnbindRequest request) throws NullPointerException
+ {
+ // FIXME: I18N need to internationalize this message.
+ Validator.ensureNotNull(request);
+
+ close(request, false, Responses.newResult(ResultCode.CLIENT_SIDE_USER_CANCELLED)
+ .setDiagnosticMessage("Connection closed by client"));
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <P> ResultFuture<CompareResult> compare(
+ CompareRequest request,
+ ResultHandler<? super CompareResult, P> handler, P p)
+ {
+ int messageID = nextMsgID.getAndIncrement();
+ CompareResultFutureImpl<P> future = new CompareResultFutureImpl<P>(
+ messageID, request, handler, p, this, connFactory
+ .getHandlerInvokers());
+ ASN1StreamWriter asn1Writer = connFactory
+ .getASN1Writer(streamWriter);
+
+ try
+ {
+ synchronized (writeLock)
+ {
+ if (connectionInvalidReason != null)
+ {
+ future.handleErrorResult(connectionInvalidReason);
+ return future;
+ }
+ if (pendingBindOrStartTLS > 0)
+ {
+ future
+ .handleResult(Responses.newCompareResult(ResultCode.OPERATIONS_ERROR)
+ .setDiagnosticMessage("Bind or Start TLS operation in progress"));
+ return future;
+ }
+ pendingRequests.put(messageID, future);
+ try
+ {
+ LDAPEncoder.encodeCompareRequest(asn1Writer, messageID,
+ request);
+ asn1Writer.flush();
+ }
+ catch (IOException e)
+ {
+ pendingRequests.remove(messageID);
+
+ Result errorResult = adaptException(e);
+ connectionErrorOccurred(errorResult);
+ future.handleErrorResult(errorResult);
+ }
+ }
+ }
+ finally
+ {
+ connFactory.releaseASN1Writer(asn1Writer);
+ }
+
+ return future;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <P> ResultFuture<Result> delete(DeleteRequest request,
+ ResultHandler<Result, P> handler, P p)
+ {
+ int messageID = nextMsgID.getAndIncrement();
+ ResultFutureImpl<P> future = new ResultFutureImpl<P>(messageID,
+ request, handler, p, this, connFactory.getHandlerInvokers());
+ ASN1StreamWriter asn1Writer = connFactory
+ .getASN1Writer(streamWriter);
+
+ try
+ {
+ synchronized (writeLock)
+ {
+ if (connectionInvalidReason != null)
+ {
+ future.handleErrorResult(connectionInvalidReason);
+ return future;
+ }
+ if (pendingBindOrStartTLS > 0)
+ {
+ future
+ .handleResult(Responses.newResult(ResultCode.OPERATIONS_ERROR)
+ .setDiagnosticMessage("Bind or Start TLS operation in progress"));
+ return future;
+ }
+ pendingRequests.put(messageID, future);
+ try
+ {
+ LDAPEncoder.encodeDeleteRequest(asn1Writer, messageID,
+ request);
+ asn1Writer.flush();
+ }
+ catch (IOException e)
+ {
+ pendingRequests.remove(messageID);
+
+ Result errorResult = adaptException(e);
+ connectionErrorOccurred(errorResult);
+ future.handleErrorResult(errorResult);
+ }
+ }
+ }
+ finally
+ {
+ connFactory.releaseASN1Writer(asn1Writer);
+ }
+
+ return future;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <R extends Result, P> ResultFuture<R> extendedRequest(
+ ExtendedRequest<R> request, ResultHandler<? super R, P> handler,
+ P p)
+ {
+ int messageID = nextMsgID.getAndIncrement();
+ ExtendedResultFutureImpl<R, P> future = new ExtendedResultFutureImpl<R, P>(
+ messageID, request, handler, p, this, connFactory
+ .getHandlerInvokers());
+ ASN1StreamWriter asn1Writer = connFactory
+ .getASN1Writer(streamWriter);
+
+ try
+ {
+ synchronized (writeLock)
+ {
+ if (connectionInvalidReason != null)
+ {
+ future.handleErrorResult(connectionInvalidReason);
+ return future;
+ }
+ if (pendingBindOrStartTLS > 0)
+ {
+ future.handleResult(request.getExtendedOperation()
+ .decodeResponse(ResultCode.OPERATIONS_ERROR, "",
+ "Bind or Start TLS operation in progress"));
+ return future;
+ }
+ if (request.getRequestName().equals(
+ StartTLSRequest.OID_START_TLS_REQUEST))
+ {
+ if (!pendingRequests.isEmpty())
+ {
+ future.handleResult(request.getExtendedOperation()
+ .decodeResponse(ResultCode.OPERATIONS_ERROR, "",
+ "There are pending operations on this connection"));
+ return future;
+ }
+ if (isTLSEnabled())
+ {
+ future.handleResult(request.getExtendedOperation()
+ .decodeResponse(ResultCode.OPERATIONS_ERROR, "",
+ "This connection is already TLS enabled"));
+ }
+ pendingBindOrStartTLS = messageID;
+ }
+ pendingRequests.put(messageID, future);
+
+ try
+ {
+ LDAPEncoder.encodeExtendedRequest(asn1Writer, messageID,
+ request);
+ asn1Writer.flush();
+ }
+ catch (IOException e)
+ {
+ pendingRequests.remove(messageID);
+
+ Result errorResult = adaptException(e);
+ connectionErrorOccurred(errorResult);
+ future.handleErrorResult(errorResult);
+ }
+ }
+ }
+ finally
+ {
+ connFactory.releaseASN1Writer(asn1Writer);
+ }
+
+ return future;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <P> ResultFuture<Result> modify(ModifyRequest request,
+ ResultHandler<Result, P> handler, P p)
+ {
+ int messageID = nextMsgID.getAndIncrement();
+ ResultFutureImpl<P> future = new ResultFutureImpl<P>(messageID,
+ request, handler, p, this, connFactory.getHandlerInvokers());
+ ASN1StreamWriter asn1Writer = connFactory
+ .getASN1Writer(streamWriter);
+
+ try
+ {
+ synchronized (writeLock)
+ {
+ if (connectionInvalidReason != null)
+ {
+ future.handleErrorResult(connectionInvalidReason);
+ return future;
+ }
+ if (pendingBindOrStartTLS > 0)
+ {
+ future
+ .handleResult(Responses.newResult(ResultCode.OPERATIONS_ERROR)
+ .setDiagnosticMessage("Bind or Start TLS operation in progress"));
+ return future;
+ }
+ pendingRequests.put(messageID, future);
+ try
+ {
+ LDAPEncoder.encodeModifyRequest(asn1Writer, messageID,
+ request);
+ asn1Writer.flush();
+ }
+ catch (IOException e)
+ {
+ pendingRequests.remove(messageID);
+
+ Result errorResult = adaptException(e);
+ connectionErrorOccurred(errorResult);
+ future.handleErrorResult(errorResult);
+ }
+ }
+ }
+ finally
+ {
+ connFactory.releaseASN1Writer(asn1Writer);
+ }
+
+ return future;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <P> ResultFuture<Result> modifyDN(ModifyDNRequest request,
+ ResultHandler<Result, P> handler, P p)
+ {
+ int messageID = nextMsgID.getAndIncrement();
+ ResultFutureImpl<P> future = new ResultFutureImpl<P>(messageID,
+ request, handler, p, this, connFactory.getHandlerInvokers());
+ ASN1StreamWriter asn1Writer = connFactory
+ .getASN1Writer(streamWriter);
+
+ try
+ {
+ synchronized (writeLock)
+ {
+ if (connectionInvalidReason != null)
+ {
+ future.handleErrorResult(connectionInvalidReason);
+ return future;
+ }
+ if (pendingBindOrStartTLS > 0)
+ {
+ future
+ .handleResult(Responses.newResult(ResultCode.OPERATIONS_ERROR)
+ .setDiagnosticMessage("Bind or Start TLS operation in progress"));
+ return future;
+ }
+ pendingRequests.put(messageID, future);
+ try
+ {
+ LDAPEncoder.encodeModifyDNRequest(asn1Writer, messageID,
+ request);
+ asn1Writer.flush();
+ }
+ catch (IOException e)
+ {
+ pendingRequests.remove(messageID);
+
+ Result errorResult = adaptException(e);
+ connectionErrorOccurred(errorResult);
+ future.handleErrorResult(errorResult);
+ }
+ }
+ }
+ finally
+ {
+ connFactory.releaseASN1Writer(asn1Writer);
+ }
+
+ return future;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void removeConnectionEventListener(
+ ConnectionEventListener listener) throws NullPointerException
+ {
+ Validator.ensureNotNull(listener);
+
+ synchronized (writeLock)
+ {
+ listeners.remove(listener);
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <P> ResultFuture<Result> search(SearchRequest request,
+ ResultHandler<Result, P> resultHandler,
+ SearchResultHandler<P> searchResulthandler, P p)
+ {
+ int messageID = nextMsgID.getAndIncrement();
+ SearchResultFutureImpl<P> future = new SearchResultFutureImpl<P>(
+ messageID, request, resultHandler, searchResulthandler, p,
+ this, connFactory.getHandlerInvokers());
+ ASN1StreamWriter asn1Writer = connFactory
+ .getASN1Writer(streamWriter);
+
+ try
+ {
+ synchronized (writeLock)
+ {
+ if (connectionInvalidReason != null)
+ {
+ future.handleErrorResult(connectionInvalidReason);
+ return future;
+ }
+ if (pendingBindOrStartTLS > 0)
+ {
+ future
+ .handleResult(Responses.newResult(ResultCode.OPERATIONS_ERROR)
+ .setDiagnosticMessage("Bind or Start TLS operation in progress"));
+ return future;
+ }
+ pendingRequests.put(messageID, future);
+ try
+ {
+ LDAPEncoder.encodeSearchRequest(asn1Writer, messageID,
+ request);
+ asn1Writer.flush();
+ }
+ catch (IOException e)
+ {
+ pendingRequests.remove(messageID);
+
+ Result errorResult = adaptException(e);
+ connectionErrorOccurred(errorResult);
+ future.handleErrorResult(errorResult);
+ }
+ }
+ }
+ finally
+ {
+ connFactory.releaseASN1Writer(asn1Writer);
+ }
+
+ return future;
+ }
+
+
+
+ /**
+ * Returns the LDAP message handler associated with this connection.
+ *
+ * @return The LDAP message handler associated with this connection.
+ */
+ LDAPMessageHandler getLDAPMessageHandler()
+ {
+ return handler;
+ }
+
+
+
+ /**
+ * Indicates whether or not TLS is enabled on this connection.
+ *
+ * @return {@code true} if TLS is enabled on this connection,
+ * otherwise {@code false}.
+ */
+ boolean isTLSEnabled()
+ {
+ FilterChain currentFilterChain = (FilterChain) connection
+ .getProcessor();
+ return currentFilterChain.get(2) instanceof SSLFilter;
+ }
+
+
+
+ private Result adaptException(Throwable t)
+ {
+ if (t instanceof ExecutionException)
+ {
+ ExecutionException e = (ExecutionException) t;
+ t = e.getCause();
+ }
+
+ Result errorResult;
+
+ try
+ {
+ throw t;
+ }
+ catch (SaslException e)
+ {
+ // FIXME: I18N need to have a better error message.
+ // FIXME: Is this the best result code?
+ errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR).setDiagnosticMessage(
+ "An error occurred during SASL authentication").setCause(e);
+ }
+ catch (EOFException e)
+ {
+ // FIXME: I18N need to have a better error message.
+ // FIXME: what sort of IOExceptions can be thrown?
+ // FIXME: Is this the best result code?
+ errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_SERVER_DOWN).setDiagnosticMessage(
+ "Connection unexpectedly terminated by server").setCause(e);
+ }
+ catch (IOException e)
+ {
+ // FIXME: I18N need to have a better error message.
+ // FIXME: what sort of IOExceptions can be thrown?
+ // FIXME: Is this the best result code?
+ errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR).setDiagnosticMessage(
+ "An error occurred whilst attempting to send a request: "
+ + e.toString()).setCause(e);
+ }
+ catch (Throwable e)
+ {
+ // FIXME: I18N need to have a better error message.
+ // FIXME: Is this the best result code?
+ errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR).setDiagnosticMessage(
+ "An unknown error occurred: " + e.toString()).setCause(e);
+ }
+
+ return errorResult;
+ }
+
+
+
+ private void close(UnbindRequest unbindRequest,
+ boolean isDisconnectNotification, Result reason)
+ {
+ synchronized (writeLock)
+ {
+ boolean notifyClose = false;
+ boolean notifyErrorOccurred = false;
+
+ if (isClosed)
+ {
+ // Already closed.
+ return;
+ }
+
+ if (unbindRequest != null)
+ {
+ // User closed.
+ isClosed = true;
+ notifyClose = true;
+ }
+ else
+ {
+ notifyErrorOccurred = true;
+ }
+
+ if (connectionInvalidReason != null)
+ {
+ // Already invalid.
+ if (notifyClose)
+ {
+ // TODO: uncomment if close notification is required.
+ // for (ConnectionEventListener listener : listeners)
+ // {
+ // listener.connectionClosed(this);
+ // }
+ }
+ return;
+ }
+
+ // First abort all outstanding requests.
+ for (AbstractResultFutureImpl<?, ?> future : pendingRequests
+ .values())
+ {
+ if (pendingBindOrStartTLS <= 0)
+ {
+ ASN1StreamWriter asn1Writer = connFactory
+ .getASN1Writer(streamWriter);
+ int messageID = nextMsgID.getAndIncrement();
+ AbandonRequest abandon = Requests.newAbandonRequest(future
+ .getMessageID());
+ try
+ {
+ LDAPEncoder.encodeAbandonRequest(asn1Writer, messageID,
+ abandon);
+ asn1Writer.flush();
+ }
+ catch (IOException e)
+ {
+ // Underlying channel probably blown up. Just ignore.
+ }
+ finally
+ {
+ connFactory.releaseASN1Writer(asn1Writer);
+ }
+ }
+
+ future.handleErrorResult(reason);
+ }
+ pendingRequests.clear();
+
+ // Now try cleanly closing the connection if possible.
+ try
+ {
+ ASN1StreamWriter asn1Writer = connFactory
+ .getASN1Writer(streamWriter);
+ if (unbindRequest == null)
+ {
+ unbindRequest = Requests.newUnbindRequest();
+ }
+
+ try
+ {
+ LDAPEncoder.encodeUnbindRequest(asn1Writer, nextMsgID
+ .getAndIncrement(), unbindRequest);
+ asn1Writer.flush();
+ }
+ finally
+ {
+ connFactory.releaseASN1Writer(asn1Writer);
+ }
+ }
+ catch (IOException e)
+ {
+ // Underlying channel prob blown up. Just ignore.
+ }
+
+ try
+ {
+ streamWriter.close();
+ }
+ catch (IOException e)
+ {
+ // Ignore.
+ }
+
+ try
+ {
+ connection.close();
+ }
+ catch (IOException e)
+ {
+ // Ignore.
+ }
+
+ // Mark the connection as invalid.
+ connectionInvalidReason = reason;
+
+ // Notify listeners.
+ if (notifyClose)
+ {
+ // TODO: uncomment if close notification is required.
+ // for (ConnectionEventListener listener : listeners)
+ // {
+ // listener.connectionClosed(this);
+ // }
+ }
+
+ if (notifyErrorOccurred)
+ {
+ for (ConnectionEventListener listener : listeners)
+ {
+ listener.connectionErrorOccurred(false, ErrorResultException
+ .wrap(reason));
+ }
+ }
+ }
+ }
+
+
+
+ private void connectionErrorOccurred(Result reason)
+ {
+ close(null, false, reason);
+ }
+
+
+
+ // TODO uncomment if we decide these methods are useful.
+ // /**
+ // * {@inheritDoc}
+ // */
+ // public boolean isClosed()
+ // {
+ // synchronized (writeLock)
+ // {
+ // return isClosed;
+ // }
+ // }
+ //
+ //
+ //
+ // /**
+ // * {@inheritDoc}
+ // */
+ // public boolean isValid() throws InterruptedException
+ // {
+ // synchronized (writeLock)
+ // {
+ // return connectionInvalidReason == null;
+ // }
+ // }
+ //
+ //
+ //
+ // /**
+ // * {@inheritDoc}
+ // */
+ // public boolean isValid(long timeout, TimeUnit unit)
+ // throws InterruptedException, TimeoutException
+ // {
+ // // FIXME: no support for timeout.
+ // return isValid();
+ // }
+
+ private StreamWriter getFilterChainStreamWriter()
+ {
+ StreamWriter writer = connection.getStreamWriter();
+ FilterChain currentFilterChain = (FilterChain) connection
+ .getProcessor();
+ for (Filter filter : currentFilterChain)
+ {
+ if (filter instanceof StreamTransformerFilter)
+ {
+ writer = ((StreamTransformerFilter) filter)
+ .getStreamWriter(writer);
+ }
+ }
+
+ return writer;
+ }
+
+
+
+ // Needed in order to expose type information.
+ private <R extends Result> void handleExtendedResult0(
+ ExtendedResultFutureImpl<R, ?> future,
+ GenericExtendedResult result) throws DecodeException
+ {
+ R decodedResponse = future.decodeResponse(result.getResultCode(),
+ result.getMatchedDN(), result.getDiagnosticMessage(), result
+ .getResponseName(), result.getResponseValue());
+
+ if (future.getRequest() instanceof StartTLSRequest)
+ {
+ if (result.getResultCode() == ResultCode.SUCCESS)
+ {
+ StartTLSRequest request = (StartTLSRequest) future.getRequest();
+ try
+ {
+ startTLS(request.getSSLContext());
+ }
+ catch (ErrorResultException e)
+ {
+ future.handleErrorResult(e.getResult());
+ return;
+ }
+ }
+ pendingBindOrStartTLS = -1;
+ }
+
+ future.handleResult(decodedResponse);
+ }
+
+
+
+ private void handleIncorrectResponse(
+ AbstractResultFutureImpl<?, ?> pendingRequest)
+ {
+ // FIXME: I18N need to have a better error message.
+ Result errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_DECODING_ERROR)
+ .setDiagnosticMessage("LDAP response message did not match request");
+
+ pendingRequest.handleErrorResult(errorResult);
+ connectionErrorOccurred(errorResult);
+ }
+
+
+
+ private void startTLS(SSLContext sslContext)
+ throws ErrorResultException
+ {
+ SSLHandshaker sslHandshaker = connFactory.getSslHandshaker();
+ SSLFilter sslFilter;
+ SSLEngineConfigurator sslEngineConfigurator;
+ if (sslContext == connFactory.getSSLContext())
+ {
+ // Use factory SSL objects since it is the same SSLContext
+ sslFilter = connFactory.getSSlFilter();
+ sslEngineConfigurator = connFactory.getSSlEngineConfigurator();
+ }
+ else
+ {
+ sslEngineConfigurator = new SSLEngineConfigurator(sslContext,
+ true, false, false);
+ sslFilter = new SSLFilter(sslEngineConfigurator, sslHandshaker);
+ }
+ installFilter(sslFilter);
+
+ performSSLHandshake(sslHandshaker, sslEngineConfigurator);
+ }
+
+
+
+ void performSSLHandshake(SSLHandshaker sslHandshaker,
+ SSLEngineConfigurator sslEngineConfigurator)
+ throws ErrorResultException
+ {
+ SSLStreamReader reader = new SSLStreamReader(connection
+ .getStreamReader());
+ SSLStreamWriter writer = new SSLStreamWriter(connection
+ .getStreamWriter());
+
+ try
+ {
+ sslHandshaker.handshake(reader, writer, sslEngineConfigurator)
+ .get();
+ }
+ catch (Exception e)
+ {
+ Result result = adaptException(e);
+ connectionErrorOccurred(result);
+ throw ErrorResultException.wrap(result);
+ }
+ }
+
+
+
+ synchronized void installFilter(Filter filter)
+ {
+ if (customFilterChain == null)
+ {
+ customFilterChain = connFactory.getDefaultFilterChainFactory()
+ .create();
+ connection.setProcessor(customFilterChain);
+ }
+
+ // Install the SSLFilter in the custom filter chain
+ Filter oldFilter = customFilterChain.remove(customFilterChain
+ .size() - 1);
+ customFilterChain.add(filter);
+ customFilterChain.add(oldFilter);
+
+ // Update stream writer
+ streamWriter = getFilterChainStreamWriter();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/LDAPConnectionFactory.java b/sdk/src/org/opends/sdk/ldap/LDAPConnectionFactory.java
new file mode 100644
index 0000000..db01881
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/LDAPConnectionFactory.java
@@ -0,0 +1,129 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import org.opends.sdk.*;
+
+
+
+/**
+ * LDAP connection factory implementation.
+ */
+public final class LDAPConnectionFactory implements
+ ConnectionFactory<AsynchronousConnection>
+{
+ // We implement the factory using the pimpl idiom in order have
+ // cleaner Javadoc which does not expose implementation methods from
+ // AbstractConnectionFactory.
+
+ private final LDAPConnectionFactoryImpl impl;
+
+
+
+ /**
+ * Creates a plain LDAP connection to the Directory Server at the
+ * specified host and port address.
+ *
+ * @param host
+ * The host name.
+ * @param port
+ * The port number.
+ * @return A connection to the Directory Server at the specified host
+ * and port address.
+ * @throws ErrorResultException
+ * If the connection request failed for some reason.
+ * @throws NullPointerException
+ * If {@code host} was {@code null}.
+ */
+ public static Connection connect(String host, int port)
+ throws ErrorResultException, NullPointerException
+ {
+ return new LDAPConnectionFactory(host, port).getConnection();
+ }
+
+
+
+ /**
+ * Creates a new LDAP connection factory which can be used to create
+ * LDAP connections to the Directory Server at the provided host and
+ * port address using default connection options.
+ *
+ * @param host
+ * The host name.
+ * @param port
+ * The port number.
+ * @throws NullPointerException
+ * If {@code host} was {@code null}.
+ */
+ public LDAPConnectionFactory(String host, int port)
+ throws NullPointerException
+ {
+ this(host, port, LDAPConnectionOptions.defaultOptions());
+ }
+
+
+
+ /**
+ * Creates a new LDAP connection factory which can be used to create
+ * LDAP connections to the Directory Server at the provided host and
+ * port address using provided connection options.
+ *
+ * @param host
+ * The host name.
+ * @param port
+ * The port number.
+ * @param options
+ * The LDAP connection options to use when creating
+ * connections.
+ * @throws NullPointerException
+ * If {@code host} or {@code options} was {@code null}.
+ */
+ public LDAPConnectionFactory(String host, int port,
+ LDAPConnectionOptions options) throws NullPointerException
+ {
+ this.impl = new LDAPConnectionFactoryImpl(host, port, options);
+ }
+
+
+
+ public <P> ConnectionFuture<AsynchronousConnection> getAsynchronousConnection(
+ ConnectionResultHandler<? super AsynchronousConnection, P> handler,
+ P p)
+ {
+ return impl.getAsynchronousConnection(handler, p);
+ }
+
+
+
+ public Connection getConnection() throws ErrorResultException
+ {
+ return impl.getConnection();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/LDAPConnectionFactoryImpl.java b/sdk/src/org/opends/sdk/ldap/LDAPConnectionFactoryImpl.java
new file mode 100644
index 0000000..bdb51f9
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/LDAPConnectionFactoryImpl.java
@@ -0,0 +1,636 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.*;
+
+import javax.net.ssl.SSLContext;
+
+import org.opends.sdk.*;
+import org.opends.sdk.controls.*;
+import org.opends.sdk.extensions.StartTLSRequest;
+import org.opends.sdk.responses.Responses;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.Validator;
+
+import com.sun.grizzly.TransportFactory;
+import com.sun.grizzly.attributes.Attribute;
+import com.sun.grizzly.filterchain.PatternFilterChainFactory;
+import com.sun.grizzly.nio.transport.TCPNIOTransport;
+import com.sun.grizzly.ssl.BlockingSSLHandshaker;
+import com.sun.grizzly.ssl.SSLEngineConfigurator;
+import com.sun.grizzly.ssl.SSLFilter;
+import com.sun.grizzly.ssl.SSLHandshaker;
+import com.sun.grizzly.streams.StreamWriter;
+
+
+
+/**
+ * LDAP connection factory implementation.
+ */
+final class LDAPConnectionFactoryImpl extends
+ AbstractConnectionFactory<AsynchronousConnection> implements
+ ConnectionFactory<AsynchronousConnection>
+{
+ private final class LDAPTransport extends AbstractLDAPTransport
+ {
+
+ @Override
+ LDAPMessageHandler getMessageHandler(
+ com.sun.grizzly.Connection<?> connection)
+ {
+ return ldapConnectionAttr.get(connection).getLDAPMessageHandler();
+ }
+
+
+
+ @Override
+ void removeMessageHandler(com.sun.grizzly.Connection<?> connection)
+ {
+ ldapConnectionAttr.remove(connection);
+ }
+
+ }
+
+
+
+ private static class FailedImpl implements
+ ConnectionFuture<AsynchronousConnection>
+ {
+ private volatile ErrorResultException exception;
+
+
+
+ private FailedImpl(ErrorResultException exception)
+ {
+ this.exception = exception;
+ }
+
+
+
+ public boolean cancel(boolean mayInterruptIfRunning)
+ {
+ return false;
+ }
+
+
+
+ public AsynchronousConnection get() throws InterruptedException,
+ ErrorResultException
+ {
+ throw exception;
+ }
+
+
+
+ public AsynchronousConnection get(long timeout, TimeUnit unit)
+ throws InterruptedException, TimeoutException,
+ ErrorResultException
+ {
+ throw exception;
+ }
+
+
+
+ public boolean isCancelled()
+ {
+ return false;
+ }
+
+
+
+ public boolean isDone()
+ {
+ return false;
+ }
+ }
+
+
+
+ private class ConnectionFutureImpl<P> implements
+ ConnectionFuture<AsynchronousConnection>,
+ com.sun.grizzly.CompletionHandler<com.sun.grizzly.Connection>,
+ ResultHandler<Result, Void>
+ {
+ private volatile AsynchronousConnection connection;
+
+ private volatile ErrorResultException exception;
+
+ private volatile Future<com.sun.grizzly.Connection> connectFuture;
+
+ private volatile ResultFuture<?> sslFuture;
+
+ private final CountDownLatch latch = new CountDownLatch(1);
+
+ private final ConnectionResultHandler<? super AsynchronousConnection, P> handler;
+
+ private boolean cancelled;
+
+ private final P p;
+
+
+
+ private ConnectionFutureImpl(
+ ConnectionResultHandler<? super AsynchronousConnection, P> handler,
+ P p)
+ {
+ this.handler = handler;
+ this.p = p;
+ }
+
+
+
+ public boolean cancel(boolean mayInterruptIfRunning)
+ {
+ cancelled = connectFuture.cancel(mayInterruptIfRunning)
+ || sslFuture != null
+ && sslFuture.cancel(mayInterruptIfRunning);
+ if (cancelled)
+ {
+ latch.countDown();
+ }
+ return cancelled;
+ }
+
+
+
+ public AsynchronousConnection get() throws InterruptedException,
+ ErrorResultException
+ {
+ latch.await();
+ if (cancelled)
+ {
+ throw new CancellationException();
+ }
+ if (exception != null)
+ {
+ throw exception;
+ }
+ return connection;
+ }
+
+
+
+ public AsynchronousConnection get(long timeout, TimeUnit unit)
+ throws InterruptedException, TimeoutException,
+ ErrorResultException
+ {
+ latch.await(timeout, unit);
+ if (cancelled)
+ {
+ throw new CancellationException();
+ }
+ if (exception != null)
+ {
+ throw exception;
+ }
+ return connection;
+ }
+
+
+
+ public boolean isCancelled()
+ {
+ return cancelled;
+ }
+
+
+
+ public boolean isDone()
+ {
+ return latch.getCount() == 0;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void cancelled(com.sun.grizzly.Connection connection)
+ {
+ // Ignore this.
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void completed(com.sun.grizzly.Connection connection,
+ com.sun.grizzly.Connection result)
+ {
+ LDAPConnection ldapConn = adaptConnection(connection);
+ this.connection = adaptConnection(connection);
+
+ if (options.getSSLContext() != null && options.useStartTLS())
+ {
+ StartTLSRequest startTLS = new StartTLSRequest(options
+ .getSSLContext());
+ sslFuture = this.connection.extendedRequest(startTLS, this,
+ null);
+ }
+ else if (options.getSSLContext() != null)
+ {
+ try
+ {
+ ldapConn.installFilter(sslFilter);
+ ldapConn.performSSLHandshake(sslHandshaker,
+ sslEngineConfigurator);
+ latch.countDown();
+ if (handler != null)
+ {
+ handler.handleConnection(p, this.connection);
+ }
+ }
+ catch (CancellationException ce)
+ {
+ // Handshake cancelled.
+ latch.countDown();
+ }
+ catch (ErrorResultException throwable)
+ {
+ exception = throwable;
+ latch.countDown();
+ if (handler != null)
+ {
+ handler.handleConnectionError(p, exception);
+ }
+ }
+ }
+ else
+ {
+ latch.countDown();
+ if (handler != null)
+ {
+ handler.handleConnection(p, this.connection);
+ }
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void failed(com.sun.grizzly.Connection connection,
+ Throwable throwable)
+ {
+ exception = adaptConnectionException(throwable);
+ latch.countDown();
+ if (handler != null)
+ {
+ handler.handleConnectionError(p, exception);
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void updated(com.sun.grizzly.Connection connection,
+ com.sun.grizzly.Connection result)
+ {
+ // Ignore this.
+ }
+
+
+
+ // This is called when the StartTLS request is successful
+ public void handleResult(Void v, Result result)
+ {
+ latch.countDown();
+ if (handler != null)
+ {
+ handler.handleConnection(p, connection);
+ }
+ }
+
+
+
+ // This is called when the StartTLS request is not successful
+ public void handleErrorResult(Void v, ErrorResultException error)
+ {
+ exception = error;
+ latch.countDown();
+ if (handler != null)
+ {
+ handler.handleConnectionError(p, exception);
+ }
+ }
+ }
+
+
+
+ private static final String LDAP_CONNECTION_OBJECT_ATTR = "LDAPConnAtr";
+
+ private static TCPNIOTransport TCP_NIO_TRANSPORT = null;
+
+
+
+ // FIXME: Need to figure out how this can be configured without
+ // exposing internal implementation details to application.
+ private static synchronized TCPNIOTransport getTCPNIOTransport()
+ {
+ if (TCP_NIO_TRANSPORT == null)
+ {
+ // Create a default transport using the Grizzly framework.
+ //
+ TCP_NIO_TRANSPORT = TransportFactory.getInstance()
+ .createTCPTransport();
+ try
+ {
+ TCP_NIO_TRANSPORT.start();
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(
+ "Unable to create default connection factory provider", e);
+ }
+
+ Runtime.getRuntime().addShutdownHook(new Thread()
+ {
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ TCP_NIO_TRANSPORT.stop();
+ }
+ catch (Exception e)
+ {
+ // Ignore.
+ }
+
+ try
+ {
+ TCP_NIO_TRANSPORT.getWorkerThreadPool().shutdown();
+ }
+ catch (Exception e)
+ {
+ // Ignore.
+ }
+ }
+
+ });
+ }
+ return TCP_NIO_TRANSPORT;
+ }
+
+
+
+ private final Attribute<LDAPConnection> ldapConnectionAttr;
+
+ private final InetSocketAddress socketAddress;
+
+ private final TCPNIOTransport transport;
+
+ private final SSLHandshaker sslHandshaker = new BlockingSSLHandshaker();
+
+ private final SSLEngineConfigurator sslEngineConfigurator;
+
+ private final SSLFilter sslFilter;
+
+ private final Map<String, ControlDecoder<?>> knownControls;
+
+ private final LDAPTransport ldapTransport = new LDAPTransport();
+
+ private final LDAPConnectionOptions options;
+
+
+
+ /**
+ * Creates a new LDAP connection factory implementation which can be
+ * used to create connections to the Directory Server at the provided
+ * host and port address using provided connection options.
+ *
+ * @param host
+ * The host name.
+ * @param port
+ * The port number.
+ * @param options
+ * The LDAP connection options to use when creating
+ * connections.
+ * @throws NullPointerException
+ * If {@code host} or {@code options} was {@code null}.
+ */
+ LDAPConnectionFactoryImpl(String host, int port,
+ LDAPConnectionOptions options) throws NullPointerException
+ {
+ this(host, port, options, getTCPNIOTransport());
+ }
+
+
+
+ private LDAPConnectionFactoryImpl(String host, int port,
+ LDAPConnectionOptions options, TCPNIOTransport transport)
+ {
+ Validator.ensureNotNull(host, transport, options);
+
+ this.transport = transport;
+ this.ldapConnectionAttr = transport.getAttributeBuilder()
+ .createAttribute(LDAP_CONNECTION_OBJECT_ATTR);
+ this.socketAddress = new InetSocketAddress(host, port);
+ this.options = LDAPConnectionOptions.copyOf(options);
+ if (this.options.getSSLContext() == null)
+ {
+ this.sslEngineConfigurator = null;
+ this.sslFilter = null;
+ }
+ else
+ {
+ this.sslEngineConfigurator = new SSLEngineConfigurator(
+ this.options.getSSLContext(), true, false, false);
+ this.sslFilter = new SSLFilter(sslEngineConfigurator,
+ sslHandshaker);
+ }
+ this.knownControls = new HashMap<String, ControlDecoder<?>>();
+ initControls();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <P> ConnectionFuture<AsynchronousConnection> getAsynchronousConnection(
+ ConnectionResultHandler<? super AsynchronousConnection, P> handler,
+ P p)
+ {
+ ConnectionFutureImpl<P> future = new ConnectionFutureImpl<P>(
+ handler, p);
+
+ try
+ {
+ future.connectFuture = transport.connect(socketAddress, future);
+ return future;
+ }
+ catch (IOException e)
+ {
+ ErrorResultException result = adaptConnectionException(e);
+ return new FailedImpl(result);
+ }
+ }
+
+
+
+ ExecutorService getHandlerInvokers()
+ {
+ // TODO: Threading strategies?
+ return null;
+ }
+
+
+
+ SSLHandshaker getSslHandshaker()
+ {
+ return sslHandshaker;
+ }
+
+
+
+ SSLFilter getSSlFilter()
+ {
+ return sslFilter;
+ }
+
+
+
+ SSLContext getSSLContext()
+ {
+ return options.getSSLContext();
+ }
+
+
+
+ SSLEngineConfigurator getSSlEngineConfigurator()
+ {
+ return sslEngineConfigurator;
+ }
+
+
+
+ ASN1StreamWriter getASN1Writer(StreamWriter streamWriter)
+ {
+ return ldapTransport.getASN1Writer(streamWriter);
+ }
+
+
+
+ void releaseASN1Writer(ASN1StreamWriter asn1Writer)
+ {
+ ldapTransport.releaseASN1Writer(asn1Writer);
+ }
+
+
+
+ PatternFilterChainFactory getDefaultFilterChainFactory()
+ {
+ return ldapTransport.getDefaultFilterChainFactory();
+ }
+
+
+
+ private LDAPConnection adaptConnection(
+ com.sun.grizzly.Connection<?> connection)
+ {
+ // Test shows that its much faster with non block writes but risk
+ // running out of memory if the server is slow.
+ connection.configureBlocking(true);
+ connection.getStreamReader().setBlocking(true);
+ connection.getStreamWriter().setBlocking(true);
+ connection.setProcessor(ldapTransport
+ .getDefaultFilterChainFactory().getFilterChainPattern());
+
+ LDAPConnection ldapConnection = new LDAPConnection(connection,
+ socketAddress, options.getSchema(), this);
+ ldapConnectionAttr.set(connection, ldapConnection);
+ return ldapConnection;
+ }
+
+
+
+ private ErrorResultException adaptConnectionException(Throwable t)
+ {
+ if (t instanceof ExecutionException)
+ {
+ t = t.getCause();
+ }
+
+ Result result = Responses.newResult(ResultCode.CLIENT_SIDE_CONNECT_ERROR).setCause(t)
+ .setDiagnosticMessage(t.getMessage());
+ return ErrorResultException.wrap(result);
+ }
+
+
+
+ ControlDecoder<?> getControlDecoder(String oid)
+ {
+ return knownControls.get(oid);
+ }
+
+
+
+ private void initControls()
+ {
+ knownControls.put(
+ AccountUsabilityControl.OID_ACCOUNT_USABLE_CONTROL,
+ AccountUsabilityControl.RESPONSE_DECODER);
+ knownControls.put(
+ AuthorizationIdentityControl.OID_AUTHZID_RESPONSE,
+ AuthorizationIdentityControl.RESPONSE_DECODER);
+ knownControls.put(
+ EntryChangeNotificationControl.OID_ENTRY_CHANGE_NOTIFICATION,
+ EntryChangeNotificationControl.DECODER);
+ knownControls.put(PagedResultsControl.OID_PAGED_RESULTS_CONTROL,
+ PagedResultsControl.DECODER);
+ knownControls.put(PasswordExpiredControl.OID_NS_PASSWORD_EXPIRED,
+ PasswordExpiredControl.DECODER);
+ knownControls.put(PasswordExpiringControl.OID_NS_PASSWORD_EXPIRING,
+ PasswordExpiringControl.DECODER);
+ knownControls.put(
+ PasswordPolicyControl.OID_PASSWORD_POLICY_CONTROL,
+ PasswordPolicyControl.RESPONSE_DECODER);
+ knownControls.put(PostReadControl.OID_LDAP_READENTRY_POSTREAD,
+ PostReadControl.RESPONSE_DECODER);
+ knownControls.put(PreReadControl.OID_LDAP_READENTRY_PREREAD,
+ PreReadControl.RESPONSE_DECODER);
+ knownControls.put(
+ ServerSideSortControl.OID_SERVER_SIDE_SORT_RESPONSE_CONTROL,
+ ServerSideSortControl.RESPONSE_DECODER);
+ knownControls.put(VLVControl.OID_VLV_RESPONSE_CONTROL,
+ VLVControl.RESPONSE_DECODER);
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/LDAPConnectionOptions.java b/sdk/src/org/opends/sdk/ldap/LDAPConnectionOptions.java
new file mode 100644
index 0000000..9104b55
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/LDAPConnectionOptions.java
@@ -0,0 +1,210 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import javax.net.ssl.SSLContext;
+
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Common connection options for LDAP connections.
+ */
+public final class LDAPConnectionOptions
+{
+ private Schema schema = Schema.getDefaultSchema();
+
+ private SSLContext sslContext = null;
+
+ private boolean useStartTLS = false;
+
+
+
+ /**
+ * Creates a copy of the provided connection options.
+ *
+ * @param options
+ * The options to be copied.
+ * @return The copy of the provided connection options.
+ */
+ public static LDAPConnectionOptions copyOf(
+ LDAPConnectionOptions options)
+ {
+ return defaultOptions().assign(options);
+ }
+
+
+
+ /**
+ * Creates a new set of connection options with default settings. SSL
+ * will not be enabled, nor will key or trust managers be defined.
+ *
+ * @return The new connection options.
+ */
+ public static LDAPConnectionOptions defaultOptions()
+ {
+ return new LDAPConnectionOptions();
+ }
+
+
+
+ // Prevent direct instantiation.
+ private LDAPConnectionOptions()
+ {
+ // Nothing to do.
+ }
+
+
+
+ /**
+ * Returns the schema which will be used to decode responses from the
+ * server. By default the schema returned by
+ * {@link Schema#getDefaultSchema()} will be used.
+ *
+ * @return The schema which will be used to decode responses from the
+ * server.
+ */
+ public Schema getSchema()
+ {
+ return schema;
+ }
+
+
+
+ /**
+ * Sets the schema which will be used to decode responses from the
+ * server. By default the schema returned by
+ * {@link Schema#getDefaultSchema()} will be used.
+ *
+ * @param schema
+ * The schema which will be used to decode responses from the
+ * server.
+ * @return A reference to this LDAP connection options.
+ * @throws NullPointerException
+ * If {@code schema} was {@code null}.
+ */
+ public LDAPConnectionOptions setSchema(Schema schema)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(schema);
+ this.schema = schema;
+ return this;
+ }
+
+
+
+ /**
+ * Returns the SSL context which will be used when initiating
+ * connections with the Directory Server. By default no SSL context
+ * will be used, indicating that connections will not be secured. If a
+ * non-{@code null} SSL context is returned then connections will be
+ * secured using either SSL or StartTLS depending on
+ * {@link #useStartTLS()}.
+ *
+ * @return The SSL context which will be used when initiating secure
+ * connections with the Directory Server, which may be {@code
+ * null} indicating that connections will not be secured.
+ */
+ public SSLContext getSSLContext()
+ {
+ return sslContext;
+ }
+
+
+
+ /**
+ * Sets the SSL context which will be used when initiating connections
+ * with the Directory Server. By default no SSL context will be used,
+ * indicating that connections will not be secured. If a non-{@code
+ * null} SSL context is returned then connections will be secured
+ * using either SSL or StartTLS depending on {@link #useStartTLS()}.
+ *
+ * @param sslContext
+ * The SSL context which will be used when initiating secure
+ * connections with the Directory Server, which may be
+ * {@code null} indicating that connections will not be
+ * secured.
+ * @return A reference to this LDAP connection options.
+ */
+ public LDAPConnectionOptions setSSLContext(SSLContext sslContext)
+ {
+ this.sslContext = sslContext;
+ return this;
+ }
+
+
+
+ /**
+ * Indicates whether or not SSL or StartTLS should be used for
+ * securing connections when an SSL context is specified. By default
+ * SSL will be used in preference to StartTLS.
+ *
+ * @return {@code true} if StartTLS should be used for securing
+ * connections when an SSL context is specified, otherwise
+ * {@code false} indicating that SSL should be used.
+ */
+ public boolean useStartTLS()
+ {
+ return useStartTLS;
+ }
+
+
+
+ /**
+ * Specifies whether or not SSL or StartTLS should be used for
+ * securing connections when an SSL context is specified. By default
+ * SSL will be used in preference to StartTLS.
+ *
+ * @param useStartTLS
+ * {@code true} if StartTLS should be used for securing
+ * connections when an SSL context is specified, otherwise
+ * {@code false} indicating that SSL should be used.
+ * @return A reference to this LDAP connection options.
+ */
+ public LDAPConnectionOptions setUseStartTLS(boolean useStartTLS)
+ {
+ this.useStartTLS = useStartTLS;
+ return this;
+ }
+
+
+
+ // Assigns the provided options to this set of options.
+ LDAPConnectionOptions assign(LDAPConnectionOptions options)
+ {
+ this.schema = options.schema;
+ this.sslContext = options.sslContext;
+ this.useStartTLS = options.useStartTLS;
+ return this;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/ldap/LDAPConstants.java b/sdk/src/org/opends/sdk/ldap/LDAPConstants.java
new file mode 100644
index 0000000..f0c4e71
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/LDAPConstants.java
@@ -0,0 +1,332 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2008 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.ldap;
+
+/**
+ * This class defines a number of constants used in the LDAP protocol.
+ */
+final class LDAPConstants
+{
+ /**
+ * The protocol op type for bind requests.
+ */
+ static final byte OP_TYPE_BIND_REQUEST = 0x60;
+
+ /**
+ * The protocol op type for bind responses.
+ */
+ static final byte OP_TYPE_BIND_RESPONSE = 0x61;
+
+ /**
+ * The protocol op type for unbind requests.
+ */
+ static final byte OP_TYPE_UNBIND_REQUEST = 0x42;
+
+ /**
+ * The protocol op type for search requests.
+ */
+ static final byte OP_TYPE_SEARCH_REQUEST = 0x63;
+
+ /**
+ * The protocol op type for search result entries.
+ */
+ static final byte OP_TYPE_SEARCH_RESULT_ENTRY = 0x64;
+
+ /**
+ * The protocol op type for search result references.
+ */
+ static final byte OP_TYPE_SEARCH_RESULT_REFERENCE = 0x73;
+
+ /**
+ * The protocol op type for search result done elements.
+ */
+ static final byte OP_TYPE_SEARCH_RESULT_DONE = 0x65;
+
+ /**
+ * The protocol op type for modify requests.
+ */
+ static final byte OP_TYPE_MODIFY_REQUEST = 0x66;
+
+ /**
+ * The protocol op type for modify responses.
+ */
+ static final byte OP_TYPE_MODIFY_RESPONSE = 0x67;
+
+ /**
+ * The protocol op type for add requests.
+ */
+ static final byte OP_TYPE_ADD_REQUEST = 0x68;
+
+ /**
+ * The protocol op type for add responses.
+ */
+ static final byte OP_TYPE_ADD_RESPONSE = 0x69;
+
+ /**
+ * The protocol op type for delete requests.
+ */
+ static final byte OP_TYPE_DELETE_REQUEST = 0x4A;
+
+ /**
+ * The protocol op type for delete responses.
+ */
+ static final byte OP_TYPE_DELETE_RESPONSE = 0x6B;
+
+ /**
+ * The protocol op type for modify DN requests.
+ */
+ static final byte OP_TYPE_MODIFY_DN_REQUEST = 0x6C;
+
+ /**
+ * The protocol op type for modify DN responses.
+ */
+ static final byte OP_TYPE_MODIFY_DN_RESPONSE = 0x6D;
+
+ /**
+ * The protocol op type for compare requests.
+ */
+ static final byte OP_TYPE_COMPARE_REQUEST = 0x6E;
+
+ /**
+ * The protocol op type for compare responses.
+ */
+ static final byte OP_TYPE_COMPARE_RESPONSE = 0x6F;
+
+ /**
+ * The protocol op type for abandon requests.
+ */
+ static final byte OP_TYPE_ABANDON_REQUEST = 0x50;
+
+ /**
+ * The protocol op type for extended requests.
+ */
+ static final byte OP_TYPE_EXTENDED_REQUEST = 0x77;
+
+ /**
+ * The protocol op type for extended responses.
+ */
+ static final byte OP_TYPE_EXTENDED_RESPONSE = 0x78;
+
+ /**
+ * The protocol op type for intermediate responses.
+ */
+ static final byte OP_TYPE_INTERMEDIATE_RESPONSE = 0x79;
+
+ /**
+ * The BER type to use for encoding the sequence of controls in an
+ * LDAP message.
+ */
+ static final byte TYPE_CONTROL_SEQUENCE = (byte) 0xA0;
+
+ /**
+ * The BER type to use for encoding the sequence of referral URLs in
+ * an LDAPResult element.
+ */
+ static final byte TYPE_REFERRAL_SEQUENCE = (byte) 0xA3;
+
+ /**
+ * The BER type to use for the AuthenticationChoice element in a bind
+ * request when simple authentication is to be used.
+ */
+ static final byte TYPE_AUTHENTICATION_SIMPLE = (byte) 0x80;
+
+ /**
+ * The BER type to use for the AuthenticationChoice element in a bind
+ * request when SASL authentication is to be used.
+ */
+ static final byte TYPE_AUTHENTICATION_SASL = (byte) 0xA3;
+
+ /**
+ * The BER type to use for the server SASL credentials in a bind
+ * response.
+ */
+ static final byte TYPE_SERVER_SASL_CREDENTIALS = (byte) 0x87;
+
+ /**
+ * The BER type to use for AND filter components.
+ */
+ static final byte TYPE_FILTER_AND = (byte) 0xA0;
+
+ /**
+ * The BER type to use for OR filter components.
+ */
+ static final byte TYPE_FILTER_OR = (byte) 0xA1;
+
+ /**
+ * The BER type to use for NOT filter components.
+ */
+ static final byte TYPE_FILTER_NOT = (byte) 0xA2;
+
+ /**
+ * The BER type to use for equality filter components.
+ */
+ static final byte TYPE_FILTER_EQUALITY = (byte) 0xA3;
+
+ /**
+ * The BER type to use for substring filter components.
+ */
+ static final byte TYPE_FILTER_SUBSTRING = (byte) 0xA4;
+
+ /**
+ * The BER type to use for greater than or equal to filter components.
+ */
+ static final byte TYPE_FILTER_GREATER_OR_EQUAL = (byte) 0xA5;
+
+ /**
+ * The BER type to use for less than or equal to filter components.
+ */
+ static final byte TYPE_FILTER_LESS_OR_EQUAL = (byte) 0xA6;
+
+ /**
+ * The BER type to use for presence filter components.
+ */
+ static final byte TYPE_FILTER_PRESENCE = (byte) 0x87;
+
+ /**
+ * The BER type to use for approximate filter components.
+ */
+ static final byte TYPE_FILTER_APPROXIMATE = (byte) 0xA8;
+
+ /**
+ * The BER type to use for extensible matching filter components.
+ */
+ static final byte TYPE_FILTER_EXTENSIBLE_MATCH = (byte) 0xA9;
+
+ /**
+ * The BER type to use for the subInitial component of a substring
+ * filter.
+ */
+ static final byte TYPE_SUBINITIAL = (byte) 0x80;
+
+ /**
+ * The BER type to use for the subAny component(s) of a substring
+ * filter.
+ */
+ static final byte TYPE_SUBANY = (byte) 0x81;
+
+ /**
+ * The BER type to use for the subFinal components of a substring
+ * filter.
+ */
+ static final byte TYPE_SUBFINAL = (byte) 0x82;
+
+ /**
+ * The BER type to use for the matching rule OID in a matching rule
+ * assertion.
+ */
+ static final byte TYPE_MATCHING_RULE_ID = (byte) 0x81;
+
+ /**
+ * The BER type to use for the attribute type in a matching rule
+ * assertion.
+ */
+ static final byte TYPE_MATCHING_RULE_TYPE = (byte) 0x82;
+
+ /**
+ * The BER type to use for the assertion value in a matching rule
+ * assertion.
+ */
+ static final byte TYPE_MATCHING_RULE_VALUE = (byte) 0x83;
+
+ /**
+ * The BER type to use for the DN attributes flag in a matching rule
+ * assertion.
+ */
+ static final byte TYPE_MATCHING_RULE_DN_ATTRIBUTES = (byte) 0x84;
+
+ /**
+ * The BER type to use for the newSuperior component of a modify DN
+ * request.
+ */
+ static final byte TYPE_MODIFY_DN_NEW_SUPERIOR = (byte) 0x80;
+
+ /**
+ * The BER type to use for the OID of an extended request.
+ */
+ static final byte TYPE_EXTENDED_REQUEST_OID = (byte) 0x80;
+
+ /**
+ * The BER type to use for the value of an extended request.
+ */
+ static final byte TYPE_EXTENDED_REQUEST_VALUE = (byte) 0x81;
+
+ /**
+ * The BER type to use for the OID of an extended response.
+ */
+ static final byte TYPE_EXTENDED_RESPONSE_OID = (byte) 0x8A;
+
+ /**
+ * The BER type to use for the value of an extended response.
+ */
+ static final byte TYPE_EXTENDED_RESPONSE_VALUE = (byte) 0x8B;
+
+ /**
+ * The BER type to use for the OID of an intermediate response
+ * message.
+ */
+ static final byte TYPE_INTERMEDIATE_RESPONSE_OID = (byte) 0x80;
+
+ /**
+ * The BER type to use for the value of an intermediate response
+ * message.
+ */
+ static final byte TYPE_INTERMEDIATE_RESPONSE_VALUE = (byte) 0x81;
+
+ /**
+ * The OID for the Kerberos V GSSAPI mechanism.
+ */
+ static final String OID_GSSAPI_KERBEROS_V = "1.2.840.113554.1.2.2";
+
+ /**
+ * The OID for the LDAP notice of disconnection extended operation.
+ */
+ static final String OID_NOTICE_OF_DISCONNECTION = "1.3.6.1.4.1.1466.20036";
+
+ /**
+ * The ASN.1 element decoding state that indicates that the next byte
+ * read should be the BER type for a new element.
+ */
+ static final int ELEMENT_READ_STATE_NEED_TYPE = 0;
+
+ /**
+ * The ASN.1 element decoding state that indicates that the next byte
+ * read should be the first byte for the element length.
+ */
+ static final int ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE = 1;
+
+ /**
+ * The ASN.1 element decoding state that indicates that the next byte
+ * read should be additional bytes of a multi-byte length.
+ */
+ static final int ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES = 2;
+
+ /**
+ * The ASN.1 element decoding state that indicates that the next byte
+ * read should be applied to the value of the element.
+ */
+ static final int ELEMENT_READ_STATE_NEED_VALUE_BYTES = 3;
+}
diff --git a/sdk/src/org/opends/sdk/ldap/LDAPDecoder.java b/sdk/src/org/opends/sdk/ldap/LDAPDecoder.java
new file mode 100644
index 0000000..3b658b3
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/LDAPDecoder.java
@@ -0,0 +1,1922 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import static org.opends.messages.ProtocolMessages.*;
+import static org.opends.sdk.asn1.ASN1Constants.*;
+import static org.opends.sdk.ldap.LDAPConstants.*;
+
+import java.io.IOException;
+import java.util.logging.Level;
+
+import org.opends.sdk.*;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.requests.*;
+import org.opends.sdk.responses.*;
+import org.opends.sdk.sasl.GenericSASLBindRequest;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * Static methods for decoding LDAP messages.
+ */
+class LDAPDecoder
+{
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as an LDAP
+ * message.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will handle a
+ * decoded message.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ static void decode(ASN1Reader reader, LDAPMessageHandler handler)
+ throws IOException
+ {
+ reader.readStartSequence();
+ try
+ {
+ int messageID = (int) reader.readInteger();
+ decodeProtocolOp(reader, messageID, handler);
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+ }
+
+
+
+ static SearchResultEntry decodeEntry(ASN1Reader reader, Schema schema)
+ throws IOException
+ {
+ SearchResultEntry message;
+
+ reader.readStartSequence(OP_TYPE_SEARCH_RESULT_ENTRY);
+ try
+ {
+ String dnString = reader.readOctetStringAsString();
+ DN dn;
+ try
+ {
+ dn = DN.valueOf(dnString, schema);
+ }
+ catch (LocalizedIllegalArgumentException e)
+ {
+ throw DecodeException.error(e.getMessageObject());
+ }
+ message = Responses.newSearchResultEntry(dn);
+
+ reader.readStartSequence();
+ try
+ {
+ while (reader.hasNextElement())
+ {
+ reader.readStartSequence();
+ try
+ {
+ String ads = reader.readOctetStringAsString();
+ AttributeDescription ad;
+ try
+ {
+ ad = AttributeDescription.valueOf(ads, schema);
+ }
+ catch (LocalizedIllegalArgumentException e)
+ {
+ throw DecodeException.error(e.getMessageObject());
+ }
+ Attribute attribute = new LinkedAttribute(ad);
+
+ reader.readStartSet();
+ try
+ {
+ while (reader.hasNextElement())
+ {
+ attribute.add(reader.readOctetString());
+ }
+ message.addAttribute(attribute);
+ }
+ finally
+ {
+ reader.readEndSet();
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ return message;
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 read as an LDAP
+ * abandon request protocol op.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will handle this
+ * decoded message.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeAbandonRequest(ASN1Reader reader,
+ int messageID, LDAPMessageHandler handler) throws IOException
+ {
+ int msgToAbandon = (int) reader
+ .readInteger(OP_TYPE_ABANDON_REQUEST);
+ AbandonRequest message = Requests.newAbandonRequest(msgToAbandon);
+
+ decodeControls(reader, message, messageID, handler);
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "DECODE LDAP ABANDON REQUEST(messageID=%d, request=%s)",
+ messageID, message));
+ }
+
+ handler.handleAbandonRequest(messageID, message);
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as an LDAP add
+ * request protocol op.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will handle this
+ * decoded message.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeAddRequest(ASN1Reader reader,
+ int messageID, LDAPMessageHandler handler) throws IOException
+ {
+ AddRequest message;
+ ResolvedSchema resolvedSchema;
+
+ reader.readStartSequence(OP_TYPE_ADD_REQUEST);
+ try
+ {
+ String dnString = reader.readOctetStringAsString();
+ resolvedSchema = handler.resolveSchema(dnString);
+ DN dn = resolvedSchema.getInitialDN();
+ message = Requests.newAddRequest(dn);
+
+ reader.readStartSequence();
+ try
+ {
+ while (reader.hasNextElement())
+ {
+ reader.readStartSequence();
+ try
+ {
+ String ads = reader.readOctetStringAsString();
+ AttributeDescription ad = resolvedSchema
+ .decodeAttributeDescription(ads);
+ Attribute attribute = new LinkedAttribute(ad);
+
+ reader.readStartSet();
+ try
+ {
+ while (reader.hasNextElement())
+ {
+ attribute.add(reader.readOctetString());
+ }
+ message.addAttribute(attribute);
+ }
+ finally
+ {
+ reader.readEndSet();
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ decodeControls(reader, message, messageID, handler, resolvedSchema
+ .getSchema());
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "DECODE LDAP ADD REQUEST(messageID=%d, request=%s)",
+ messageID, message));
+ }
+
+ handler.handleAddRequest(messageID, message);
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as an add
+ * response protocol op.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will handle this
+ * decoded message.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeAddResult(ASN1Reader reader, int messageID,
+ LDAPMessageHandler handler) throws IOException
+ {
+ Result message;
+
+ reader.readStartSequence(OP_TYPE_ADD_RESPONSE);
+ try
+ {
+ ResultCode resultCode = ResultCode.valueOf(reader
+ .readEnumerated());
+ String matchedDN = reader.readOctetStringAsString();
+ String diagnosticMessage = reader.readOctetStringAsString();
+ message = Responses.newResult(resultCode).setMatchedDN(matchedDN)
+ .setDiagnosticMessage(diagnosticMessage);
+ decodeResponseReferrals(reader, message);
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ decodeControls(reader, message, messageID, handler);
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "DECODE LDAP ADD RESULT(messageID=%d, result=%s)", messageID,
+ message));
+ }
+
+ handler.handleAddResult(messageID, message);
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 read as an LDAP bind
+ * request protocol op.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will handle this
+ * decoded message.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeBindRequest(ASN1Reader reader,
+ int messageID, LDAPMessageHandler handler) throws IOException
+ {
+ reader.readStartSequence(OP_TYPE_BIND_REQUEST);
+ try
+ {
+ int protocolVersion = (int) reader.readInteger();
+
+ String dnString = reader.readOctetStringAsString();
+ ResolvedSchema resolvedSchema = handler.resolveSchema(dnString);
+ DN dn = resolvedSchema.getInitialDN();
+
+ byte type = reader.peekType();
+
+ switch (type)
+ {
+ case TYPE_AUTHENTICATION_SIMPLE:
+ ByteString simplePassword = reader
+ .readOctetString(TYPE_AUTHENTICATION_SIMPLE);
+
+ SimpleBindRequest simpleBindMessage = Requests.newSimpleBindRequest(dn, simplePassword);
+
+ decodeControls(reader, simpleBindMessage, messageID, handler,
+ resolvedSchema.getSchema());
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG
+ .finer(String
+ .format(
+ "DECODE LDAP BIND REQUEST(messageID=%d, auth=simple, request=%s)",
+ messageID, simpleBindMessage));
+ }
+
+ handler.handleBindRequest(messageID, protocolVersion,
+ simpleBindMessage);
+ break;
+ case TYPE_AUTHENTICATION_SASL:
+ String saslMechanism;
+ ByteString saslCredentials;
+
+ reader.readStartSequence(TYPE_AUTHENTICATION_SASL);
+ try
+ {
+ saslMechanism = reader.readOctetStringAsString();
+ if (reader.hasNextElement()
+ && (reader.peekType() == UNIVERSAL_OCTET_STRING_TYPE))
+ {
+ saslCredentials = reader.readOctetString();
+ }
+ else
+ {
+ saslCredentials = ByteString.empty();
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ GenericSASLBindRequest rawSASLBindMessage = new GenericSASLBindRequest(
+ saslMechanism, saslCredentials);
+
+ // TODO: we can ignore the bind DN for SASL bind requests
+ // according to the RFC.
+ //
+ // rawSASLBindMessage.setName(dn);
+
+ decodeControls(reader, rawSASLBindMessage, messageID, handler,
+ resolvedSchema.getSchema());
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG
+ .finer(String
+ .format(
+ "DECODE LDAP BIND REQUEST(messageID=%d, auth=SASL, request=%s)",
+ messageID, rawSASLBindMessage));
+ }
+
+ handler.handleBindRequest(messageID, protocolVersion,
+ rawSASLBindMessage);
+ break;
+ default:
+ ByteString unknownAuthBytes = reader.readOctetString(type);
+
+ GenericBindRequest rawUnknownBindMessage = Requests.newGenericBindRequest(dn, type, unknownAuthBytes);
+
+ decodeControls(reader, rawUnknownBindMessage, messageID,
+ handler, resolvedSchema.getSchema());
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG
+ .finer(String
+ .format(
+ "DECODE LDAP BIND REQUEST(messageID=%d, auth=0x%x, request=%s)",
+ messageID, rawUnknownBindMessage
+ .getAuthenticationType(),
+ rawUnknownBindMessage));
+ }
+
+ handler.handleBindRequest(messageID, protocolVersion,
+ rawUnknownBindMessage);
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as a bind
+ * response protocol op.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will handle this
+ * decoded message.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeBindResult(ASN1Reader reader,
+ int messageID, LDAPMessageHandler handler) throws IOException
+ {
+ BindResult message;
+
+ reader.readStartSequence(OP_TYPE_BIND_RESPONSE);
+ try
+ {
+ ResultCode resultCode = ResultCode.valueOf(reader
+ .readEnumerated());
+ String matchedDN = reader.readOctetStringAsString();
+ String diagnosticMessage = reader.readOctetStringAsString();
+ message = Responses.newBindResult(resultCode).setMatchedDN(matchedDN)
+ .setDiagnosticMessage(diagnosticMessage);
+ decodeResponseReferrals(reader, message);
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_SERVER_SASL_CREDENTIALS))
+ {
+ message.setServerSASLCredentials(reader
+ .readOctetString(TYPE_SERVER_SASL_CREDENTIALS));
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ decodeControls(reader, message, messageID, handler);
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "DECODE LDAP BIND RESULT(messageID=%d, result=%s)",
+ messageID, message));
+ }
+
+ handler.handleBindResult(messageID, message);
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as an LDAP
+ * compare request protocol op.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will handle this
+ * decoded message.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeCompareRequest(ASN1Reader reader,
+ int messageID, LDAPMessageHandler handler) throws IOException
+ {
+ DN dn;
+ AttributeDescription ad;
+ ByteString assertionValue;
+ ResolvedSchema resolvedSchema;
+
+ reader.readStartSequence(OP_TYPE_COMPARE_REQUEST);
+ try
+ {
+ String dnString = reader.readOctetStringAsString();
+ resolvedSchema = handler.resolveSchema(dnString);
+ dn = resolvedSchema.getInitialDN();
+
+ reader.readStartSequence();
+ try
+ {
+ String ads = reader.readOctetStringAsString();
+ ad = resolvedSchema.decodeAttributeDescription(ads);
+ assertionValue = reader.readOctetString();
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ CompareRequest message = Requests.newCompareRequest(dn, ad, assertionValue);
+ decodeControls(reader, message, messageID, handler, resolvedSchema
+ .getSchema());
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "DECODE LDAP COMPARE REQUEST(messageID=%d, request=%s)",
+ messageID, message));
+ }
+
+ handler.handleCompareRequest(messageID, message);
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as a compare
+ * response protocol op.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will handle this
+ * decoded message.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeCompareResult(ASN1Reader reader,
+ int messageID, LDAPMessageHandler handler) throws IOException
+ {
+ CompareResult message;
+
+ reader.readStartSequence(OP_TYPE_COMPARE_RESPONSE);
+ try
+ {
+ ResultCode resultCode = ResultCode.valueOf(reader
+ .readEnumerated());
+ String matchedDN = reader.readOctetStringAsString();
+ String diagnosticMessage = reader.readOctetStringAsString();
+ message = Responses.newCompareResult(resultCode).setMatchedDN(matchedDN)
+ .setDiagnosticMessage(diagnosticMessage);
+ decodeResponseReferrals(reader, message);
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ decodeControls(reader, message, messageID, handler);
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "DECODE LDAP COMPARE RESULT(messageID=%d, result=%s)",
+ messageID, message));
+ }
+
+ handler.handleCompareResult(messageID, message);
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as an LDAP
+ * control.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param request
+ * The decoded request to decode controls for.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will decode the
+ * control.
+ * @param schema
+ * The schema to use when decoding control.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeControl(ASN1Reader reader, Request request,
+ int messageID, LDAPMessageHandler handler, Schema schema)
+ throws IOException
+ {
+ String oid;
+ boolean isCritical;
+ ByteString value;
+
+ reader.readStartSequence();
+ try
+ {
+ oid = reader.readOctetStringAsString();
+ isCritical = false;
+ value = null;
+ if (reader.hasNextElement()
+ && (reader.peekType() == UNIVERSAL_BOOLEAN_TYPE))
+ {
+ isCritical = reader.readBoolean();
+ }
+ if (reader.hasNextElement()
+ && (reader.peekType() == UNIVERSAL_OCTET_STRING_TYPE))
+ {
+ value = reader.readOctetString();
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ try
+ {
+ Control c = handler.decodeRequestControl(messageID, oid,
+ isCritical, value, schema);
+ request.addControl(c);
+ }
+ catch (DecodeException e)
+ {
+ if (isCritical)
+ {
+ if (e.isFatal())
+ {
+ throw DecodeException.error(e.getMessageObject(), e
+ .getCause());
+ }
+ else
+ {
+ // Exceptions encountered when decoding controls are never
+ // fatal.
+ throw e;
+ }
+ }
+ }
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as an LDAP
+ * control.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param response
+ * The decoded message to decode controls for.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will decode the
+ * control.
+ * @param schema
+ * The schema to use when decoding control.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeControl(ASN1Reader reader,
+ Response response, int messageID, LDAPMessageHandler handler,
+ Schema schema) throws IOException
+ {
+ String oid;
+ boolean isCritical;
+ ByteString value;
+
+ reader.readStartSequence();
+ try
+ {
+ oid = reader.readOctetStringAsString();
+ isCritical = false;
+ value = null;
+ if (reader.hasNextElement()
+ && (reader.peekType() == UNIVERSAL_BOOLEAN_TYPE))
+ {
+ isCritical = reader.readBoolean();
+ }
+ if (reader.hasNextElement()
+ && (reader.peekType() == UNIVERSAL_OCTET_STRING_TYPE))
+ {
+ value = reader.readOctetString();
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ try
+ {
+ Control c = handler.decodeResponseControl(messageID, oid,
+ isCritical, value, schema);
+ response.addControl(c);
+ }
+ catch (DecodeException e)
+ {
+ if (isCritical)
+ {
+ if (e.isFatal())
+ {
+ throw DecodeException.error(e.getMessageObject(), e
+ .getCause());
+ }
+ else
+ {
+ // Exceptions encountered when decoding controls are never
+ // fatal.
+ throw e;
+ }
+ }
+ }
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as a set of
+ * controls using the default schema.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param request
+ * The decoded message to decode controls for.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will decode the
+ * controls.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeControls(ASN1Reader reader,
+ Request request, int messageID, LDAPMessageHandler handler)
+ throws IOException
+ {
+ decodeControls(reader, request, messageID, handler, handler
+ .getDefaultSchema());
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as a set of
+ * controls.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param request
+ * The decoded message to decode controls for.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will decode the
+ * controls.
+ * @param schema
+ * The schema to use when decoding controls.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeControls(ASN1Reader reader,
+ Request request, int messageID, LDAPMessageHandler handler,
+ Schema schema) throws IOException
+ {
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_CONTROL_SEQUENCE))
+ {
+ reader.readStartSequence(TYPE_CONTROL_SEQUENCE);
+ try
+ {
+ while (reader.hasNextElement())
+ {
+ decodeControl(reader, request, messageID, handler, schema);
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+ }
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as a set of
+ * controls using the default schema.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param response
+ * The decoded message to decode controls for.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will decode the
+ * controls.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeControls(ASN1Reader reader,
+ Response response, int messageID, LDAPMessageHandler handler)
+ throws IOException
+ {
+ decodeControls(reader, response, messageID, handler, handler
+ .getDefaultSchema());
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as a set of
+ * controls.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param response
+ * The decoded message to decode controls for.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will decode the
+ * controls.
+ * @param schema
+ * The schema to use when decoding control.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeControls(ASN1Reader reader,
+ Response response, int messageID, LDAPMessageHandler handler,
+ Schema schema) throws IOException
+ {
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_CONTROL_SEQUENCE))
+ {
+ reader.readStartSequence(TYPE_CONTROL_SEQUENCE);
+ try
+ {
+ while (reader.hasNextElement())
+ {
+ decodeControl(reader, response, messageID, handler, schema);
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+ }
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as an LDAP
+ * delete request protocol op.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will handle this
+ * decoded message.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeDeleteRequest(ASN1Reader reader,
+ int messageID, LDAPMessageHandler handler) throws IOException
+ {
+ String dnString = reader
+ .readOctetStringAsString(OP_TYPE_DELETE_REQUEST);
+ ResolvedSchema resolvedSchema = handler.resolveSchema(dnString);
+ DN dn = resolvedSchema.getInitialDN();
+ DeleteRequest message = Requests.newDeleteRequest(dn);
+
+ decodeControls(reader, message, messageID, handler, resolvedSchema
+ .getSchema());
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "DECODE LDAP DELETE REQUEST(messageID=%d, request=%s)",
+ messageID, message));
+ }
+
+ handler.handleDeleteRequest(messageID, message);
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as a delete
+ * response protocol op.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will handle this
+ * decoded message.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeDeleteResult(ASN1Reader reader,
+ int messageID, LDAPMessageHandler handler) throws IOException
+ {
+ Result message;
+
+ reader.readStartSequence(OP_TYPE_DELETE_RESPONSE);
+ try
+ {
+ ResultCode resultCode = ResultCode.valueOf(reader
+ .readEnumerated());
+ String matchedDN = reader.readOctetStringAsString();
+ String diagnosticMessage = reader.readOctetStringAsString();
+ message = Responses.newResult(resultCode).setMatchedDN(matchedDN)
+ .setDiagnosticMessage(diagnosticMessage);
+ decodeResponseReferrals(reader, message);
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ decodeControls(reader, message, messageID, handler);
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "DECODE LDAP DELETE RESULT(messageID=%d, result=%s)",
+ messageID, message));
+ }
+
+ handler.handleDeleteResult(messageID, message);
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as an LDAP
+ * extended request protocol op.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will handle this
+ * decoded message.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeExtendedRequest(ASN1Reader reader,
+ int messageID, LDAPMessageHandler handler) throws IOException
+ {
+ String oid;
+ ByteString value;
+
+ reader.readStartSequence(OP_TYPE_EXTENDED_REQUEST);
+ try
+ {
+ oid = reader.readOctetStringAsString(TYPE_EXTENDED_REQUEST_OID);
+ value = null;
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_EXTENDED_REQUEST_VALUE))
+ {
+ value = reader.readOctetString(TYPE_EXTENDED_REQUEST_VALUE);
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ GenericExtendedRequest message = Requests.newGenericExtendedRequest(oid, value);
+
+ decodeControls(reader, message, messageID, handler);
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "DECODE LDAP EXTENDED REQUEST(messageID=%d, request=%s)",
+ messageID, message));
+ }
+
+ handler.handleExtendedRequest(messageID, message);
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as a extended
+ * response protocol op.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will handle this
+ * decoded message.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeExtendedResult(ASN1Reader reader,
+ int messageID, LDAPMessageHandler handler) throws IOException
+ {
+
+ GenericExtendedResult message;
+
+ reader.readStartSequence(OP_TYPE_EXTENDED_RESPONSE);
+ try
+ {
+ ResultCode resultCode = ResultCode.valueOf(reader
+ .readEnumerated());
+ String matchedDN = reader.readOctetStringAsString();
+ String diagnosticMessage = reader.readOctetStringAsString();
+ message = Responses.newGenericExtendedResult(resultCode).setMatchedDN(
+ matchedDN).setDiagnosticMessage(diagnosticMessage);
+ decodeResponseReferrals(reader, message);
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_EXTENDED_RESPONSE_OID))
+ {
+ message.setResponseName(reader
+ .readOctetStringAsString(TYPE_EXTENDED_RESPONSE_OID));
+ }
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_EXTENDED_RESPONSE_VALUE))
+ {
+ message.setResponseValue(reader
+ .readOctetString(TYPE_EXTENDED_RESPONSE_VALUE));
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ decodeControls(reader, message, messageID, handler);
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "DECODE LDAP EXTENDED RESULT(messageID=%d, result=%s)",
+ messageID, message));
+ }
+
+ handler.handleExtendedResult(messageID, message);
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as an LDAP
+ * intermediate response protocol op.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will handle this
+ * decoded message.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeIntermediateResponse(ASN1Reader reader,
+ int messageID, LDAPMessageHandler handler) throws IOException
+ {
+ GenericIntermediateResponse message;
+
+ reader.readStartSequence(OP_TYPE_INTERMEDIATE_RESPONSE);
+ try
+ {
+ message = Responses.newGenericIntermediateResponse();
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_INTERMEDIATE_RESPONSE_OID))
+ {
+ message.setResponseName(reader
+ .readOctetStringAsString(TYPE_INTERMEDIATE_RESPONSE_OID));
+ }
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_INTERMEDIATE_RESPONSE_VALUE))
+ {
+ message.setResponseValue(reader
+ .readOctetString(TYPE_INTERMEDIATE_RESPONSE_VALUE));
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ decodeControls(reader, message, messageID, handler);
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG
+ .finer(String
+ .format(
+ "DECODE LDAP INTERMEDIATE RESPONSE(messageID=%d, response=%s)",
+ messageID, message));
+ }
+
+ handler.handleIntermediateResponse(messageID, message);
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as a modify DN
+ * request protocol op.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will handle this
+ * decoded message.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeModifyDNRequest(ASN1Reader reader,
+ int messageID, LDAPMessageHandler handler) throws IOException
+ {
+ ModifyDNRequest message;
+ ResolvedSchema resolvedSchema;
+
+ reader.readStartSequence(OP_TYPE_MODIFY_DN_REQUEST);
+ try
+ {
+ String dnString = reader.readOctetStringAsString();
+ resolvedSchema = handler.resolveSchema(dnString);
+ DN dn = resolvedSchema.getInitialDN();
+
+ String newRDNString = reader.readOctetStringAsString();
+ RDN newRDN = resolvedSchema.decodeRDN(newRDNString);
+
+ message = Requests.newModifyDNRequest(dn, newRDN);
+
+ message.setDeleteOldRDN(reader.readBoolean());
+
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_MODIFY_DN_NEW_SUPERIOR))
+ {
+ String newSuperiorString = reader
+ .readOctetStringAsString(TYPE_MODIFY_DN_NEW_SUPERIOR);
+ DN newSuperior = resolvedSchema.decodeDN(newSuperiorString);
+ message.setNewSuperior(newSuperior);
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ decodeControls(reader, message, messageID, handler, resolvedSchema
+ .getSchema());
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "DECODE LDAP MODIFY DN REQUEST(messageID=%d, request=%s)",
+ messageID, message));
+ }
+
+ handler.handleModifyDNRequest(messageID, message);
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as a modify DN
+ * response protocol op.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will handle this
+ * decoded message.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeModifyDNResult(ASN1Reader reader,
+ int messageID, LDAPMessageHandler handler) throws IOException
+ {
+ Result message;
+
+ reader.readStartSequence(OP_TYPE_MODIFY_DN_RESPONSE);
+ try
+ {
+ ResultCode resultCode = ResultCode.valueOf(reader
+ .readEnumerated());
+ String matchedDN = reader.readOctetStringAsString();
+ String diagnosticMessage = reader.readOctetStringAsString();
+ message = Responses.newResult(resultCode).setMatchedDN(matchedDN)
+ .setDiagnosticMessage(diagnosticMessage);
+ decodeResponseReferrals(reader, message);
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ decodeControls(reader, message, messageID, handler);
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "DECODE LDAP MODIFY DN RESULT(messageID=%d, result=%s)",
+ messageID, message));
+ }
+
+ handler.handleModifyDNResult(messageID, message);
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as an LDAP
+ * modify request protocol op.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will handle this
+ * decoded message.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeModifyRequest(ASN1Reader reader,
+ int messageID, LDAPMessageHandler handler) throws IOException
+ {
+ ModifyRequest message;
+ ResolvedSchema resolvedSchema;
+
+ reader.readStartSequence(OP_TYPE_MODIFY_REQUEST);
+ try
+ {
+ String dnString = reader.readOctetStringAsString();
+ resolvedSchema = handler.resolveSchema(dnString);
+ DN dn = resolvedSchema.getInitialDN();
+ message = Requests.newModifyRequest(dn);
+
+ reader.readStartSequence();
+ try
+ {
+ while (reader.hasNextElement())
+ {
+ reader.readStartSequence();
+ try
+ {
+ int typeIntValue = reader.readEnumerated();
+ ModificationType type = ModificationType
+ .valueOf(typeIntValue);
+ if (type == null)
+ {
+ throw DecodeException
+ .error(ERR_LDAP_MODIFICATION_DECODE_INVALID_MOD_TYPE
+ .get(typeIntValue));
+ }
+ reader.readStartSequence();
+ try
+ {
+ String attributeDescription = reader
+ .readOctetStringAsString();
+ Attribute attribute = new LinkedAttribute(resolvedSchema
+ .decodeAttributeDescription(attributeDescription));
+
+ reader.readStartSet();
+ try
+ {
+ while (reader.hasNextElement())
+ {
+ attribute.add(reader.readOctetString());
+ }
+ message.addChange(new Change(type, attribute));
+ }
+ finally
+ {
+ reader.readEndSet();
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ decodeControls(reader, message, messageID, handler, resolvedSchema
+ .getSchema());
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "DECODE LDAP MODIFY REQUEST(messageID=%d, request=%s)",
+ messageID, message));
+ }
+
+ handler.handleModifyRequest(messageID, message);
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as a modify
+ * response protocol op.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will handle this
+ * decoded message.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeModifyResult(ASN1Reader reader,
+ int messageID, LDAPMessageHandler handler) throws IOException
+ {
+ Result message;
+
+ reader.readStartSequence(OP_TYPE_MODIFY_RESPONSE);
+ try
+ {
+ ResultCode resultCode = ResultCode.valueOf(reader
+ .readEnumerated());
+ String matchedDN = reader.readOctetStringAsString();
+ String diagnosticMessage = reader.readOctetStringAsString();
+ message = Responses.newResult(resultCode).setMatchedDN(matchedDN)
+ .setDiagnosticMessage(diagnosticMessage);
+ decodeResponseReferrals(reader, message);
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ decodeControls(reader, message, messageID, handler);
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "DECODE LDAP MODIFY RESULT(messageID=%d, result=%s)",
+ messageID, message));
+ }
+
+ handler.handleModifyResult(messageID, message);
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as an LDAP
+ * protocol op.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will handle this
+ * decoded message.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeProtocolOp(ASN1Reader reader,
+ int messageID, LDAPMessageHandler handler) throws IOException
+ {
+ byte type = reader.peekType();
+
+ switch (type)
+ {
+ case OP_TYPE_UNBIND_REQUEST: // 0x42
+ decodeUnbindRequest(reader, messageID, handler);
+ break;
+ case 0x43: // 0x43
+ case 0x44: // 0x44
+ case 0x45: // 0x45
+ case 0x46: // 0x46
+ case 0x47: // 0x47
+ case 0x48: // 0x48
+ case 0x49: // 0x49
+ handler.handleUnrecognizedMessage(messageID, type, reader
+ .readOctetString(type));
+ break;
+ case OP_TYPE_DELETE_REQUEST: // 0x4A
+ decodeDeleteRequest(reader, messageID, handler);
+ break;
+ case 0x4B: // 0x4B
+ case 0x4C: // 0x4C
+ case 0x4D: // 0x4D
+ case 0x4E: // 0x4E
+ case 0x4F: // 0x4F
+ handler.handleUnrecognizedMessage(messageID, type, reader
+ .readOctetString(type));
+ break;
+ case OP_TYPE_ABANDON_REQUEST: // 0x50
+ decodeAbandonRequest(reader, messageID, handler);
+ break;
+ case 0x51: // 0x51
+ case 0x52: // 0x52
+ case 0x53: // 0x53
+ case 0x54: // 0x54
+ case 0x55: // 0x55
+ case 0x56: // 0x56
+ case 0x57: // 0x57
+ case 0x58: // 0x58
+ case 0x59: // 0x59
+ case 0x5A: // 0x5A
+ case 0x5B: // 0x5B
+ case 0x5C: // 0x5C
+ case 0x5D: // 0x5D
+ case 0x5E: // 0x5E
+ case 0x5F: // 0x5F
+ handler.handleUnrecognizedMessage(messageID, type, reader
+ .readOctetString(type));
+ break;
+ case OP_TYPE_BIND_REQUEST: // 0x60
+ decodeBindRequest(reader, messageID, handler);
+ break;
+ case OP_TYPE_BIND_RESPONSE: // 0x61
+ decodeBindResult(reader, messageID, handler);
+ break;
+ case 0x62: // 0x62
+ handler.handleUnrecognizedMessage(messageID, type, reader
+ .readOctetString(type));
+ break;
+ case OP_TYPE_SEARCH_REQUEST: // 0x63
+ decodeSearchRequest(reader, messageID, handler);
+ break;
+ case OP_TYPE_SEARCH_RESULT_ENTRY: // 0x64
+ decodeSearchResultEntry(reader, messageID, handler);
+ break;
+ case OP_TYPE_SEARCH_RESULT_DONE: // 0x65
+ decodeSearchResult(reader, messageID, handler);
+ break;
+ case OP_TYPE_MODIFY_REQUEST: // 0x66
+ decodeModifyRequest(reader, messageID, handler);
+ break;
+ case OP_TYPE_MODIFY_RESPONSE: // 0x67
+ decodeModifyResult(reader, messageID, handler);
+ break;
+ case OP_TYPE_ADD_REQUEST: // 0x68
+ decodeAddRequest(reader, messageID, handler);
+ break;
+ case OP_TYPE_ADD_RESPONSE: // 0x69
+ decodeAddResult(reader, messageID, handler);
+ break;
+ case 0x6A: // 0x6A
+ handler.handleUnrecognizedMessage(messageID, type, reader
+ .readOctetString(type));
+ break;
+ case OP_TYPE_DELETE_RESPONSE: // 0x6B
+ decodeDeleteResult(reader, messageID, handler);
+ break;
+ case OP_TYPE_MODIFY_DN_REQUEST: // 0x6C
+ decodeModifyDNRequest(reader, messageID, handler);
+ break;
+ case OP_TYPE_MODIFY_DN_RESPONSE: // 0x6D
+ decodeModifyDNResult(reader, messageID, handler);
+ break;
+ case OP_TYPE_COMPARE_REQUEST: // 0x6E
+ decodeCompareRequest(reader, messageID, handler);
+ break;
+ case OP_TYPE_COMPARE_RESPONSE: // 0x6F
+ decodeCompareResult(reader, messageID, handler);
+ break;
+ case 0x70: // 0x70
+ case 0x71: // 0x71
+ case 0x72: // 0x72
+ handler.handleUnrecognizedMessage(messageID, type, reader
+ .readOctetString(type));
+ break;
+ case OP_TYPE_SEARCH_RESULT_REFERENCE: // 0x73
+ decodeSearchResultReference(reader, messageID, handler);
+ break;
+ case 0x74: // 0x74
+ case 0x75: // 0x75
+ case 0x76: // 0x76
+ handler.handleUnrecognizedMessage(messageID, type, reader
+ .readOctetString(type));
+ break;
+ case OP_TYPE_EXTENDED_REQUEST: // 0x77
+ decodeExtendedRequest(reader, messageID, handler);
+ break;
+ case OP_TYPE_EXTENDED_RESPONSE: // 0x78
+ decodeExtendedResult(reader, messageID, handler);
+ break;
+ case OP_TYPE_INTERMEDIATE_RESPONSE: // 0x79
+ decodeIntermediateResponse(reader, messageID, handler);
+ break;
+ default:
+ handler.handleUnrecognizedMessage(messageID, type, reader
+ .readOctetString(type));
+ break;
+ }
+ }
+
+
+
+ private static void decodeResponseReferrals(ASN1Reader reader,
+ Result message) throws IOException
+ {
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_REFERRAL_SEQUENCE))
+ {
+ reader.readStartSequence(TYPE_REFERRAL_SEQUENCE);
+ try
+ {
+ // Should have at least 1.
+ do
+ {
+ message.addReferralURI((reader.readOctetStringAsString()));
+ } while (reader.hasNextElement());
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+ }
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as a search
+ * result done protocol op.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will handle this
+ * decoded message.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeSearchResult(ASN1Reader reader,
+ int messageID, LDAPMessageHandler handler) throws IOException
+ {
+
+ Result message;
+
+ reader.readStartSequence(OP_TYPE_SEARCH_RESULT_DONE);
+ try
+ {
+ ResultCode resultCode = ResultCode.valueOf(reader
+ .readEnumerated());
+ String matchedDN = reader.readOctetStringAsString();
+ String diagnosticMessage = reader.readOctetStringAsString();
+ message = Responses.newResult(resultCode).setMatchedDN(matchedDN)
+ .setDiagnosticMessage(diagnosticMessage);
+ decodeResponseReferrals(reader, message);
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ decodeControls(reader, message, messageID, handler);
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "DECODE LDAP SEARCH RESULT(messageID=%d, result=%s)",
+ messageID, message));
+ }
+
+ handler.handleSearchResult(messageID, message);
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as an LDAP
+ * search result entry protocol op.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will handle this
+ * decoded message.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeSearchResultEntry(ASN1Reader reader,
+ int messageID, LDAPMessageHandler handler) throws IOException
+ {
+ SearchResultEntry message;
+ ResolvedSchema resolvedSchema;
+
+ reader.readStartSequence(OP_TYPE_SEARCH_RESULT_ENTRY);
+ try
+ {
+ String dnString = reader.readOctetStringAsString();
+ resolvedSchema = handler.resolveSchema(dnString);
+ DN dn = resolvedSchema.getInitialDN();
+ message = Responses.newSearchResultEntry(dn);
+
+ reader.readStartSequence();
+ try
+ {
+ while (reader.hasNextElement())
+ {
+ reader.readStartSequence();
+ try
+ {
+ String ads = reader.readOctetStringAsString();
+ AttributeDescription ad = resolvedSchema
+ .decodeAttributeDescription(ads);
+ Attribute attribute = new LinkedAttribute(ad);
+
+ reader.readStartSet();
+ try
+ {
+ while (reader.hasNextElement())
+ {
+ attribute.add(reader.readOctetString());
+ }
+ message.addAttribute(attribute);
+ }
+ finally
+ {
+ reader.readEndSet();
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ decodeControls(reader, message, messageID, handler, resolvedSchema
+ .getSchema());
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "DECODE LDAP SEARCH RESULT ENTRY(messageID=%d, entry=%s)",
+ messageID, message));
+ }
+
+ handler.handleSearchResultEntry(messageID, message);
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as a search
+ * result reference protocol op.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will handle this
+ * decoded message.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeSearchResultReference(ASN1Reader reader,
+ int messageID, LDAPMessageHandler handler) throws IOException
+ {
+ SearchResultReference message;
+
+ reader.readStartSequence(OP_TYPE_SEARCH_RESULT_REFERENCE);
+ try
+ {
+ message = Responses.newSearchResultReference(reader
+ .readOctetStringAsString());
+ while (reader.hasNextElement())
+ {
+ message.addURI(reader.readOctetStringAsString());
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ decodeControls(reader, message, messageID, handler);
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG
+ .finer(String
+ .format(
+ "DECODE LDAP SEARCH RESULT REFERENCE(messageID=%d, reference=%s)",
+ messageID, message));
+ }
+
+ handler.handleSearchResultReference(messageID, message);
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 reader as an LDAP
+ * search request protocol op.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will handle this
+ * decoded message.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeSearchRequest(ASN1Reader reader,
+ int messageID, LDAPMessageHandler handler) throws IOException
+ {
+ SearchRequest message;
+ ResolvedSchema resolvedSchema;
+
+ reader.readStartSequence(OP_TYPE_SEARCH_REQUEST);
+ try
+ {
+ String baseDNString = reader.readOctetStringAsString();
+ resolvedSchema = handler.resolveSchema(baseDNString);
+ DN baseDN = resolvedSchema.getInitialDN();
+
+ int scopeIntValue = reader.readEnumerated();
+ SearchScope scope = SearchScope.valueOf(scopeIntValue);
+ if (scope == null)
+ {
+ throw DecodeException
+ .error(ERR_LDAP_SEARCH_REQUEST_DECODE_INVALID_SCOPE
+ .get(scopeIntValue));
+ }
+
+ int dereferencePolicyIntValue = reader.readEnumerated();
+ DereferenceAliasesPolicy dereferencePolicy = DereferenceAliasesPolicy
+ .valueOf(dereferencePolicyIntValue);
+ if (dereferencePolicy == null)
+ {
+ throw DecodeException
+ .error(ERR_LDAP_SEARCH_REQUEST_DECODE_INVALID_DEREF
+ .get(dereferencePolicyIntValue));
+ }
+
+ int sizeLimit = (int) reader.readInteger();
+ int timeLimit = (int) reader.readInteger();
+ boolean typesOnly = reader.readBoolean();
+ Filter filter = LDAPUtils.decodeFilter(reader);
+
+ message = Requests.newSearchRequest(baseDN, scope, filter);
+ message.setDereferenceAliasesPolicy(dereferencePolicy);
+ try
+ {
+ message.setTimeLimit(timeLimit);
+ message.setSizeLimit(sizeLimit);
+ }
+ catch (LocalizedIllegalArgumentException e)
+ {
+ throw DecodeException.error(e.getMessageObject());
+ }
+ message.setTypesOnly(typesOnly);
+
+ reader.readStartSequence();
+ try
+ {
+ while (reader.hasNextElement())
+ {
+ message.addAttribute(reader.readOctetStringAsString());
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ decodeControls(reader, message, messageID, handler, resolvedSchema
+ .getSchema());
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "DECODE LDAP SEARCH REQUEST(messageID=%d, request=%s)",
+ messageID, message));
+ }
+
+ handler.handleSearchRequest(messageID, message);
+ }
+
+
+
+ /**
+ * Decodes the elements from the provided ASN.1 read as an LDAP unbind
+ * request protocol op.
+ *
+ * @param reader
+ * The ASN.1 reader.
+ * @param messageID
+ * The decoded message ID for this message.
+ * @param handler
+ * The <code>LDAPMessageHandler</code> that will handle this
+ * decoded message.
+ * @throws IOException
+ * If an error occurred while reading bytes to decode.
+ */
+ private static void decodeUnbindRequest(ASN1Reader reader,
+ int messageID, LDAPMessageHandler handler) throws IOException
+ {
+ UnbindRequest message;
+ reader.readNull(OP_TYPE_UNBIND_REQUEST);
+ message = Requests.newUnbindRequest();
+
+ decodeControls(reader, message, messageID, handler);
+
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "DECODE LDAP UNBIND REQUEST(messageID=%d, request=%s)",
+ messageID, message));
+ }
+
+ handler.handleUnbindRequest(messageID, message);
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/LDAPEncoder.java b/sdk/src/org/opends/sdk/ldap/LDAPEncoder.java
new file mode 100644
index 0000000..1fe7632
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/LDAPEncoder.java
@@ -0,0 +1,723 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import static org.opends.sdk.ldap.LDAPConstants.*;
+
+import java.io.IOException;
+import java.util.logging.Level;
+
+import org.opends.sdk.Attribute;
+import org.opends.sdk.Change;
+import org.opends.sdk.DN;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.requests.*;
+import org.opends.sdk.responses.*;
+import org.opends.sdk.sasl.SASLBindRequest;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * Static methods for encoding LDAP messages.
+ */
+final class LDAPEncoder
+{
+ static void encodeAttribute(ASN1Writer writer, Attribute attribute)
+ throws IOException
+ {
+ writer.writeStartSequence();
+ writer
+ .writeOctetString(attribute.getAttributeDescriptionAsString());
+
+ writer.writeStartSet();
+ for (ByteString value : attribute)
+ {
+ writer.writeOctetString(value);
+ }
+ writer.writeEndSequence();
+
+ writer.writeEndSequence();
+ }
+
+
+
+ static void encodeControl(ASN1Writer writer, Control control)
+ throws IOException
+ {
+ writer.writeStartSequence();
+ writer.writeOctetString(control.getOID());
+ if (control.isCritical())
+ {
+ writer.writeBoolean(control.isCritical());
+ }
+ if (control.getValue() != null)
+ {
+ writer.writeOctetString(control.getValue());
+ }
+ writer.writeEndSequence();
+ }
+
+
+
+ static void encodeEntry(ASN1Writer writer,
+ SearchResultEntry searchResultEntry) throws IOException
+ {
+ writer.writeStartSequence(OP_TYPE_SEARCH_RESULT_ENTRY);
+ writer.writeOctetString(searchResultEntry.getName().toString());
+
+ writer.writeStartSequence();
+ for (Attribute attr : searchResultEntry.getAttributes())
+ {
+ encodeAttribute(writer, attr);
+ }
+ writer.writeEndSequence();
+ writer.writeEndSequence();
+ }
+
+
+
+ static void encodeAbandonRequest(ASN1Writer writer, int messageID,
+ AbandonRequest request) throws IOException
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "ENCODE LDAP ABANDON REQUEST(messageID=%d, request=%s)",
+ messageID, request));
+ }
+ encodeMessageHeader(writer, messageID);
+ writer
+ .writeInteger(OP_TYPE_ABANDON_REQUEST, request.getMessageID());
+ encodeMessageFooter(writer, request);
+ }
+
+
+
+ static void encodeAddRequest(ASN1Writer writer, int messageID,
+ AddRequest request) throws IOException
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "ENCODE LDAP ADD REQUEST(messageID=%d, request=%s)",
+ messageID, request));
+ }
+ encodeMessageHeader(writer, messageID);
+ writer.writeStartSequence(OP_TYPE_ADD_REQUEST);
+ writer.writeOctetString(request.getName().toString());
+
+ // Write the attributes
+ writer.writeStartSequence();
+ for (Attribute attr : request.getAttributes())
+ {
+ encodeAttribute(writer, attr);
+ }
+ writer.writeEndSequence();
+
+ writer.writeEndSequence();
+ encodeMessageFooter(writer, request);
+ }
+
+
+
+ static void encodeCompareRequest(ASN1Writer writer, int messageID,
+ CompareRequest request) throws IOException
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "ENCODE LDAP COMPARE REQUEST(messageID=%d, request=%s)",
+ messageID, request));
+ }
+ encodeMessageHeader(writer, messageID);
+ writer.writeStartSequence(OP_TYPE_COMPARE_REQUEST);
+ writer.writeOctetString(request.getName().toString());
+
+ writer.writeStartSequence();
+ writer.writeOctetString(request.getAttributeDescription()
+ .toString());
+ writer.writeOctetString(request.getAssertionValue());
+ writer.writeEndSequence();
+
+ writer.writeEndSequence();
+ encodeMessageFooter(writer, request);
+ }
+
+
+
+ static void encodeDeleteRequest(ASN1Writer writer, int messageID,
+ DeleteRequest request) throws IOException
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "ENCODE LDAP DELETE REQUEST(messageID=%d, request=%s)",
+ messageID, request));
+ }
+ encodeMessageHeader(writer, messageID);
+ writer.writeOctetString(OP_TYPE_DELETE_REQUEST, request.getName()
+ .toString());
+ encodeMessageFooter(writer, request);
+ }
+
+
+
+ static void encodeExtendedRequest(ASN1Writer writer, int messageID,
+ ExtendedRequest<?> request) throws IOException
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "ENCODE LDAP EXTENDED REQUEST(messageID=%d, request=%s)",
+ messageID, request));
+ }
+ encodeMessageHeader(writer, messageID);
+ writer.writeStartSequence(OP_TYPE_EXTENDED_REQUEST);
+ writer.writeOctetString(TYPE_EXTENDED_REQUEST_OID, request
+ .getRequestName());
+
+ ByteString requestValue = request.getRequestValue();
+ if (requestValue != null)
+ {
+ writer
+ .writeOctetString(TYPE_EXTENDED_REQUEST_VALUE, requestValue);
+ }
+
+ writer.writeEndSequence();
+ encodeMessageFooter(writer, request);
+ }
+
+
+
+ static void encodeBindRequest(ASN1Writer writer, int messageID,
+ int version, GenericBindRequest request) throws IOException
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG
+ .finer(String
+ .format(
+ "ENCODE LDAP BIND REQUEST(messageID=%d, auth=0x%x, request=%s)",
+ messageID, request.getAuthenticationType(), request));
+ }
+ encodeMessageHeader(writer, messageID);
+ writer.writeStartSequence(OP_TYPE_BIND_REQUEST);
+
+ writer.writeInteger(version);
+ writer.writeOctetString(request.getName().toString());
+
+ writer.writeOctetString(request.getAuthenticationType(), request
+ .getAuthenticationValue());
+
+ writer.writeEndSequence();
+ encodeMessageFooter(writer, request);
+ }
+
+
+
+ static void encodeBindRequest(ASN1Writer writer, int messageID,
+ int version, SASLBindRequest<?> request,
+ ByteString saslCredentials) throws IOException
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG
+ .finer(String
+ .format(
+ "ENCODE LDAP BIND REQUEST(messageID=%d, auth=SASL, request=%s)",
+ messageID, request));
+ }
+ encodeMessageHeader(writer, messageID);
+ writer.writeStartSequence(OP_TYPE_BIND_REQUEST);
+
+ writer.writeInteger(version);
+ writer.writeOctetString(request.getName().toString());
+
+ writer.writeStartSequence(TYPE_AUTHENTICATION_SASL);
+ writer.writeOctetString(request.getSASLMechanism());
+ if (saslCredentials != null)
+ {
+ writer.writeOctetString(saslCredentials);
+ }
+ writer.writeEndSequence();
+
+ writer.writeEndSequence();
+ encodeMessageFooter(writer, request);
+ }
+
+
+
+ static void encodeBindRequest(ASN1Writer writer, int messageID,
+ int version, SimpleBindRequest request) throws IOException
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG
+ .finer(String
+ .format(
+ "ENCODE LDAP BIND REQUEST(messageID=%d, auth=simple, request=%s)",
+ messageID, request));
+ }
+ encodeMessageHeader(writer, messageID);
+ writer.writeStartSequence(OP_TYPE_BIND_REQUEST);
+
+ writer.writeInteger(version);
+ writer.writeOctetString(request.getName().toString());
+ writer.writeOctetString(TYPE_AUTHENTICATION_SIMPLE, request
+ .getPassword());
+
+ writer.writeEndSequence();
+ encodeMessageFooter(writer, request);
+ }
+
+
+
+ static void encodeModifyDNRequest(ASN1Writer writer, int messageID,
+ ModifyDNRequest request) throws IOException
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "ENCODE LDAP MODIFY DN REQUEST(messageID=%d, request=%s)",
+ messageID, request));
+ }
+ encodeMessageHeader(writer, messageID);
+ writer.writeStartSequence(OP_TYPE_MODIFY_DN_REQUEST);
+ writer.writeOctetString(request.getName().toString());
+ writer.writeOctetString(request.getNewRDN().toString());
+ writer.writeBoolean(request.isDeleteOldRDN());
+
+ DN newSuperior = request.getNewSuperior();
+ if (newSuperior != null)
+ {
+ writer.writeOctetString(TYPE_MODIFY_DN_NEW_SUPERIOR, newSuperior
+ .toString());
+ }
+
+ writer.writeEndSequence();
+ encodeMessageFooter(writer, request);
+ }
+
+
+
+ static void encodeModifyRequest(ASN1Writer writer, int messageID,
+ ModifyRequest request) throws IOException
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "ENCODE LDAP MODIFY REQUEST(messageID=%d, request=%s)",
+ messageID, request));
+ }
+ encodeMessageHeader(writer, messageID);
+ writer.writeStartSequence(OP_TYPE_MODIFY_REQUEST);
+ writer.writeOctetString(request.getName().toString());
+
+ writer.writeStartSequence();
+ for (Change change : request.getChanges())
+ {
+ encodeChange(writer, change);
+ }
+ writer.writeEndSequence();
+
+ writer.writeEndSequence();
+ encodeMessageFooter(writer, request);
+ }
+
+
+
+ static void encodeSearchRequest(ASN1Writer writer, int messageID,
+ SearchRequest request) throws IOException
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "ENCODE LDAP SEARCH REQUEST(messageID=%d, request=%s)",
+ messageID, request));
+ }
+ encodeMessageHeader(writer, messageID);
+ writer.writeStartSequence(OP_TYPE_SEARCH_REQUEST);
+ writer.writeOctetString(request.getName().toString());
+ writer.writeEnumerated(request.getScope().intValue());
+ writer.writeEnumerated(request.getDereferenceAliasesPolicy()
+ .intValue());
+ writer.writeInteger(request.getSizeLimit());
+ writer.writeInteger(request.getTimeLimit());
+ writer.writeBoolean(request.isTypesOnly());
+ LDAPUtils.encodeFilter(writer, request.getFilter());
+
+ writer.writeStartSequence();
+ for (String attribute : request.getAttributes())
+ {
+ writer.writeOctetString(attribute);
+ }
+ writer.writeEndSequence();
+
+ writer.writeEndSequence();
+ encodeMessageFooter(writer, request);
+ }
+
+
+
+ static void encodeUnbindRequest(ASN1Writer writer, int messageID,
+ UnbindRequest request) throws IOException
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "ENCODE LDAP UNBIND REQUEST(messageID=%d, request=%s)",
+ messageID, request));
+ }
+ encodeMessageHeader(writer, messageID);
+ writer.writeNull(OP_TYPE_UNBIND_REQUEST);
+ encodeMessageFooter(writer, request);
+ }
+
+
+
+ static void encodeAddResult(ASN1Writer writer, int messageID,
+ Result result) throws IOException
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "ENCODE LDAP ADD RESULT(messageID=%d, result=%s)", messageID,
+ result));
+ }
+ encodeMessageHeader(writer, messageID);
+ encodeResultHeader(writer, OP_TYPE_ADD_RESPONSE, result);
+ encodeResultFooter(writer);
+ encodeMessageFooter(writer, result);
+ }
+
+
+
+ static void encodeBindResult(ASN1Writer writer, int messageID,
+ BindResult result) throws IOException
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "ENCODE LDAP BIND RESULT(messageID=%d, result=%s)",
+ messageID, result));
+ }
+ encodeMessageHeader(writer, messageID);
+ encodeResultHeader(writer, OP_TYPE_BIND_RESPONSE, result);
+
+ if (result.getServerSASLCredentials().length() > 0)
+ {
+ writer.writeOctetString(TYPE_SERVER_SASL_CREDENTIALS, result
+ .getServerSASLCredentials());
+ }
+
+ encodeResultFooter(writer);
+ encodeMessageFooter(writer, result);
+ }
+
+
+
+ static void encodeCompareResult(ASN1Writer writer, int messageID,
+ CompareResult result) throws IOException
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "ENCODE LDAP COMPARE RESULT(messageID=%d, result=%s)",
+ messageID, result));
+ }
+ encodeMessageHeader(writer, messageID);
+ encodeResultHeader(writer, OP_TYPE_COMPARE_RESPONSE, result);
+ encodeResultFooter(writer);
+ encodeMessageFooter(writer, result);
+ }
+
+
+
+ static void encodeDeleteResult(ASN1Writer writer, int messageID,
+ Result result) throws IOException
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "ENCODE LDAP DELETE RESULT(messageID=%d, result=%s)",
+ messageID, result));
+ }
+ encodeMessageHeader(writer, messageID);
+ encodeResultHeader(writer, OP_TYPE_DELETE_RESPONSE, result);
+ encodeResultFooter(writer);
+ encodeMessageFooter(writer, result);
+ }
+
+
+
+ static void encodeExtendedResult(ASN1Writer writer, int messageID,
+ ExtendedResult result) throws IOException
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "ENCODE LDAP EXTENDED RESULT(messageID=%d, result=%s)",
+ messageID, result));
+ }
+ encodeMessageHeader(writer, messageID);
+ encodeResultHeader(writer, OP_TYPE_EXTENDED_RESPONSE, result);
+
+ String responseName = result.getResponseName();
+ ByteString responseValue = result.getResponseValue();
+
+ if (responseName != null)
+ {
+ writer.writeOctetString(TYPE_EXTENDED_RESPONSE_OID, responseName);
+ }
+
+ if (responseValue != null)
+ {
+ writer.writeOctetString(TYPE_EXTENDED_RESPONSE_VALUE,
+ responseValue);
+ }
+
+ encodeResultFooter(writer);
+ encodeMessageFooter(writer, result);
+ }
+
+
+
+ static void encodeIntermediateResponse(ASN1Writer writer,
+ int messageID, IntermediateResponse response) throws IOException
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG
+ .finer(String
+ .format(
+ "ENCODE LDAP INTERMEDIATE RESPONSE(messageID=%d, response=%s)",
+ messageID, response));
+ }
+ encodeMessageHeader(writer, messageID);
+ writer.writeStartSequence(OP_TYPE_INTERMEDIATE_RESPONSE);
+
+ String responseName = response.getResponseName();
+ ByteString responseValue = response.getResponseValue();
+
+ if (responseName != null)
+ {
+ writer.writeOctetString(TYPE_INTERMEDIATE_RESPONSE_OID, response
+ .getResponseName());
+ }
+
+ if (responseValue != null)
+ {
+ writer.writeOctetString(TYPE_INTERMEDIATE_RESPONSE_VALUE,
+ response.getResponseValue());
+ }
+
+ writer.writeEndSequence();
+ encodeMessageFooter(writer, response);
+ }
+
+
+
+ static void encodeModifyDNResult(ASN1Writer writer, int messageID,
+ Result result) throws IOException
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "ENCODE LDAP MODIFY DN RESULT(messageID=%d, result=%s)",
+ messageID, result));
+ }
+ encodeMessageHeader(writer, messageID);
+ encodeResultHeader(writer, OP_TYPE_MODIFY_DN_RESPONSE, result);
+ encodeResultFooter(writer);
+ encodeMessageFooter(writer, result);
+ }
+
+
+
+ static void encodeModifyResult(ASN1Writer writer, int messageID,
+ Result result) throws IOException
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "ENCODE LDAP MODIFY RESULT(messageID=%d, result=%s)",
+ messageID, result));
+ }
+ encodeMessageHeader(writer, messageID);
+ encodeResultHeader(writer, OP_TYPE_MODIFY_RESPONSE, result);
+ encodeResultFooter(writer);
+ encodeMessageFooter(writer, result);
+ }
+
+
+
+ static void encodeSearchResult(ASN1Writer writer, int messageID,
+ Result result) throws IOException
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "ENCODE LDAP SEARCH RESULT(messageID=%d, result=%s)",
+ messageID, result));
+ }
+ encodeMessageHeader(writer, messageID);
+ encodeResultHeader(writer, OP_TYPE_SEARCH_RESULT_DONE, result);
+ encodeResultFooter(writer);
+ encodeMessageFooter(writer, result);
+ }
+
+
+
+ static void encodeSearchResultEntry(ASN1Writer writer, int messageID,
+ SearchResultEntry entry) throws IOException
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG.finer(String.format(
+ "ENCODE LDAP SEARCH RESULT ENTRY(messageID=%d, entry=%s)",
+ messageID, entry));
+ }
+ encodeMessageHeader(writer, messageID);
+ encodeEntry(writer, entry);
+ encodeMessageFooter(writer, entry);
+ }
+
+
+
+ static void encodeSearchResultReference(ASN1Writer writer,
+ int messageID, SearchResultReference reference)
+ throws IOException
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+ {
+ StaticUtils.DEBUG_LOG
+ .finer(String
+ .format(
+ "ENCODE LDAP SEARCH RESULT REFERENCE(messageID=%d, reference=%s)",
+ messageID, reference));
+ }
+ encodeMessageHeader(writer, messageID);
+ writer.writeStartSequence(OP_TYPE_SEARCH_RESULT_REFERENCE);
+ for (String url : reference.getURIs())
+ {
+ writer.writeOctetString(url);
+ }
+ writer.writeEndSequence();
+ encodeMessageFooter(writer, reference);
+ }
+
+
+
+ private static void encodeChange(ASN1Writer writer, Change change)
+ throws IOException
+ {
+ writer.writeStartSequence();
+ writer.writeEnumerated(change.getModificationType().intValue());
+ encodeAttribute(writer, change.getAttribute());
+ writer.writeEndSequence();
+ }
+
+
+
+ private static void encodeMessageFooter(ASN1Writer writer,
+ Request request) throws IOException
+ {
+ if (request.hasControls())
+ {
+ writer.writeStartSequence(TYPE_CONTROL_SEQUENCE);
+ for (Control control : request.getControls())
+ {
+ encodeControl(writer, control);
+ }
+ writer.writeEndSequence();
+ }
+
+ writer.writeEndSequence();
+ }
+
+
+
+ private static void encodeMessageFooter(ASN1Writer writer,
+ Response response) throws IOException
+ {
+ if (response.hasControls())
+ {
+ writer.writeStartSequence(TYPE_CONTROL_SEQUENCE);
+ for (Control control : response.getControls())
+ {
+ encodeControl(writer, control);
+ }
+ writer.writeEndSequence();
+ }
+
+ writer.writeEndSequence();
+ }
+
+
+
+ private static void encodeMessageHeader(ASN1Writer writer,
+ int messageID) throws IOException
+ {
+ writer.writeStartSequence();
+ writer.writeInteger(messageID);
+ }
+
+
+
+ private static void encodeResultFooter(ASN1Writer writer)
+ throws IOException
+ {
+ writer.writeEndSequence();
+ }
+
+
+
+ private static void encodeResultHeader(ASN1Writer writer,
+ byte typeTag, Result rawMessage) throws IOException
+ {
+ writer.writeStartSequence(typeTag);
+ writer.writeEnumerated(rawMessage.getResultCode().intValue());
+ writer.writeOctetString(rawMessage.getMatchedDN());
+ writer.writeOctetString(rawMessage.getDiagnosticMessage());
+
+ if (rawMessage.hasReferralURIs())
+ {
+ writer.writeStartSequence(TYPE_REFERRAL_SEQUENCE);
+ for (String s : rawMessage.getReferralURIs())
+ {
+ writer.writeOctetString(s);
+ }
+ writer.writeEndSequence();
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/LDAPMessageHandler.java b/sdk/src/org/opends/sdk/ldap/LDAPMessageHandler.java
new file mode 100644
index 0000000..4cc393d
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/LDAPMessageHandler.java
@@ -0,0 +1,190 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.requests.*;
+import org.opends.sdk.responses.*;
+import org.opends.sdk.sasl.SASLBindRequest;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * LDAP message handler interface.
+ */
+interface LDAPMessageHandler
+{
+ ResolvedSchema resolveSchema(String dn) throws DecodeException;
+
+
+
+ Schema getDefaultSchema();
+
+
+
+ void handleException(Throwable throwable);
+
+
+
+ void handleUnrecognizedMessage(int messageID, byte messageTag,
+ ByteString messageBytes) throws UnsupportedMessageException;
+
+
+
+ void handleAbandonRequest(int messageID, AbandonRequest request)
+ throws UnexpectedRequestException;
+
+
+
+ void handleAddRequest(int messageID, AddRequest request)
+ throws UnexpectedRequestException;
+
+
+
+ void handleCompareRequest(int messageID, CompareRequest request)
+ throws UnexpectedRequestException;
+
+
+
+ void handleDeleteRequest(int messageID, DeleteRequest request)
+ throws UnexpectedRequestException;
+
+
+
+ void handleExtendedRequest(int messageID,
+ GenericExtendedRequest request) throws UnexpectedRequestException;
+
+
+
+ void handleBindRequest(int messageID, int version,
+ GenericBindRequest request) throws UnexpectedRequestException;
+
+
+
+ void handleBindRequest(int messageID, int version,
+ SASLBindRequest<?> request) throws UnexpectedRequestException;
+
+
+
+ void handleBindRequest(int messageID, int version,
+ SimpleBindRequest request) throws UnexpectedRequestException;
+
+
+
+ void handleModifyDNRequest(int messageID, ModifyDNRequest request)
+ throws UnexpectedRequestException;
+
+
+
+ void handleModifyRequest(int messageID, ModifyRequest request)
+ throws UnexpectedRequestException;
+
+
+
+ void handleSearchRequest(int messageID, SearchRequest request)
+ throws UnexpectedRequestException;
+
+
+
+ void handleUnbindRequest(int messageID, UnbindRequest request)
+ throws UnexpectedRequestException;
+
+
+
+ void handleAddResult(int messageID, Result result)
+ throws UnexpectedResponseException;
+
+
+
+ void handleBindResult(int messageID, BindResult result)
+ throws UnexpectedResponseException;
+
+
+
+ void handleCompareResult(int messageID, CompareResult result)
+ throws UnexpectedResponseException;
+
+
+
+ void handleDeleteResult(int messageID, Result result)
+ throws UnexpectedResponseException;
+
+
+
+ void handleExtendedResult(int messageID, GenericExtendedResult result)
+ throws UnexpectedResponseException;
+
+
+
+ void handleIntermediateResponse(int messageID,
+ GenericIntermediateResponse response)
+ throws UnexpectedResponseException;
+
+
+
+ void handleModifyDNResult(int messageID, Result result)
+ throws UnexpectedResponseException;
+
+
+
+ void handleModifyResult(int messageID, Result result)
+ throws UnexpectedResponseException;
+
+
+
+ void handleSearchResult(int messageID, Result result)
+ throws UnexpectedResponseException;
+
+
+
+ void handleSearchResultEntry(int messageID, SearchResultEntry entry)
+ throws UnexpectedResponseException;
+
+
+
+ void handleSearchResultReference(int messageID,
+ SearchResultReference reference)
+ throws UnexpectedResponseException;
+
+
+
+ Control decodeResponseControl(int messageID, String oid,
+ boolean isCritical, ByteString value, Schema schema)
+ throws DecodeException;
+
+
+
+ Control decodeRequestControl(int messageID, String oid,
+ boolean isCritical, ByteString value, Schema schema)
+ throws DecodeException;
+}
diff --git a/sdk/src/org/opends/sdk/ldap/LDAPUtils.java b/sdk/src/org/opends/sdk/ldap/LDAPUtils.java
new file mode 100644
index 0000000..ecf5c5f
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/LDAPUtils.java
@@ -0,0 +1,738 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import static org.opends.sdk.ldap.LDAPConstants.*;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opends.sdk.Filter;
+import org.opends.sdk.FilterVisitor;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.responses.SearchResultEntry;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * Common LDAP utility methods which may be used when implementing new
+ * controls and extension.
+ */
+public final class LDAPUtils
+{
+
+ private static final FilterVisitor<IOException, ASN1Writer> ASN1_ENCODER = new FilterVisitor<IOException, ASN1Writer>()
+ {
+
+ public IOException visitAndFilter(ASN1Writer writer,
+ List<Filter> subFilters)
+ {
+ try
+ {
+ writer.writeStartSequence(TYPE_FILTER_AND);
+ for (Filter subFilter : subFilters)
+ {
+ IOException e = subFilter.accept(this, writer);
+ if (e != null)
+ {
+ return e;
+ }
+ }
+ writer.writeEndSequence();
+ return null;
+ }
+ catch (IOException e)
+ {
+ return e;
+ }
+ }
+
+
+
+ public IOException visitApproxMatchFilter(ASN1Writer writer,
+ String attributeDescription, ByteSequence assertionValue)
+ {
+ try
+ {
+ writer.writeStartSequence(TYPE_FILTER_APPROXIMATE);
+ writer.writeOctetString(attributeDescription);
+ writer.writeOctetString(assertionValue);
+ writer.writeEndSequence();
+ return null;
+ }
+ catch (IOException e)
+ {
+ return e;
+ }
+ }
+
+
+
+ public IOException visitEqualityMatchFilter(ASN1Writer writer,
+ String attributeDescription, ByteSequence assertionValue)
+ {
+ try
+ {
+ writer.writeStartSequence(TYPE_FILTER_EQUALITY);
+ writer.writeOctetString(attributeDescription);
+ writer.writeOctetString(assertionValue);
+ writer.writeEndSequence();
+ return null;
+ }
+ catch (IOException e)
+ {
+ return e;
+ }
+ }
+
+
+
+ public IOException visitExtensibleMatchFilter(ASN1Writer writer,
+ String matchingRule, String attributeDescription,
+ ByteSequence assertionValue, boolean dnAttributes)
+ {
+ try
+ {
+ writer.writeStartSequence(TYPE_FILTER_EXTENSIBLE_MATCH);
+
+ if (matchingRule != null)
+ {
+ writer.writeOctetString(TYPE_MATCHING_RULE_ID, matchingRule);
+ }
+
+ if (attributeDescription != null)
+ {
+ writer.writeOctetString(TYPE_MATCHING_RULE_TYPE,
+ attributeDescription);
+ }
+
+ writer.writeOctetString(TYPE_MATCHING_RULE_VALUE,
+ assertionValue);
+
+ if (dnAttributes)
+ {
+ writer.writeBoolean(TYPE_MATCHING_RULE_DN_ATTRIBUTES, true);
+ }
+
+ writer.writeEndSequence();
+ return null;
+ }
+ catch (IOException e)
+ {
+ return e;
+ }
+ }
+
+
+
+ public IOException visitGreaterOrEqualFilter(ASN1Writer writer,
+ String attributeDescription, ByteSequence assertionValue)
+ {
+ try
+ {
+ writer.writeStartSequence(TYPE_FILTER_GREATER_OR_EQUAL);
+ writer.writeOctetString(attributeDescription);
+ writer.writeOctetString(assertionValue);
+ writer.writeEndSequence();
+ return null;
+ }
+ catch (IOException e)
+ {
+ return e;
+ }
+ }
+
+
+
+ public IOException visitLessOrEqualFilter(ASN1Writer writer,
+ String attributeDescription, ByteSequence assertionValue)
+ {
+ try
+ {
+ writer.writeStartSequence(TYPE_FILTER_LESS_OR_EQUAL);
+ writer.writeOctetString(attributeDescription);
+ writer.writeOctetString(assertionValue);
+ writer.writeEndSequence();
+ return null;
+ }
+ catch (IOException e)
+ {
+ return e;
+ }
+ }
+
+
+
+ public IOException visitNotFilter(ASN1Writer writer,
+ Filter subFilter)
+ {
+ try
+ {
+ writer.writeStartSequence(TYPE_FILTER_NOT);
+ IOException e = subFilter.accept(this, writer);
+ if (e != null)
+ {
+ return e;
+ }
+ writer.writeEndSequence();
+ return null;
+ }
+ catch (IOException e)
+ {
+ return e;
+ }
+ }
+
+
+
+ public IOException visitOrFilter(ASN1Writer writer,
+ List<Filter> subFilters)
+ {
+ try
+ {
+ writer.writeStartSequence(TYPE_FILTER_OR);
+ for (Filter subFilter : subFilters)
+ {
+ IOException e = subFilter.accept(this, writer);
+ if (e != null)
+ {
+ return e;
+ }
+ }
+ writer.writeEndSequence();
+ return null;
+ }
+ catch (IOException e)
+ {
+ return e;
+ }
+ }
+
+
+
+ public IOException visitPresentFilter(ASN1Writer writer,
+ String attributeDescription)
+ {
+ try
+ {
+ writer.writeOctetString(TYPE_FILTER_PRESENCE,
+ attributeDescription);
+ return null;
+ }
+ catch (IOException e)
+ {
+ return e;
+ }
+ }
+
+
+
+ public IOException visitSubstringsFilter(ASN1Writer writer,
+ String attributeDescription, ByteSequence initialSubstring,
+ List<ByteSequence> anySubstrings, ByteSequence finalSubstring)
+ {
+ try
+ {
+ writer.writeStartSequence(TYPE_FILTER_SUBSTRING);
+ writer.writeOctetString(attributeDescription);
+
+ writer.writeStartSequence();
+ if (initialSubstring != null)
+ {
+ writer.writeOctetString(TYPE_SUBINITIAL, initialSubstring);
+ }
+
+ for (ByteSequence anySubstring : anySubstrings)
+ {
+ writer.writeOctetString(TYPE_SUBANY, anySubstring);
+ }
+
+ if (finalSubstring != null)
+ {
+ writer.writeOctetString(TYPE_SUBFINAL, finalSubstring);
+ }
+ writer.writeEndSequence();
+
+ writer.writeEndSequence();
+ return null;
+ }
+ catch (IOException e)
+ {
+ return e;
+ }
+ }
+
+
+
+ public IOException visitUnrecognizedFilter(ASN1Writer writer,
+ byte filterTag, ByteSequence filterBytes)
+ {
+ try
+ {
+ writer.writeOctetString(filterTag, filterBytes);
+ return null;
+ }
+ catch (IOException e)
+ {
+ return e;
+ }
+ }
+ };
+
+
+
+ /**
+ * Reads the next ASN.1 element from the provided {@code ASN1Reader}
+ * as a {@code Filter}.
+ *
+ * @param reader
+ * The {@code ASN1Reader} from which the ASN.1 encoded
+ * {@code Filter} should be read.
+ * @return The decoded {@code Filter}.
+ * @throws IOException
+ * If an error occurs while reading from {@code reader}.
+ */
+ public static Filter decodeFilter(ASN1Reader reader)
+ throws IOException
+ {
+ byte type = reader.peekType();
+
+ switch (type)
+ {
+ case TYPE_FILTER_AND:
+ return decodeAndFilter(reader);
+
+ case TYPE_FILTER_OR:
+ return decodeOrFilter(reader);
+
+ case TYPE_FILTER_NOT:
+ return decodeNotFilter(reader);
+
+ case TYPE_FILTER_EQUALITY:
+ return decodeEqualityMatchFilter(reader);
+
+ case TYPE_FILTER_GREATER_OR_EQUAL:
+ return decodeGreaterOrEqualMatchFilter(reader);
+
+ case TYPE_FILTER_LESS_OR_EQUAL:
+ return decodeLessOrEqualMatchFilter(reader);
+
+ case TYPE_FILTER_APPROXIMATE:
+ return decodeApproxMatchFilter(reader);
+
+ case TYPE_FILTER_SUBSTRING:
+ return decodeSubstringsFilter(reader);
+
+ case TYPE_FILTER_PRESENCE:
+ return Filter.newPresentFilter(reader
+ .readOctetStringAsString(type));
+
+ case TYPE_FILTER_EXTENSIBLE_MATCH:
+ return decodeExtensibleMatchFilter(reader);
+
+ default:
+ return Filter.newUnrecognizedFilter(type, reader
+ .readOctetString(type));
+ }
+ }
+
+
+
+ /**
+ * Reads the next ASN.1 element from the provided {@code ASN1Reader}
+ * as a {@code SearchResultEntry}.
+ *
+ * @param reader
+ * The {@code ASN1Reader} from which the ASN.1 encoded
+ * {@code SearchResultEntry} should be read.
+ * @param schema
+ * The schema to use when decoding the entry.
+ * @return The decoded {@code SearchResultEntry}.
+ * @throws IOException
+ * If an error occurs while reading from {@code reader}.
+ */
+ public static SearchResultEntry decodeSearchResultEntry(
+ ASN1Reader reader, Schema schema) throws IOException
+ {
+ return LDAPDecoder.decodeEntry(reader, schema);
+ }
+
+
+
+ /**
+ * Writes the ASN.1 encoding of the provided {@code Filter} to the
+ * provided {@code ASN1Writer}.
+ *
+ * @param writer
+ * The {@code ASN1Writer} to which the ASN.1 encoding of the
+ * provided {@code Filter} should be written.
+ * @param filter
+ * The filter to be encoded.
+ * @return The updated {@code ASN1Writer}.
+ * @throws IOException
+ * If an error occurs while writing to {@code writer}.
+ */
+ public static ASN1Writer encodeFilter(ASN1Writer writer, Filter filter)
+ throws IOException
+ {
+ IOException e = filter.accept(ASN1_ENCODER, writer);
+ if (e != null)
+ {
+ throw e;
+ }
+ else
+ {
+ return writer;
+ }
+ }
+
+
+
+ /**
+ * Writes the ASN.1 encoding of the provided {@code SearchResultEntry}
+ * to the provided {@code ASN1Writer}.
+ *
+ * @param writer
+ * The {@code ASN1Writer} to which the ASN.1 encoding of the
+ * provided {@code SearchResultEntry} should be written.
+ * @param entry
+ * The Search Result Entry to be encoded.
+ * @return The updated {@code ASN1Writer}.
+ * @throws IOException
+ * If an error occurs while writing to {@code writer}.
+ */
+ public static ASN1Writer encodeSearchResultEntry(ASN1Writer writer,
+ SearchResultEntry entry) throws IOException
+ {
+ LDAPEncoder.encodeEntry(writer, entry);
+ return writer;
+ }
+
+
+
+ // Decodes an and filter.
+ private static Filter decodeAndFilter(ASN1Reader reader)
+ throws IOException
+ {
+ Filter filter;
+
+ reader.readStartSequence(TYPE_FILTER_AND);
+ try
+ {
+ if (reader.hasNextElement())
+ {
+ List<Filter> subFilters = new LinkedList<Filter>();
+ do
+ {
+ subFilters.add(decodeFilter(reader));
+ } while (reader.hasNextElement());
+ filter = Filter.newAndFilter(subFilters);
+ }
+ else
+ {
+ // No sub-filters - this is an RFC 4526 absolute true filter.
+ filter = Filter.getAbsoluteTrueFilter();
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ return filter;
+ }
+
+
+
+ // Decodes an approximate match filter.
+ private static Filter decodeApproxMatchFilter(ASN1Reader reader)
+ throws IOException
+ {
+ String attributeDescription;
+ ByteSequence assertionValue;
+
+ reader.readStartSequence(TYPE_FILTER_APPROXIMATE);
+ try
+ {
+ attributeDescription = reader.readOctetStringAsString();
+ assertionValue = reader.readOctetString();
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ return Filter.newApproxMatchFilter(attributeDescription,
+ assertionValue);
+ }
+
+
+
+ // Decodes an equality match filter.
+ private static Filter decodeEqualityMatchFilter(ASN1Reader reader)
+ throws IOException
+ {
+ String attributeDescription;
+ ByteSequence assertionValue;
+
+ reader.readStartSequence(TYPE_FILTER_EQUALITY);
+ try
+ {
+ attributeDescription = reader.readOctetStringAsString();
+ assertionValue = reader.readOctetString();
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ return Filter.newEqualityMatchFilter(attributeDescription,
+ assertionValue);
+ }
+
+
+
+ // Decodes an extensible match filter.
+ private static Filter decodeExtensibleMatchFilter(ASN1Reader reader)
+ throws IOException
+ {
+ String matchingRule;
+ String attributeDescription;
+ boolean dnAttributes;
+ ByteSequence assertionValue;
+
+ reader.readStartSequence(TYPE_FILTER_EXTENSIBLE_MATCH);
+ try
+ {
+ matchingRule = null;
+ if (reader.peekType() == TYPE_MATCHING_RULE_ID)
+ {
+ matchingRule = reader
+ .readOctetStringAsString(TYPE_MATCHING_RULE_ID);
+ }
+ attributeDescription = null;
+ if (reader.peekType() == TYPE_MATCHING_RULE_TYPE)
+ {
+ attributeDescription = reader
+ .readOctetStringAsString(TYPE_MATCHING_RULE_TYPE);
+ }
+ dnAttributes = false;
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_MATCHING_RULE_DN_ATTRIBUTES))
+ {
+ dnAttributes = reader.readBoolean();
+ }
+ assertionValue = reader.readOctetString(TYPE_MATCHING_RULE_VALUE);
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ return Filter.newExtensibleMatchFilter(matchingRule,
+ attributeDescription, assertionValue, dnAttributes);
+ }
+
+
+
+ // Decodes a greater than or equal filter.
+ private static Filter decodeGreaterOrEqualMatchFilter(
+ ASN1Reader reader) throws IOException
+ {
+ String attributeDescription;
+ ByteSequence assertionValue;
+
+ reader.readStartSequence(TYPE_FILTER_GREATER_OR_EQUAL);
+ try
+ {
+ attributeDescription = reader.readOctetStringAsString();
+ assertionValue = reader.readOctetString();
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+ return Filter.newGreaterOrEqualFilter(attributeDescription,
+ assertionValue);
+ }
+
+
+
+ // Decodes a less than or equal filter.
+ private static Filter decodeLessOrEqualMatchFilter(ASN1Reader reader)
+ throws IOException
+ {
+ String attributeDescription;
+ ByteSequence assertionValue;
+
+ reader.readStartSequence(TYPE_FILTER_LESS_OR_EQUAL);
+ try
+ {
+ attributeDescription = reader.readOctetStringAsString();
+ assertionValue = reader.readOctetString();
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ return Filter.newLessOrEqualFilter(attributeDescription,
+ assertionValue);
+ }
+
+
+
+ // Decodes a not filter.
+ private static Filter decodeNotFilter(ASN1Reader reader)
+ throws IOException
+ {
+ Filter subFilter;
+
+ reader.readStartSequence(TYPE_FILTER_NOT);
+ try
+ {
+ subFilter = decodeFilter(reader);
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ return Filter.newNotFilter(subFilter);
+ }
+
+
+
+ // Decodes an or filter.
+ private static Filter decodeOrFilter(ASN1Reader reader)
+ throws IOException
+ {
+ Filter filter;
+
+ reader.readStartSequence(TYPE_FILTER_OR);
+ try
+ {
+ if (reader.hasNextElement())
+ {
+ List<Filter> subFilters = new LinkedList<Filter>();
+ do
+ {
+ subFilters.add(decodeFilter(reader));
+ } while (reader.hasNextElement());
+ filter = Filter.newOrFilter(subFilters);
+ }
+ else
+ {
+ // No sub-filters - this is an RFC 4526 absolute false filter.
+ filter = Filter.getAbsoluteFalseFilter();
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ return filter;
+ }
+
+
+
+ // Decodes a sub-strings filter.
+ private static Filter decodeSubstringsFilter(ASN1Reader reader)
+ throws IOException
+ {
+ ByteSequence initialSubstring = null;
+ List<ByteSequence> anySubstrings = null;
+ ByteSequence finalSubstring = null;
+ String attributeDescription;
+
+ reader.readStartSequence(TYPE_FILTER_SUBSTRING);
+ try
+ {
+ attributeDescription = reader.readOctetStringAsString();
+ reader.readStartSequence();
+ try
+ {
+ // FIXME: There should be at least one element in this substring
+ // filter sequence.
+ if (reader.peekType() == TYPE_SUBINITIAL)
+ {
+ initialSubstring = reader.readOctetString(TYPE_SUBINITIAL);
+ }
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_SUBANY))
+ {
+ anySubstrings = new LinkedList<ByteSequence>();
+ do
+ {
+ anySubstrings.add(reader.readOctetString(TYPE_SUBANY));
+ } while (reader.hasNextElement()
+ && (reader.peekType() == TYPE_SUBANY));
+ }
+ if (reader.hasNextElement()
+ && (reader.peekType() == TYPE_SUBFINAL))
+ {
+ finalSubstring = reader.readOctetString(TYPE_SUBFINAL);
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+ }
+ finally
+ {
+ reader.readEndSequence();
+ }
+
+ if (anySubstrings == null)
+ {
+ anySubstrings = Collections.emptyList();
+ }
+
+ return Filter.newSubstringsFilter(attributeDescription,
+ initialSubstring, anySubstrings, finalSubstring);
+ }
+
+
+
+ /**
+ * Prevent instantiation.
+ */
+ private LDAPUtils()
+ {
+ // Nothing to do.
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/ResolvedSchema.java b/sdk/src/org/opends/sdk/ldap/ResolvedSchema.java
new file mode 100644
index 0000000..5d57a10
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/ResolvedSchema.java
@@ -0,0 +1,65 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import org.opends.sdk.AttributeDescription;
+import org.opends.sdk.DN;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.RDN;
+import org.opends.sdk.schema.Schema;
+
+
+
+/**
+ * A reference to a schema which should be used when decoding incoming
+ * protocol elements.
+ */
+interface ResolvedSchema
+{
+ DN getInitialDN();
+
+
+
+ Schema getSchema();
+
+
+
+ DN decodeDN(String dn) throws DecodeException;
+
+
+
+ RDN decodeRDN(String rdn) throws DecodeException;
+
+
+
+ AttributeDescription decodeAttributeDescription(
+ String attributeDescription) throws DecodeException;
+
+}
diff --git a/sdk/src/org/opends/sdk/ldap/ResultFutureImpl.java b/sdk/src/org/opends/sdk/ldap/ResultFutureImpl.java
new file mode 100644
index 0000000..fef66e6
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/ResultFutureImpl.java
@@ -0,0 +1,80 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.util.concurrent.ExecutorService;
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.ResultFuture;
+import org.opends.sdk.ResultHandler;
+import org.opends.sdk.requests.Request;
+import org.opends.sdk.responses.Responses;
+import org.opends.sdk.responses.Result;
+
+
+
+/**
+ * Result future implementation.
+ */
+final class ResultFutureImpl<P> extends
+ AbstractResultFutureImpl<Result, P> implements ResultFuture<Result>
+{
+ private final Request request;
+
+
+
+ ResultFutureImpl(int messageID, Request request,
+ ResultHandler<Result, P> handler, P p, LDAPConnection connection,
+ ExecutorService handlerExecutor)
+ {
+ super(messageID, handler, p, connection, handlerExecutor);
+ this.request = request;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ Result newErrorResult(ResultCode resultCode,
+ String diagnosticMessage, Throwable cause)
+ {
+ return Responses.newResult(resultCode).setDiagnosticMessage(
+ diagnosticMessage).setCause(cause);
+ }
+
+
+
+ Request getRequest()
+ {
+ return request;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/SASLFilter.java b/sdk/src/org/opends/sdk/ldap/SASLFilter.java
new file mode 100644
index 0000000..04b55b1
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/SASLFilter.java
@@ -0,0 +1,350 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.io.IOException;
+
+import javax.security.sasl.SaslException;
+
+import org.opends.sdk.sasl.SASLContext;
+
+import com.sun.grizzly.Buffer;
+import com.sun.grizzly.Connection;
+import com.sun.grizzly.Grizzly;
+import com.sun.grizzly.attributes.Attribute;
+import com.sun.grizzly.attributes.AttributeBuilder;
+import com.sun.grizzly.attributes.AttributeStorage;
+import com.sun.grizzly.filterchain.FilterAdapter;
+import com.sun.grizzly.filterchain.FilterChainContext;
+import com.sun.grizzly.filterchain.NextAction;
+import com.sun.grizzly.filterchain.StreamTransformerFilter;
+import com.sun.grizzly.streams.StreamReader;
+import com.sun.grizzly.streams.StreamWriter;
+import com.sun.grizzly.threadpool.WorkerThread;
+
+
+
+/**
+ * SASL filter adapter.
+ */
+final class SASLFilter extends FilterAdapter implements
+ StreamTransformerFilter
+{
+ private static SASLFilter SINGLETON = new SASLFilter();
+
+ private static final String SASL_CONTEXT_ATTR_NAME = "SASLContextAttr";
+
+ private static final String SASL_INCOMING_BUFFER_NAME = "SASLIncomingBufferAttr";
+
+ private static final String SASL_OUTGOING_BUFFER_NAME = "SASLOutgoingBufferAttr";
+
+
+
+ public static SASLFilter getInstance(SASLContext saslContext,
+ Connection<?> connection)
+ {
+ SINGLETON.saslContextAttribute.set(connection, saslContext);
+ return SINGLETON;
+ }
+
+
+
+ private final Attribute<SASLContext> saslContextAttribute;
+
+ private final Attribute<byte[]> saslIncomingBufferAttribute;
+
+ private final Attribute<byte[]> saslOutgoingBufferAttribute;
+
+
+
+ private SASLFilter()
+ {
+ AttributeBuilder attrBuilder = getAttributeBuilder();
+ saslContextAttribute = attrBuilder
+ .createAttribute(SASL_CONTEXT_ATTR_NAME);
+ saslIncomingBufferAttribute = attrBuilder
+ .createAttribute(SASL_INCOMING_BUFFER_NAME);
+ saslOutgoingBufferAttribute = attrBuilder
+ .createAttribute(SASL_OUTGOING_BUFFER_NAME);
+ }
+
+
+
+ public StreamReader getStreamReader(StreamReader parentStreamReader)
+ {
+ return new SASLStreamReader(parentStreamReader, this);
+ }
+
+
+
+ public StreamWriter getStreamWriter(StreamWriter parentStreamWriter)
+ {
+ return new SASLStreamWriter(parentStreamWriter, this);
+ }
+
+
+
+ /**
+ * Wraps {@link com.sun.grizzly.filterchain.FilterChainContext}
+ * default {@link com.sun.grizzly.streams.StreamReader} and
+ * {@link com.sun.grizzly.streams.StreamWriter} with SASL aware ones.
+ */
+ @Override
+ public NextAction handleRead(FilterChainContext ctx,
+ NextAction nextAction) throws IOException
+ {
+ StreamReader parentReader = ctx.getStreamReader();
+ StreamWriter parentWriter = ctx.getStreamWriter();
+
+ SASLStreamReader saslStreamReader = new SASLStreamReader(
+ parentReader, this);
+ SASLStreamWriter saslStreamWriter = new SASLStreamWriter(
+ parentWriter, this);
+
+ ctx.setStreamReader(saslStreamReader);
+ ctx.setStreamWriter(saslStreamWriter);
+
+ saslStreamReader.pull();
+
+ if (!saslStreamReader.hasAvailableData())
+ {
+ nextAction = ctx.getStopAction();
+ }
+ return nextAction;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public NextAction handleWrite(FilterChainContext ctx,
+ NextAction nextAction) throws IOException
+ {
+ StreamWriter writer = ctx.getStreamWriter();
+
+ Object message = ctx.getMessage();
+
+ if (message instanceof Buffer<?>)
+ {
+ writer.writeBuffer((Buffer<?>) message);
+ }
+ writer.flush();
+
+ return nextAction;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public NextAction postClose(FilterChainContext ctx,
+ NextAction nextAction) throws IOException
+ {
+ saslContextAttribute.remove(ctx.getConnection());
+ saslIncomingBufferAttribute.remove(ctx.getConnection());
+ saslOutgoingBufferAttribute.remove(ctx.getConnection());
+ return nextAction;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public NextAction postRead(FilterChainContext ctx,
+ NextAction nextAction) throws IOException
+ {
+ SASLStreamReader saslStreamReader = (SASLStreamReader) ctx
+ .getStreamReader();
+ SASLStreamWriter saslStreamWriter = (SASLStreamWriter) ctx
+ .getStreamWriter();
+
+ ctx.setStreamReader(saslStreamReader.getUnderlyingReader());
+ ctx.setStreamWriter(saslStreamWriter.getUnderlyingWriter());
+
+ return nextAction;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public NextAction postWrite(FilterChainContext ctx,
+ NextAction nextAction) throws IOException
+ {
+ return nextAction;
+ }
+
+
+
+ public byte[] unwrap(Buffer<?> incoming, Connection<?> connection)
+ throws SaslException
+ {
+ SASLContext saslClient = saslContextAttribute.get(connection);
+ byte[] incomingBuffer = obtainIncomingBuffer(incoming.capacity(),
+ connection);
+ int remaining = incoming.remaining();
+
+ incoming.get(incomingBuffer, 0, remaining);
+ return saslClient.unwrap(incomingBuffer, 0, remaining);
+ }
+
+
+
+ public byte[] wrap(Buffer<?> outgoing, Connection<?> connection)
+ throws SaslException
+ {
+ SASLContext saslClient = saslContextAttribute.get(connection);
+ byte[] outgoingBuffer = obtainOutgoingBuffer(outgoing.capacity(),
+ connection);
+ int remaining = outgoing.remaining();
+
+ outgoing.get(outgoingBuffer, 0, remaining);
+ return saslClient.wrap(outgoingBuffer, 0, remaining);
+ }
+
+
+
+ protected final AttributeBuilder getAttributeBuilder()
+ {
+ return Grizzly.DEFAULT_ATTRIBUTE_BUILDER;
+ }
+
+
+
+ /**
+ * Obtaining incoming buffer
+ *
+ * @param state
+ * State storage
+ * @return incoming buffer
+ */
+ protected final byte[] obtainIncomingBuffer(int size,
+ AttributeStorage state)
+ {
+
+ // #1 - Try to get buffer from the attribute storage (connection)
+ byte[] buffer = saslIncomingBufferAttribute.get(state);
+
+ if ((buffer != null) && (buffer.length >= size))
+ {
+ return buffer;
+ }
+
+ // #2 - Try to get buffer from the WorkerThread (if possible)
+ Thread currentThread = Thread.currentThread();
+ boolean isWorkingThread = (currentThread instanceof WorkerThread);
+
+ if (isWorkingThread)
+ {
+ WorkerThread workerThread = (WorkerThread) currentThread;
+ buffer = saslIncomingBufferAttribute.get(workerThread);
+ }
+
+ if ((buffer != null) && (buffer.length >= size))
+ {
+ return buffer;
+ }
+
+ // #3 - Allocate new buffer
+ buffer = new byte[size];
+
+ if (isWorkingThread)
+ {
+ WorkerThread workerThread = (WorkerThread) currentThread;
+ saslIncomingBufferAttribute.set(workerThread, buffer);
+ }
+ else
+ {
+ saslIncomingBufferAttribute.set(state, buffer);
+ }
+
+ return buffer;
+ }
+
+
+
+ /**
+ * Obtaining outgoing buffer
+ *
+ * @param state
+ * State storage
+ * @return Outgoing buffer
+ */
+ protected final byte[] obtainOutgoingBuffer(int size,
+ AttributeStorage state)
+ {
+
+ // #1 - Try to get buffer from the attribute storage (connection)
+ byte[] buffer = saslOutgoingBufferAttribute.get(state);
+
+ if ((buffer != null) && (buffer.length >= size))
+ {
+ return buffer;
+ }
+
+ // #2 - Try to get buffer from the WorkerThread (if possible)
+ Thread currentThread = Thread.currentThread();
+ boolean isWorkingThread = (currentThread instanceof WorkerThread);
+
+ if (isWorkingThread)
+ {
+ WorkerThread workerThread = (WorkerThread) currentThread;
+ buffer = saslOutgoingBufferAttribute.get(workerThread);
+ }
+
+ if ((buffer != null) && (buffer.length >= size))
+ {
+ return buffer;
+ }
+
+ // #3 - Allocate new buffer
+ buffer = new byte[size];
+
+ if (isWorkingThread)
+ {
+ WorkerThread workerThread = (WorkerThread) currentThread;
+ saslOutgoingBufferAttribute.set(workerThread, buffer);
+ }
+ else
+ {
+ saslOutgoingBufferAttribute.set(state, buffer);
+ }
+
+ return buffer;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/SASLStreamReader.java b/sdk/src/org/opends/sdk/ldap/SASLStreamReader.java
new file mode 100644
index 0000000..337a12b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/SASLStreamReader.java
@@ -0,0 +1,118 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.io.IOException;
+
+import javax.security.sasl.SaslException;
+
+import com.sun.grizzly.Buffer;
+import com.sun.grizzly.streams.StreamReader;
+import com.sun.grizzly.streams.StreamReaderDecorator;
+
+
+
+/**
+ * SASL stream reader.
+ */
+final class SASLStreamReader extends StreamReaderDecorator
+{
+ private final SASLFilter saslFilter;
+
+
+
+ public SASLStreamReader(StreamReader underlyingReader,
+ SASLFilter saslFilter)
+ {
+ super(underlyingReader);
+ this.saslFilter = saslFilter;
+ }
+
+
+
+ @Override
+ public boolean appendBuffer(Buffer buffer)
+ {
+ if (buffer == null)
+ {
+ return false;
+ }
+
+ byte[] appBuffer;
+ try
+ {
+ appBuffer = saslFilter.unwrap(buffer, getConnection());
+ }
+ catch (SaslException e)
+ {
+ throw new IllegalStateException(e);
+ }
+
+ if (appBuffer.length == 0)
+ {
+ return false;
+ }
+
+ Buffer newBuffer = newBuffer(appBuffer.length);
+ newBuffer.put(appBuffer);
+
+ if (super.appendBuffer(newBuffer))
+ {
+ buffer.dispose();
+ return true;
+ }
+
+ return false;
+ }
+
+
+
+ @Override
+ protected Buffer read0() throws IOException
+ {
+ return underlyingReader.readBuffer();
+ }
+
+
+
+ @Override
+ protected Buffer unwrap(Object data)
+ {
+ return (Buffer) data;
+ }
+
+
+
+ @Override
+ protected final Object wrap(Buffer buffer)
+ {
+ return buffer;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/SASLStreamWriter.java b/sdk/src/org/opends/sdk/ldap/SASLStreamWriter.java
new file mode 100644
index 0000000..22a74a3
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/SASLStreamWriter.java
@@ -0,0 +1,87 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.io.IOException;
+import java.util.concurrent.Future;
+
+import com.sun.grizzly.Buffer;
+import com.sun.grizzly.CompletionHandler;
+import com.sun.grizzly.streams.StreamWriter;
+import com.sun.grizzly.streams.StreamWriterDecorator;
+
+
+
+/**
+ * SASL stream writer.
+ */
+final class SASLStreamWriter extends StreamWriterDecorator
+{
+ private final SASLFilter saslFilter;
+
+
+
+ public SASLStreamWriter(StreamWriter underlyingWriter,
+ SASLFilter saslFilter)
+ {
+ super(underlyingWriter);
+ this.saslFilter = saslFilter;
+ }
+
+
+
+ @Override
+ protected Future<Integer> flush0(Buffer buffer,
+ CompletionHandler<Integer> completionHandler) throws IOException
+ {
+ Future<Integer> lastWriterFuture = null;
+
+ if (buffer != null)
+ {
+ buffer.flip();
+
+ Buffer underlyingBuffer = underlyingWriter.getBuffer();
+ byte[] netBuffer = saslFilter.wrap(buffer, getConnection());
+ int remaining = netBuffer.length;
+ while (remaining > 0)
+ {
+ int writeSize = Math.min(remaining, underlyingBuffer
+ .remaining());
+ underlyingBuffer.put(netBuffer, netBuffer.length - remaining,
+ writeSize);
+ lastWriterFuture = underlyingWriter.flush();
+ remaining -= writeSize;
+ }
+ buffer.clear();
+ }
+
+ return lastWriterFuture;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/SearchResultFutureImpl.java b/sdk/src/org/opends/sdk/ldap/SearchResultFutureImpl.java
new file mode 100644
index 0000000..b8c7a19
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/SearchResultFutureImpl.java
@@ -0,0 +1,127 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.util.concurrent.ExecutorService;
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.ResultFuture;
+import org.opends.sdk.ResultHandler;
+import org.opends.sdk.SearchResultHandler;
+import org.opends.sdk.requests.SearchRequest;
+import org.opends.sdk.responses.*;
+
+
+
+/**
+ * Search result future implementation.
+ */
+final class SearchResultFutureImpl<P> extends
+ AbstractResultFutureImpl<Result, P> implements ResultFuture<Result>
+{
+
+ private final SearchResultHandler<P> searchResultHandler;
+
+ private final P p;
+
+ private final SearchRequest request;
+
+
+
+ SearchResultFutureImpl(int messageID, SearchRequest request,
+ ResultHandler<Result, P> resultHandler,
+ SearchResultHandler<P> searchResultHandler, P p,
+ LDAPConnection connection, ExecutorService handlerExecutor)
+ {
+ super(messageID, resultHandler, p, connection, handlerExecutor);
+ this.request = request;
+ this.searchResultHandler = searchResultHandler;
+ this.p = p;
+ }
+
+
+
+ synchronized void handleSearchResultEntry(
+ final SearchResultEntry entry)
+ {
+ if (!isDone())
+ {
+ if (searchResultHandler != null)
+ {
+ invokeHandler(new Runnable()
+ {
+ public void run()
+ {
+ searchResultHandler.handleEntry(p, entry);
+ }
+ });
+ }
+ }
+ }
+
+
+
+ synchronized void handleSearchResultReference(
+ final SearchResultReference reference)
+ {
+ if (!isDone())
+ {
+ if (searchResultHandler != null)
+ {
+ invokeHandler(new Runnable()
+ {
+ public void run()
+ {
+ searchResultHandler.handleReference(p, reference);
+ }
+ });
+ }
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Result newErrorResult(ResultCode resultCode,
+ String diagnosticMessage, Throwable cause)
+ {
+ return Responses.newResult(resultCode).setDiagnosticMessage(
+ diagnosticMessage).setCause(cause);
+ }
+
+
+
+ SearchRequest getRequest()
+ {
+ return request;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/UnexpectedRequestException.java b/sdk/src/org/opends/sdk/ldap/UnexpectedRequestException.java
new file mode 100644
index 0000000..d854fd4
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/UnexpectedRequestException.java
@@ -0,0 +1,71 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.requests.Request;
+
+
+
+/**
+ * Thrown when an expected LDAP request is received.
+ */
+@SuppressWarnings("serial")
+final class UnexpectedRequestException extends IOException
+{
+ private final int messageID;
+ private final Request request;
+
+
+
+ public UnexpectedRequestException(int messageID, Request request)
+ {
+ super(Message.raw("Unexpected LDAP request: id=%d, message=%s",
+ messageID, request).toString());
+ this.messageID = messageID;
+ this.request = request;
+ }
+
+
+
+ public int getMessageID()
+ {
+ return messageID;
+ }
+
+
+
+ public Request getRequest()
+ {
+ return request;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/UnexpectedResponseException.java b/sdk/src/org/opends/sdk/ldap/UnexpectedResponseException.java
new file mode 100644
index 0000000..1f038dd
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/UnexpectedResponseException.java
@@ -0,0 +1,71 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.responses.Response;
+
+
+
+/**
+ * Thrown when an unexpected LDAP response is received.
+ */
+@SuppressWarnings("serial")
+final class UnexpectedResponseException extends IOException
+{
+ private final int messageID;
+ private final Response response;
+
+
+
+ public UnexpectedResponseException(int messageID, Response response)
+ {
+ super(Message.raw("Unexpected LDAP response: id=%d, message=%s",
+ messageID, response).toString());
+ this.messageID = messageID;
+ this.response = response;
+ }
+
+
+
+ public int getMessageID()
+ {
+ return messageID;
+ }
+
+
+
+ public Response getResponse()
+ {
+ return response;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/UnsupportedMessageException.java b/sdk/src/org/opends/sdk/ldap/UnsupportedMessageException.java
new file mode 100644
index 0000000..d2ae7d5
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/UnsupportedMessageException.java
@@ -0,0 +1,81 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.io.IOException;
+
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * Thrown when an unsupported LDAP message is received.
+ */
+@SuppressWarnings("serial")
+final class UnsupportedMessageException extends IOException
+{
+ private final int id;
+ private final byte tag;
+ private final ByteString content;
+
+
+
+ public UnsupportedMessageException(int id, byte tag,
+ ByteString content)
+ {
+ super(org.opends.messages.Message.raw(
+ "Unsupported LDAP message: id=%d, tag=%d, content=%s", id, tag,
+ content).toString());
+ this.id = id;
+ this.tag = tag;
+ this.content = content;
+ }
+
+
+
+ public ByteString getContent()
+ {
+ return content;
+ }
+
+
+
+ public int getID()
+ {
+ return id;
+ }
+
+
+
+ public byte getTag()
+ {
+ return tag;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldif/AbstractLDIFReader.java b/sdk/src/org/opends/sdk/ldif/AbstractLDIFReader.java
new file mode 100644
index 0000000..54760c5
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/AbstractLDIFReader.java
@@ -0,0 +1,891 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import static org.opends.messages.UtilityMessages.*;
+import static org.opends.sdk.util.StaticUtils.toLowerCase;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.*;
+import org.opends.sdk.util.*;
+
+
+
+/**
+ * Common LDIF reader functionality.
+ */
+abstract class AbstractLDIFReader extends AbstractLDIFStream
+{
+ static final class KeyValuePair
+ {
+ String key;
+
+ String value;
+ }
+
+
+
+ /**
+ * LDIF reader implementation interface.
+ */
+ interface LDIFReaderImpl
+ {
+
+ /**
+ * Closes any resources associated with this LDIF reader
+ * implementation.
+ *
+ * @throws IOException
+ * If an error occurs while closing.
+ */
+ void close() throws IOException;
+
+
+
+ /**
+ * Reads the next line of LDIF from the underlying LDIF source.
+ * Implementations must remove trailing line delimiters.
+ *
+ * @return The next line of LDIF, or {@code null} if the end of the
+ * LDIF source has been reached.
+ * @throws IOException
+ * If an error occurs while reading from the LDIF source.
+ */
+ String readLine() throws IOException;
+ }
+
+
+
+ final class LDIFRecord
+ {
+ final Iterator<String> iterator;
+
+ final LinkedList<String> ldifLines;
+
+ final long lineNumber;
+
+
+
+ private LDIFRecord(long lineNumber, LinkedList<String> ldifLines)
+ {
+ this.lineNumber = lineNumber;
+ this.ldifLines = ldifLines;
+ this.iterator = ldifLines.iterator();
+ }
+ }
+
+
+
+ /**
+ * LDIF output stream writer implementation.
+ */
+ private final class LDIFReaderInputStreamImpl implements
+ LDIFReaderImpl
+ {
+
+ private BufferedReader reader;
+
+
+
+ /**
+ * Creates a new LDIF input stream reader implementation.
+ *
+ * @param in
+ * The input stream to use.
+ */
+ LDIFReaderInputStreamImpl(InputStream in)
+ {
+ this.reader = new BufferedReader(new InputStreamReader(in));
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void close() throws IOException
+ {
+ reader.close();
+ reader = null;
+ }
+
+
+
+ /**
+ *{@inheritDoc}
+ */
+ public String readLine() throws IOException
+ {
+ String line = null;
+ if (reader != null)
+ {
+ line = reader.readLine();
+ if (line == null)
+ {
+ // Automatically close.
+ close();
+ }
+ }
+ return line;
+ }
+ }
+
+
+
+ /**
+ * LDIF output stream writer implementation.
+ */
+ private final class LDIFReaderListImpl implements LDIFReaderImpl
+ {
+
+ private final Iterator<String> iterator;
+
+
+
+ /**
+ * Creates a new LDIF list reader.
+ *
+ * @param ldifLines
+ * The string list.
+ */
+ LDIFReaderListImpl(List<String> ldifLines)
+ {
+ this.iterator = ldifLines.iterator();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void close() throws IOException
+ {
+ // Nothing to do.
+ }
+
+
+
+ /**
+ *{@inheritDoc}
+ */
+ public String readLine() throws IOException
+ {
+ if (iterator.hasNext())
+ {
+ return iterator.next();
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+
+
+
+ boolean validateSchema = true;
+
+ private final LDIFReaderImpl impl;
+
+ private long lineNumber = 0;
+
+
+
+ /**
+ * Creates a new LDIF entry reader whose source is the provided input
+ * stream.
+ *
+ * @param in
+ * The input stream to use.
+ */
+ AbstractLDIFReader(InputStream in)
+ {
+ Validator.ensureNotNull(in);
+ this.impl = new LDIFReaderInputStreamImpl(in);
+ }
+
+
+
+ /**
+ * Creates a new LDIF entry reader which will read lines of LDIF from
+ * the provided list.
+ *
+ * @param ldifLines
+ * The list from which lines of LDIF should be read.
+ */
+ AbstractLDIFReader(List<String> ldifLines)
+ {
+ Validator.ensureNotNull(ldifLines);
+ this.impl = new LDIFReaderListImpl(ldifLines);
+ }
+
+
+
+ final void close0() throws IOException
+ {
+ impl.close();
+ }
+
+
+
+ final int parseColonPosition(LDIFRecord record, String ldifLine)
+ throws DecodeException
+ {
+ final int colonPos = ldifLine.indexOf(":");
+ if (colonPos <= 0)
+ {
+ final Message message = ERR_LDIF_NO_ATTR_NAME.get(
+ record.lineNumber, ldifLine);
+ throw DecodeException.error(message);
+ }
+ return colonPos;
+ }
+
+
+
+ final ByteString parseSingleValue(LDIFRecord record, String ldifLine,
+ DN entryDN, int colonPos, String attrName) throws DecodeException
+ {
+
+ // Look at the character immediately after the colon. If there is
+ // none, then assume an attribute with an empty value. If it is
+ // another colon, then the value must be base64-encoded. If it is a
+ // less-than sign, then assume that it is a URL. Otherwise, it is a
+ // regular value.
+ final int length = ldifLine.length();
+ ByteString value;
+ if (colonPos == length - 1)
+ {
+ value = ByteString.empty();
+ }
+ else
+ {
+ final char c = ldifLine.charAt(colonPos + 1);
+ if (c == ':')
+ {
+ // The value is base64-encoded. Find the first non-blank
+ // character, take the rest of the line, and base64-decode it.
+ int pos = colonPos + 2;
+ while (pos < length && ldifLine.charAt(pos) == ' ')
+ {
+ pos++;
+ }
+
+ try
+ {
+ value = Base64.decode(ldifLine.substring(pos));
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ // The value did not have a valid base64-encoding.
+ final Message message = ERR_LDIF_COULD_NOT_BASE64_DECODE_ATTR
+ .get(entryDN.toString(), record.lineNumber, ldifLine, e
+ .getMessageObject());
+ throw DecodeException.error(message);
+ }
+ }
+ else if (c == '<')
+ {
+ // Find the first non-blank character, decode the rest of the
+ // line as a URL, and read its contents.
+ int pos = colonPos + 2;
+ while (pos < length && ldifLine.charAt(pos) == ' ')
+ {
+ pos++;
+ }
+
+ URL contentURL;
+ try
+ {
+ contentURL = new URL(ldifLine.substring(pos));
+ }
+ catch (final Exception e)
+ {
+ // The URL was malformed or had an invalid protocol.
+ final Message message = ERR_LDIF_INVALID_URL.get(entryDN
+ .toString(), record.lineNumber, attrName, String
+ .valueOf(e));
+ throw DecodeException.error(message);
+ }
+
+ InputStream inputStream = null;
+ ByteStringBuilder builder = null;
+ try
+ {
+ builder = new ByteStringBuilder();
+ inputStream = contentURL.openConnection().getInputStream();
+
+ int bytesRead;
+ final byte[] buffer = new byte[4096];
+ while ((bytesRead = inputStream.read(buffer)) > 0)
+ {
+ builder.append(buffer, 0, bytesRead);
+ }
+
+ value = builder.toByteString();
+ }
+ catch (final Exception e)
+ {
+ // We were unable to read the contents of that URL for some
+ // reason.
+ final Message message = ERR_LDIF_URL_IO_ERROR.get(entryDN
+ .toString(), record.lineNumber, attrName, String
+ .valueOf(contentURL), String.valueOf(e));
+ throw DecodeException.error(message);
+ }
+ finally
+ {
+ if (inputStream != null)
+ {
+ try
+ {
+ inputStream.close();
+ }
+ catch (final Exception e)
+ {
+ // Ignore.
+ }
+ }
+ }
+ }
+ else
+ {
+ // The rest of the line should be the value. Skip over any
+ // spaces and take the rest of the line as the value.
+ int pos = colonPos + 1;
+ while (pos < length && ldifLine.charAt(pos) == ' ')
+ {
+ pos++;
+ }
+
+ value = ByteString.valueOf(ldifLine.substring(pos));
+ }
+ }
+ return value;
+ }
+
+
+
+ final LDIFRecord readLDIFRecord() throws IOException
+ {
+ // Read the entry lines into a buffer.
+ final StringBuilder lastLineBuilder = new StringBuilder();
+ final LinkedList<String> ldifLines = new LinkedList<String>();
+ long recordLineNumber = 0;
+
+ final int START = 0;
+ final int START_COMMENT_LINE = 1;
+ final int GOT_LDIF_LINE = 2;
+ final int GOT_COMMENT_LINE = 3;
+ final int APPENDING_LDIF_LINE = 4;
+
+ int state = START;
+
+ while (true)
+ {
+ final String line = readLine();
+
+ switch (state)
+ {
+ case START:
+ if (line == null)
+ {
+ // We have reached the end of the LDIF source.
+ return null;
+ }
+ else if (line.length() == 0)
+ {
+ // Skip leading blank lines.
+ }
+ else if (line.charAt(0) == '#')
+ {
+ // This is a comment at the start of the LDIF record.
+ state = START_COMMENT_LINE;
+ }
+ else if (isContinuationLine(line))
+ {
+ // Fatal: got a continuation line at the start of the record.
+ final Message message = ERR_LDIF_INVALID_LEADING_SPACE.get(
+ lineNumber, line);
+ throw DecodeException.fatalError(message);
+ }
+ else
+ {
+ // Got the first line of LDIF.
+ ldifLines.add(line);
+ recordLineNumber = lineNumber;
+ state = GOT_LDIF_LINE;
+ }
+ break;
+ case START_COMMENT_LINE:
+ if (line == null)
+ {
+ // We have reached the end of the LDIF source.
+ return null;
+ }
+ else if (line.length() == 0)
+ {
+ // Skip leading blank lines and comments.
+ state = START;
+ }
+ else if (line.charAt(0) == '#')
+ {
+ // This is another comment at the start of the LDIF record.
+ }
+ else if (isContinuationLine(line))
+ {
+ // Skip comment continuation lines.
+ }
+ else
+ {
+ // Got the first line of LDIF.
+ ldifLines.add(line);
+ recordLineNumber = lineNumber;
+ state = GOT_LDIF_LINE;
+ }
+ break;
+ case GOT_LDIF_LINE:
+ if (line == null)
+ {
+ // We have reached the end of the LDIF source.
+ return new LDIFRecord(recordLineNumber, ldifLines);
+ }
+ else if (line.length() == 0)
+ {
+ // We have reached the end of the LDIF record.
+ return new LDIFRecord(recordLineNumber, ldifLines);
+ }
+ else if (line.charAt(0) == '#')
+ {
+ // This is a comment.
+ state = GOT_COMMENT_LINE;
+ }
+ else if (isContinuationLine(line))
+ {
+ // Got a continuation line for the previous line.
+ lastLineBuilder.setLength(0);
+ lastLineBuilder.append(ldifLines.removeLast());
+ lastLineBuilder.append(line.substring(1));
+ state = APPENDING_LDIF_LINE;
+ }
+ else
+ {
+ // Got the next line of LDIF.
+ ldifLines.add(line);
+ state = GOT_LDIF_LINE;
+ }
+ break;
+ case GOT_COMMENT_LINE:
+ if (line == null)
+ {
+ // We have reached the end of the LDIF source.
+ return new LDIFRecord(recordLineNumber, ldifLines);
+ }
+ else if (line.length() == 0)
+ {
+ // We have reached the end of the LDIF record.
+ return new LDIFRecord(recordLineNumber, ldifLines);
+ }
+ else if (line.charAt(0) == '#')
+ {
+ // This is another comment.
+ state = GOT_COMMENT_LINE;
+ }
+ else if (isContinuationLine(line))
+ {
+ // Skip comment continuation lines.
+ }
+ else
+ {
+ // Got the next line of LDIF.
+ ldifLines.add(line);
+ state = GOT_LDIF_LINE;
+ }
+ break;
+ case APPENDING_LDIF_LINE:
+ if (line == null)
+ {
+ // We have reached the end of the LDIF source.
+ ldifLines.add(lastLineBuilder.toString());
+ return new LDIFRecord(recordLineNumber, ldifLines);
+ }
+ else if (line.length() == 0)
+ {
+ // We have reached the end of the LDIF record.
+ ldifLines.add(lastLineBuilder.toString());
+ return new LDIFRecord(recordLineNumber, ldifLines);
+ }
+ else if (line.charAt(0) == '#')
+ {
+ // This is a comment.
+ ldifLines.add(lastLineBuilder.toString());
+ state = GOT_COMMENT_LINE;
+ }
+ else if (isContinuationLine(line))
+ {
+ // Got another continuation line for the previous line.
+ lastLineBuilder.append(line.substring(1));
+ }
+ else
+ {
+ // Got the next line of LDIF.
+ ldifLines.add(lastLineBuilder.toString());
+ ldifLines.add(line);
+ state = GOT_LDIF_LINE;
+ }
+ break;
+ }
+ }
+ }
+
+
+
+ final void readLDIFRecordAttributeValue(LDIFRecord record,
+ String ldifLine, Entry entry) throws DecodeException
+ {
+ // Parse the attribute description.
+ final int colonPos = parseColonPosition(record, ldifLine);
+ final String attrDescr = ldifLine.substring(0, colonPos);
+
+ AttributeDescription attributeDescription;
+ try
+ {
+ attributeDescription = AttributeDescription.valueOf(attrDescr,
+ schema);
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ throw DecodeException.error(e.getMessageObject());
+ }
+
+ // Now parse the attribute value.
+ final ByteString value = parseSingleValue(record, ldifLine, entry
+ .getName(), colonPos, attrDescr);
+
+ // Skip the attribute if requested before performing any schema
+ // checking: the attribute may have been excluded because it is
+ // known to violate the schema.
+ if (isAttributeExcluded(attributeDescription))
+ {
+ return;
+ }
+
+ // Ensure that the binary option is present if required.
+ if (!attributeDescription.getAttributeType().getSyntax()
+ .isBEREncodingRequired())
+ {
+ if (validateSchema
+ && attributeDescription.containsOption("binary"))
+ {
+ final Message message = ERR_LDIF_INVALID_ATTR_OPTION.get(entry
+ .getName().toString(), record.lineNumber, attrDescr);
+ throw DecodeException.error(message);
+ }
+ }
+ else
+ {
+ attributeDescription = AttributeDescription.create(
+ attributeDescription, "binary");
+ }
+
+ Attribute attribute = entry.getAttribute(attributeDescription);
+ if (attribute == null)
+ {
+ if (validateSchema)
+ {
+ final MessageBuilder invalidReason = new MessageBuilder();
+ if (!attributeDescription.getAttributeType().getSyntax()
+ .valueIsAcceptable(value, invalidReason))
+ {
+ final Message message = WARN_LDIF_VALUE_VIOLATES_SYNTAX.get(
+ entry.getName().toString(), record.lineNumber, value
+ .toString(), attrDescr, invalidReason);
+ throw DecodeException.error(message);
+ }
+ }
+
+ attribute = new LinkedAttribute(attributeDescription, value);
+ entry.addAttribute(attribute);
+ }
+ else
+ {
+ if (validateSchema)
+ {
+ final MessageBuilder invalidReason = new MessageBuilder();
+ if (!attributeDescription.getAttributeType().getSyntax()
+ .valueIsAcceptable(value, invalidReason))
+ {
+ final Message message = WARN_LDIF_VALUE_VIOLATES_SYNTAX.get(
+ entry.getName().toString(), record.lineNumber, value
+ .toString(), attrDescr, invalidReason);
+ throw DecodeException.error(message);
+ }
+
+ if (!attribute.add(value))
+ {
+ final Message message = WARN_LDIF_DUPLICATE_ATTR.get(entry
+ .getName().toString(), record.lineNumber, attrDescr,
+ value.toString());
+ throw DecodeException.error(message);
+ }
+
+ if (attributeDescription.getAttributeType().isSingleValue())
+ {
+ final Message message = ERR_LDIF_MULTIPLE_VALUES_FOR_SINGLE_VALUED_ATTR
+ .get(entry.getName().toString(), record.lineNumber,
+ attrDescr);
+ throw DecodeException.error(message);
+ }
+ }
+ else
+ {
+ attribute.add(value);
+ }
+ }
+ }
+
+
+
+ final DN readLDIFRecordDN(LDIFRecord record) throws DecodeException
+ {
+ String ldifLine = record.iterator.next();
+ int colonPos = ldifLine.indexOf(":");
+ if (colonPos <= 0)
+ {
+ final Message message = ERR_LDIF_NO_ATTR_NAME.get(
+ record.lineNumber, ldifLine.toString());
+ throw DecodeException.error(message);
+ }
+
+ String attrName = toLowerCase(ldifLine.substring(0, colonPos));
+ if (attrName.equals("version"))
+ {
+ // This is the version line, try the next line if there is one.
+ if (!record.iterator.hasNext())
+ {
+ return null;
+ }
+
+ ldifLine = record.iterator.next();
+ colonPos = ldifLine.indexOf(":");
+ if (colonPos <= 0)
+ {
+ final Message message = ERR_LDIF_NO_ATTR_NAME.get(
+ record.lineNumber, ldifLine.toString());
+ throw DecodeException.error(message);
+ }
+
+ attrName = toLowerCase(ldifLine.substring(0, colonPos));
+ }
+
+ if (!attrName.equals("dn"))
+ {
+ final Message message = ERR_LDIF_NO_DN.get(record.lineNumber,
+ ldifLine.toString());
+ throw DecodeException.error(message);
+ }
+
+ // Look at the character immediately after the colon. If there is
+ // none, then assume the null DN. If it is another colon, then the
+ // DN must be base64-encoded. Otherwise, it may be one or more
+ // spaces.
+ final int length = ldifLine.length();
+ if (colonPos == length - 1)
+ {
+ return DN.rootDN();
+ }
+
+ String dnString = null;
+
+ if (ldifLine.charAt(colonPos + 1) == ':')
+ {
+ // The DN is base64-encoded. Find the first non-blank character
+ // and take the rest of the line and base64-decode it.
+ int pos = colonPos + 2;
+ while (pos < length && ldifLine.charAt(pos) == ' ')
+ {
+ pos++;
+ }
+
+ final String base64DN = ldifLine.substring(pos);
+ try
+ {
+ dnString = Base64.decode(base64DN).toString();
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ // The value did not have a valid base64-encoding.
+ final Message message = ERR_LDIF_COULD_NOT_BASE64_DECODE_DN
+ .get(record.lineNumber, ldifLine, e.getMessageObject());
+ throw DecodeException.error(message);
+ }
+ }
+ else
+ {
+ // The rest of the value should be the DN. Skip over any spaces
+ // and attempt to decode the rest of the line as the DN.
+ int pos = colonPos + 1;
+ while (pos < length && ldifLine.charAt(pos) == ' ')
+ {
+ pos++;
+ }
+
+ dnString = ldifLine.substring(pos);
+ }
+
+ try
+ {
+ return DN.valueOf(dnString, schema);
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ final Message message = ERR_LDIF_INVALID_DN.get(
+ record.lineNumber, ldifLine, e.getMessageObject());
+ throw DecodeException.error(message);
+ }
+ }
+
+
+
+ final String readLDIFRecordKeyValuePair(LDIFRecord record,
+ KeyValuePair pair, boolean allowBase64) throws DecodeException
+ {
+ final String ldifLine = record.iterator.next();
+ final int colonPos = ldifLine.indexOf(":");
+ if (colonPos <= 0)
+ {
+ final Message message = ERR_LDIF_NO_ATTR_NAME.get(
+ record.lineNumber, ldifLine);
+ throw DecodeException.error(message);
+ }
+ pair.key = ldifLine.substring(0, colonPos);
+
+ // Look at the character immediately after the colon. If there is
+ // none, then no value was specified. Throw an exception
+ final int length = ldifLine.length();
+ if (colonPos == length - 1)
+ {
+ // FIXME: improve error.
+ final Message message = Message
+ .raw("Malformed changetype attribute");
+ throw DecodeException.error(message);
+ }
+
+ if (allowBase64 && ldifLine.charAt(colonPos + 1) == ':')
+ {
+ // The value is base64-encoded. Find the first non-blank
+ // character, take the rest of the line, and base64-decode it.
+ int pos = colonPos + 2;
+ while (pos < length && ldifLine.charAt(pos) == ' ')
+ {
+ pos++;
+ }
+
+ try
+ {
+ pair.value = Base64.decode(ldifLine.substring(pos)).toString();
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ // The value did not have a valid base64-encoding.
+ // FIXME: improve error.
+ final Message message = Message
+ .raw("Malformed base64 changetype attribute");
+ throw DecodeException.error(message);
+ }
+ }
+ else
+ {
+ // The rest of the value should be the changetype. Skip over any
+ // spaces and attempt to decode the rest of the line as the
+ // changetype string.
+ int pos = colonPos + 1;
+ while (pos < length && ldifLine.charAt(pos) == ' ')
+ {
+ pos++;
+ }
+
+ pair.value = ldifLine.substring(pos);
+ }
+
+ return ldifLine;
+ }
+
+
+
+ final void rejectLDIFRecord(LDIFRecord record, Message message)
+ throws DecodeException
+ {
+ // FIXME: not yet implemented.
+ throw DecodeException.error(message);
+ }
+
+
+
+ final void skipLDIFRecord(LDIFRecord record, Message message)
+ {
+ // FIXME: not yet implemented.
+ }
+
+
+
+ // Determine whether the provided line is a continuation line. Note
+ // that while RFC 2849 technically only allows a space in this
+ // position, both OpenLDAP and the Sun Java System Directory Server
+ // allow a tab as well, so we will too for compatibility reasons. See
+ // issue #852 for details.
+ private boolean isContinuationLine(String line)
+ {
+ return line.charAt(0) == ' ' || line.charAt(0) == '\t';
+ }
+
+
+
+ private String readLine() throws IOException
+ {
+ final String line = impl.readLine();
+ if (line != null)
+ {
+ lineNumber++;
+ }
+ return line;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/ldif/AbstractLDIFStream.java b/sdk/src/org/opends/sdk/ldif/AbstractLDIFStream.java
new file mode 100644
index 0000000..5e7ca7e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/AbstractLDIFStream.java
@@ -0,0 +1,174 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import org.opends.sdk.AttributeDescription;
+import org.opends.sdk.DN;
+import org.opends.sdk.Entry;
+import org.opends.sdk.Matcher;
+import org.opends.sdk.schema.AttributeType;
+import org.opends.sdk.schema.Schema;
+
+
+
+/**
+ * Common LDIF reader/writer functionality.
+ */
+abstract class AbstractLDIFStream
+{
+
+ final Set<AttributeDescription> excludeAttributes =
+ new HashSet<AttributeDescription>();
+
+ boolean excludeOperationalAttributes = false;
+
+ boolean excludeUserAttributes = false;
+
+ final Set<AttributeDescription> includeAttributes =
+ new HashSet<AttributeDescription>();
+
+ Schema schema = Schema.getDefaultSchema();
+
+ final Set<DN> includeBranches = new HashSet<DN>();
+
+ final Set<DN> excludeBranches = new HashSet<DN>();
+
+ final List<Matcher> includeFilters = new LinkedList<Matcher>();
+
+ final List<Matcher> excludeFilters = new LinkedList<Matcher>();
+
+
+
+ /**
+ * Creates a new abstract LDIF stream.
+ */
+ AbstractLDIFStream()
+ {
+ // Nothing to do.
+ }
+
+
+
+ final boolean isAttributeExcluded(
+ AttributeDescription attributeDescription)
+ {
+ if (!excludeAttributes.isEmpty()
+ && excludeAttributes.contains(attributeDescription))
+ {
+ return true;
+ }
+
+ // Let explicit include override more general exclude.
+ if (!includeAttributes.isEmpty())
+ {
+ return !includeAttributes.contains(attributeDescription);
+ }
+
+ final AttributeType type = attributeDescription.getAttributeType();
+
+ if (excludeOperationalAttributes && type.isOperational())
+ {
+ return true;
+ }
+
+ if (excludeUserAttributes && !type.isOperational())
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+
+
+ final boolean isBranchExcluded(DN dn)
+ {
+ if (!excludeBranches.isEmpty())
+ {
+ for (final DN excludeBranch : excludeBranches)
+ {
+ if (excludeBranch.isSuperiorOrEqualTo(dn))
+ {
+ return true;
+ }
+ }
+ }
+
+ if (!includeBranches.isEmpty())
+ {
+ for (final DN includeBranch : includeBranches)
+ {
+ if (includeBranch.isSuperiorOrEqualTo(dn))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+
+
+ final boolean isEntryExcluded(Entry entry)
+ {
+ if (!excludeFilters.isEmpty())
+ {
+ for (final Matcher excludeFilter : excludeFilters)
+ {
+ if (excludeFilter.matches(entry).toBoolean())
+ {
+ return true;
+ }
+ }
+ }
+
+ if (!includeFilters.isEmpty())
+ {
+ for (final Matcher includeFilter : includeFilters)
+ {
+ if (includeFilter.matches(entry).toBoolean())
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/ldif/AbstractLDIFWriter.java b/sdk/src/org/opends/sdk/ldif/AbstractLDIFWriter.java
new file mode 100644
index 0000000..ecf3c1f
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/AbstractLDIFWriter.java
@@ -0,0 +1,545 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.Base64;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Common LDIF writer functionality.
+ */
+abstract class AbstractLDIFWriter extends AbstractLDIFStream
+{
+
+ /**
+ * LDIF writer implementation interface.
+ */
+ interface LDIFWriterImpl
+ {
+
+ /**
+ * Closes any resources associated with this LDIF writer
+ * implementation.
+ *
+ * @throws IOException
+ * If an error occurs while closing.
+ */
+ void close() throws IOException;
+
+
+
+ /**
+ * Flushes this LDIF writer implementation so that any buffered data
+ * is written immediately to underlying stream, flushing the stream
+ * if it is also {@code Flushable}.
+ * <p>
+ * If the intended destination of this stream is an abstraction
+ * provided by the underlying operating system, for example a file,
+ * then flushing the stream guarantees only that bytes previously
+ * written to the stream are passed to the operating system for
+ * writing; it does not guarantee that they are actually written to
+ * a physical device such as a disk drive.
+ *
+ * @throws IOException
+ * If an error occurs while flushing.
+ */
+ void flush() throws IOException;
+
+
+
+ /**
+ * Prints the provided {@code CharSequence}. Implementations must
+ * not add a new-line character sequence.
+ *
+ * @param s
+ * The {@code CharSequence} to be printed.
+ * @throws IOException
+ * If an error occurs while printing {@code s}.
+ */
+ void print(CharSequence s) throws IOException;
+
+
+
+ /**
+ * Prints a new-line character sequence.
+ *
+ * @throws IOException
+ * If an error occurs while printing the new-line
+ * character sequence.
+ */
+ void println() throws IOException;
+ }
+
+
+
+ /**
+ * LDIF string list writer implementation.
+ */
+ private final class LDIFWriterListImpl implements LDIFWriterImpl
+ {
+
+ private final StringBuilder builder = new StringBuilder();
+
+ private final List<String> ldifLines;
+
+
+
+ /**
+ * Creates a new LDIF list writer.
+ *
+ * @param ldifLines
+ * The string list.
+ */
+ LDIFWriterListImpl(List<String> ldifLines)
+ {
+ this.ldifLines = ldifLines;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void close() throws IOException
+ {
+ // Nothing to do.
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void flush() throws IOException
+ {
+ // Nothing to do.
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void print(CharSequence s) throws IOException
+ {
+ builder.append(s);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void println() throws IOException
+ {
+ ldifLines.add(builder.toString());
+ builder.setLength(0);
+ }
+ }
+
+
+
+ /**
+ * LDIF output stream writer implementation.
+ */
+ private final class LDIFWriterOutputStreamImpl implements
+ LDIFWriterImpl
+ {
+
+ private final BufferedWriter writer;
+
+
+
+ /**
+ * Creates a new LDIF output stream writer.
+ *
+ * @param out
+ * The output stream.
+ */
+ LDIFWriterOutputStreamImpl(OutputStream out)
+ {
+ this.writer = new BufferedWriter(new OutputStreamWriter(out));
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void close() throws IOException
+ {
+ writer.close();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void flush() throws IOException
+ {
+ writer.flush();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void print(CharSequence s) throws IOException
+ {
+ writer.append(s);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void println() throws IOException
+ {
+ writer.newLine();
+ }
+ }
+
+ // Regular expression used for splitting comments on line-breaks.
+ private static final Pattern SPLIT_NEWLINE =
+ Pattern.compile("\\r?\\n");
+
+ boolean addUserFriendlyComments = false;
+
+ final LDIFWriterImpl impl;
+
+ int wrapColumn = 0;
+
+ private final StringBuilder builder = new StringBuilder(80);
+
+
+
+ /**
+ * Creates a new LDIF entry writer which will append lines of LDIF to
+ * the provided list.
+ *
+ * @param ldifLines
+ * The list to which lines of LDIF should be appended.
+ */
+ public AbstractLDIFWriter(List<String> ldifLines)
+ {
+ Validator.ensureNotNull(ldifLines);
+ this.impl = new LDIFWriterListImpl(ldifLines);
+ }
+
+
+
+ /**
+ * Creates a new LDIF entry writer whose destination is the provided
+ * output stream.
+ *
+ * @param out
+ * The output stream to use.
+ */
+ public AbstractLDIFWriter(OutputStream out)
+ {
+ Validator.ensureNotNull(out);
+ this.impl = new LDIFWriterOutputStreamImpl(out);
+ }
+
+
+
+ final void close0() throws IOException
+ {
+ flush0();
+ impl.close();
+ }
+
+
+
+ final void flush0() throws IOException
+ {
+ impl.flush();
+ }
+
+
+
+ final void writeComment0(CharSequence comment) throws IOException,
+ NullPointerException
+ {
+ Validator.ensureNotNull(comment);
+
+ // First, break up the comment into multiple lines to preserve the
+ // original spacing that it contained.
+ final String[] lines = SPLIT_NEWLINE.split(comment);
+
+ // Now iterate through the lines and write them out, prefixing and
+ // wrapping them as necessary.
+ for (final String line : lines)
+ {
+ if (!shouldWrap())
+ {
+ impl.print("# ");
+ impl.print(line);
+ impl.println();
+ }
+ else
+ {
+ final int breakColumn = wrapColumn - 2;
+
+ if (line.length() <= breakColumn)
+ {
+ impl.print("# ");
+ impl.print(line);
+ impl.println();
+ }
+ else
+ {
+ int startPos = 0;
+ outerLoop: while (startPos < line.length())
+ {
+ if (startPos + breakColumn >= line.length())
+ {
+ impl.print("# ");
+ impl.print(line.substring(startPos));
+ impl.println();
+ startPos = line.length();
+ }
+ else
+ {
+ final int endPos = startPos + breakColumn;
+
+ int i = endPos - 1;
+ while (i > startPos)
+ {
+ if (line.charAt(i) == ' ')
+ {
+ impl.print("# ");
+ impl.print(line.substring(startPos, i));
+ impl.println();
+
+ startPos = i + 1;
+ continue outerLoop;
+ }
+
+ i--;
+ }
+
+ // If we've gotten here, then there are no spaces on the
+ // entire line. If that happens, then we'll have to break
+ // in the middle of a word.
+ impl.print("# ");
+ impl.print(line.substring(startPos, endPos));
+ impl.println();
+
+ startPos = endPos;
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+
+ final void writeControls(Iterable<Control> controls)
+ throws IOException
+ {
+ for (final Control control : controls)
+ {
+ final StringBuilder key = new StringBuilder("control: ");
+ key.append(control.getOID());
+ key.append(control.isCritical() ? " true" : " false");
+
+ if (control.hasValue())
+ {
+ writeKeyAndValue(key, control.getValue());
+ }
+ else
+ {
+ writeLine(key);
+ }
+ }
+ }
+
+
+
+ final void writeKeyAndValue(CharSequence key, ByteSequence value)
+ throws IOException
+ {
+ builder.setLength(0);
+
+ // If the value is empty, then just append a single colon and a
+ // single space.
+ if (value.length() == 0)
+ {
+ builder.append(key);
+ builder.append(": ");
+ }
+ else if (needsBase64Encoding(value))
+ {
+ if (addUserFriendlyComments)
+ {
+ // TODO: Only display comments for valid UTF-8 values, not
+ // binary values.
+ }
+
+ builder.setLength(0);
+ builder.append(key);
+ builder.append(":: ");
+ builder.append(Base64.encode(value));
+ }
+ else
+ {
+ builder.append(key);
+ builder.append(": ");
+ builder.append(value.toString());
+ }
+
+ writeLine(builder);
+ }
+
+
+
+ final void writeKeyAndValue(CharSequence key, CharSequence value)
+ throws IOException
+ {
+ // FIXME: We should optimize this at some point.
+ writeKeyAndValue(key, ByteString.valueOf(value.toString()));
+ }
+
+
+
+ final void writeLine(CharSequence line) throws IOException
+ {
+ final int length = line.length();
+ if (shouldWrap() && length > wrapColumn)
+ {
+ impl.print(line.subSequence(0, wrapColumn));
+ impl.println();
+ int pos = wrapColumn;
+ while (pos < length)
+ {
+ final int writeLength = Math.min(wrapColumn - 1, length - pos);
+ impl.print(" ");
+ impl.print(line.subSequence(pos, pos + writeLength));
+ impl.println();
+ pos += wrapColumn - 1;
+ }
+ }
+ else
+ {
+ impl.print(line);
+ impl.println();
+ }
+ }
+
+
+
+ private boolean needsBase64Encoding(ByteSequence bytes)
+ {
+ final int length = bytes.length();
+ if (length == 0)
+ {
+ return false;
+ }
+
+ // If the value starts with a space, colon, or less than, then it
+ // needs to be base64 encoded.
+ switch (bytes.byteAt(0))
+ {
+ case 0x20: // Space
+ case 0x3A: // Colon
+ case 0x3C: // Less-than
+ return true;
+ }
+
+ // If the value ends with a space, then it needs to be
+ // base64 encoded.
+ if (length > 1 && bytes.byteAt(length - 1) == 0x20)
+ {
+ return true;
+ }
+
+ // If the value contains a null, newline, or return character, then
+ // it needs to be base64 encoded.
+ byte b;
+ for (int i = 0; i < bytes.length(); i++)
+ {
+ b = bytes.byteAt(i);
+ if (b > 127 || b < 0)
+ {
+ return true;
+ }
+
+ switch (b)
+ {
+ case 0x00: // Null
+ case 0x0A: // New line
+ case 0x0D: // Carriage return
+ return true;
+ }
+ }
+
+ // If we've made it here, then there's no reason to base64 encode.
+ return false;
+ }
+
+
+
+ private boolean shouldWrap()
+ {
+ return wrapColumn > 1;
+ }
+
+
+
+ @SuppressWarnings("unused")
+ private void writeKeyAndURL(CharSequence key, CharSequence url)
+ throws IOException
+ {
+ builder.setLength(0);
+
+ builder.append(key);
+ builder.append(":: ");
+ builder.append(url);
+
+ writeLine(builder);
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldif/ChangeRecord.java b/sdk/src/org/opends/sdk/ldif/ChangeRecord.java
new file mode 100644
index 0000000..37a1167
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/ChangeRecord.java
@@ -0,0 +1,71 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+import org.opends.sdk.DN;
+
+
+
+/**
+ * A request to modify the content of the Directory in some way. A
+ * change record represents one of the following operations:
+ * <ul>
+ * <li>An {@code Add} operation.
+ * <li>An {@code Delete} operation.
+ * <li>An {@code Modify} operation.
+ * <li>An {@code ModifyDN} operation.
+ * </ul>
+ */
+public interface ChangeRecord
+{
+ /**
+ * Applies a {@code ChangeRecordVisitor} to this {@code ChangeRecord}.
+ *
+ * @param <R>
+ * The return type of the visitor's methods.
+ * @param <P>
+ * The type of the additional parameters to the visitor's
+ * methods.
+ * @param v
+ * The change record visitor.
+ * @param p
+ * Optional additional visitor parameter.
+ * @return A result as specified by the visitor.
+ */
+ <R, P> R accept(ChangeRecordVisitor<R, P> v, P p);
+
+
+
+ /**
+ * Returns the distinguished name of the entry being modified by this
+ * {@code ChangeRecord}.
+ *
+ * @return The distinguished name of the entry being modified.
+ */
+ DN getName();
+}
diff --git a/sdk/src/org/opends/sdk/ldif/ChangeRecordReader.java b/sdk/src/org/opends/sdk/ldif/ChangeRecordReader.java
new file mode 100644
index 0000000..8894159
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/ChangeRecordReader.java
@@ -0,0 +1,86 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import java.io.Closeable;
+import java.io.IOException;
+
+import org.opends.sdk.DecodeException;
+
+
+
+/**
+ * An interface for reading change records from a data source, typically
+ * an LDIF file.
+ * <p>
+ * Implementations must specify the following:
+ * <ul>
+ * <li>Whether or not it is possible for the implementation to encounter
+ * malformed change records and, if it is possible, how they are
+ * handled.
+ * <li>Any synchronization limitations.
+ * </ul>
+ * <p>
+ * TODO: LDIFInputStreamReader
+ * <p>
+ * TODO: SearchResultEntryReader
+ */
+public interface ChangeRecordReader extends Closeable
+{
+
+ /**
+ * Closes this change record reader if it not already closed. Note
+ * that this method does not need to be called if a previous call of
+ * {@link #readChangeRecord()} has returned {@code null}.
+ *
+ * @throws IOException
+ * If an unexpected IO error occurred while closing.
+ */
+ void close() throws IOException;
+
+
+
+ /**
+ * Reads the next change record, blocking if necessary until a change
+ * record is available. If the next change record does not contain a
+ * change type then it will be treated as an {@code Add} change
+ * record.
+ *
+ * @return The next change record, or {@code null} if there are no
+ * more change records to be read.
+ * @throws DecodeException
+ * If the change record could not be decoded because it was
+ * malformed.
+ * @throws IOException
+ * If an unexpected IO error occurred while reading the
+ * change record.
+ */
+ ChangeRecord readChangeRecord() throws DecodeException, IOException;
+}
diff --git a/sdk/src/org/opends/sdk/ldif/ChangeRecordVisitor.java b/sdk/src/org/opends/sdk/ldif/ChangeRecordVisitor.java
new file mode 100644
index 0000000..6cf718a
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/ChangeRecordVisitor.java
@@ -0,0 +1,110 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import org.opends.sdk.requests.AddRequest;
+import org.opends.sdk.requests.DeleteRequest;
+import org.opends.sdk.requests.ModifyDNRequest;
+import org.opends.sdk.requests.ModifyRequest;
+
+
+
+/**
+ * A visitor of {@code ChangeRecord}s, in the style of the visitor
+ * design pattern.
+ * <p>
+ * Classes implementing this interface can query change records in a
+ * type-safe manner. When a visitor is passed to a change record's
+ * accept method, the corresponding visit method most applicable to that
+ * change record is invoked.
+ *
+ * @param <R>
+ * The return type of this visitor's methods. Use
+ * {@link java.lang.Void} for visitors that do not need to
+ * return results.
+ * @param <P>
+ * The type of the additional parameter to this visitor's
+ * methods. Use {@link java.lang.Void} for visitors that do not
+ * need an additional parameter.
+ */
+public interface ChangeRecordVisitor<R, P>
+{
+
+ /**
+ * Visits an {@code Add} change record.
+ *
+ * @param p
+ * A visitor specified parameter.
+ * @param change
+ * The {@code Add} change record.
+ * @return Returns a visitor specified result.
+ */
+ R visitChangeRecord(P p, AddRequest change);
+
+
+
+ /**
+ * Visits an {@code Delete} change record.
+ *
+ * @param p
+ * A visitor specified parameter.
+ * @param change
+ * The {@code Delete} change record.
+ * @return Returns a visitor specified result.
+ */
+ R visitChangeRecord(P p, DeleteRequest change);
+
+
+
+ /**
+ * Visits an {@code ModifyDN} change record.
+ *
+ * @param p
+ * A visitor specified parameter.
+ * @param change
+ * The {@code ModifyDN} change record.
+ * @return Returns a visitor specified result.
+ */
+ R visitChangeRecord(P p, ModifyDNRequest change);
+
+
+
+ /**
+ * Visits an {@code Modify} change record.
+ *
+ * @param p
+ * A visitor specified parameter.
+ * @param change
+ * The {@code Modify} change record.
+ * @return Returns a visitor specified result.
+ */
+ R visitChangeRecord(P p, ModifyRequest change);
+
+}
diff --git a/sdk/src/org/opends/sdk/ldif/ChangeRecordVisitorWriter.java b/sdk/src/org/opends/sdk/ldif/ChangeRecordVisitorWriter.java
new file mode 100644
index 0000000..6a09c24
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/ChangeRecordVisitorWriter.java
@@ -0,0 +1,106 @@
+package org.opends.sdk.ldif;
+
+
+
+import java.io.IOException;
+
+import org.opends.sdk.requests.AddRequest;
+import org.opends.sdk.requests.DeleteRequest;
+import org.opends.sdk.requests.ModifyDNRequest;
+import org.opends.sdk.requests.ModifyRequest;
+
+
+
+/**
+ * A visitor which can be used to write generic change records.
+ */
+final class ChangeRecordVisitorWriter implements
+ ChangeRecordVisitor<IOException, ChangeRecordWriter>
+{
+ // Visitor used for writing generic change records.
+ private static final ChangeRecordVisitorWriter VISITOR =
+ new ChangeRecordVisitorWriter();
+
+
+
+ /**
+ * Returns the singleton instance.
+ *
+ * @return The instance.
+ */
+ static ChangeRecordVisitorWriter getInstance()
+ {
+ return VISITOR;
+ }
+
+
+
+ private ChangeRecordVisitorWriter()
+ {
+ // Nothing to do.
+ }
+
+
+
+ public IOException visitChangeRecord(ChangeRecordWriter p,
+ AddRequest change)
+ {
+ try
+ {
+ p.writeChangeRecord(change);
+ return null;
+ }
+ catch (final IOException e)
+ {
+ return e;
+ }
+ }
+
+
+
+ public IOException visitChangeRecord(ChangeRecordWriter p,
+ DeleteRequest change)
+ {
+ try
+ {
+ p.writeChangeRecord(change);
+ return null;
+ }
+ catch (final IOException e)
+ {
+ return e;
+ }
+ }
+
+
+
+ public IOException visitChangeRecord(ChangeRecordWriter p,
+ ModifyDNRequest change)
+ {
+ try
+ {
+ p.writeChangeRecord(change);
+ return null;
+ }
+ catch (final IOException e)
+ {
+ return e;
+ }
+ }
+
+
+
+ public IOException visitChangeRecord(ChangeRecordWriter p,
+ ModifyRequest change)
+ {
+ try
+ {
+ p.writeChangeRecord(change);
+ return null;
+ }
+ catch (final IOException e)
+ {
+ return e;
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldif/ChangeRecordWriter.java b/sdk/src/org/opends/sdk/ldif/ChangeRecordWriter.java
new file mode 100644
index 0000000..8c5a713
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/ChangeRecordWriter.java
@@ -0,0 +1,185 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import java.io.Closeable;
+import java.io.Flushable;
+import java.io.IOException;
+
+import org.opends.sdk.requests.AddRequest;
+import org.opends.sdk.requests.DeleteRequest;
+import org.opends.sdk.requests.ModifyDNRequest;
+import org.opends.sdk.requests.ModifyRequest;
+
+
+
+/**
+ * An interface for writing change records to a data source, typically
+ * an LDIF file.
+ * <p>
+ * TODO: FilteredChangeRecordWriter
+ */
+public interface ChangeRecordWriter extends Closeable, Flushable
+{
+ /**
+ * Closes this change record writer, flushing it first. Closing a
+ * previously closed change record writer has no effect.
+ *
+ * @throws IOException
+ * If an unexpected IO error occurred while closing.
+ */
+ void close() throws IOException;
+
+
+
+ /**
+ * Flushes this change record writer so that any buffered data is
+ * written immediately to underlying stream, flushing the stream if it
+ * is also {@code Flushable}.
+ * <p>
+ * If the intended destination of this stream is an abstraction
+ * provided by the underlying operating system, for example a file,
+ * then flushing the stream guarantees only that bytes previously
+ * written to the stream are passed to the operating system for
+ * writing; it does not guarantee that they are actually written to a
+ * physical device such as a disk drive.
+ *
+ * @throws IOException
+ * If an unexpected IO error occurred while flushing.
+ */
+ void flush() throws IOException;
+
+
+
+ /**
+ * Writes an {@code Add} change record.
+ *
+ * @param change
+ * The {@code AddRequest} to be written as an {@code Add}
+ * change record.
+ * @return A reference to this change record writer.
+ * @throws IOException
+ * If an unexpected IO error occurred while writing the
+ * change record.
+ * @throws NullPointerException
+ * If {@code change} was {@code null}.
+ */
+ ChangeRecordWriter writeChangeRecord(AddRequest change)
+ throws IOException, NullPointerException;
+
+
+
+ /**
+ * Writes a change record.
+ *
+ * @param change
+ * The {@code ChangeRecord} to be written.
+ * @return A reference to this change record writer.
+ * @throws IOException
+ * If an unexpected IO error occurred while writing the
+ * change record.
+ * @throws NullPointerException
+ * If {@code change} was {@code null}.
+ */
+ ChangeRecordWriter writeChangeRecord(ChangeRecord change)
+ throws IOException, NullPointerException;
+
+
+
+ /**
+ * Writes a {@code Delete} change record.
+ *
+ * @param change
+ * The {@code DeleteRequest} to be written as an {@code
+ * Delete} change record.
+ * @return A reference to this change record writer.
+ * @throws IOException
+ * If an unexpected IO error occurred while writing the
+ * change record.
+ * @throws NullPointerException
+ * If {@code change} was {@code null}.
+ */
+ ChangeRecordWriter writeChangeRecord(DeleteRequest change)
+ throws IOException, NullPointerException;
+
+
+
+ /**
+ * Writes a {@code ModifyDN} change record.
+ *
+ * @param change
+ * The {@code ModifyDNRequest} to be written as an {@code
+ * ModifyDN} change record.
+ * @return A reference to this change record writer.
+ * @throws IOException
+ * If an unexpected IO error occurred while writing the
+ * change record.
+ * @throws NullPointerException
+ * If {@code change} was {@code null}.
+ */
+ ChangeRecordWriter writeChangeRecord(ModifyDNRequest change)
+ throws IOException, NullPointerException;
+
+
+
+ /**
+ * Writes a {@code Modify} change record.
+ *
+ * @param change
+ * The {@code ModifyRequest} to be written as an {@code
+ * Modify} change record.
+ * @return A reference to this change record writer.
+ * @throws IOException
+ * If an unexpected IO error occurred while writing the
+ * change record.
+ * @throws NullPointerException
+ * If {@code change} was {@code null}.
+ */
+ ChangeRecordWriter writeChangeRecord(ModifyRequest change)
+ throws IOException, NullPointerException;
+
+
+
+ /**
+ * Writes a comment.
+ *
+ * @param comment
+ * The {@code CharSequence} to be written as a comment.
+ * @return A reference to this change record writer.
+ * @throws IOException
+ * If an unexpected IO error occurred while writing the
+ * comment.
+ * @throws NullPointerException
+ * If {@code comment} was {@code null}.
+ */
+ ChangeRecordWriter writeComment(CharSequence comment)
+ throws IOException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/ldif/ConnectionChangeRecordWriter.java b/sdk/src/org/opends/sdk/ldif/ConnectionChangeRecordWriter.java
new file mode 100644
index 0000000..fd63d88
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/ConnectionChangeRecordWriter.java
@@ -0,0 +1,323 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+
+import org.opends.sdk.Connection;
+import org.opends.sdk.ErrorResultException;
+import org.opends.sdk.ErrorResultIOException;
+import org.opends.sdk.requests.AddRequest;
+import org.opends.sdk.requests.DeleteRequest;
+import org.opends.sdk.requests.ModifyDNRequest;
+import org.opends.sdk.requests.ModifyRequest;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * A {@code ConnectionChangeRecordWriter} is a bridge from {@code
+ * Connection}s to {@code ChangeRecordWriter}s. A connection change
+ * record writer writes change records by sending appropriate update
+ * requests (Add, Delete, Modify, or ModifyDN) to an underlying
+ * connection.
+ * <p>
+ * All update requests are performed synchronously, blocking until an
+ * update result is received. If an update result indicates that an
+ * update request has failed for some reason then the error result is
+ * propagated to the caller using an {@code ErrorResultIOException}.
+ * <p>
+ * <b>Note:</b> comments are not supported by connection change record
+ * writers. Attempts to write comments will be ignored.
+ */
+public final class ConnectionChangeRecordWriter implements
+ ChangeRecordWriter
+{
+ private final Connection connection;
+
+
+
+ /**
+ * Creates a new connection change record writer whose destination is
+ * the provided connection.
+ *
+ * @param connection
+ * The connection to use.
+ * @throws NullPointerException
+ * If {@code connection} was {@code null}.
+ */
+ public ConnectionChangeRecordWriter(Connection connection)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(connection);
+ this.connection = connection;
+ }
+
+
+
+ /**
+ * Closes this connection change record writer, including the
+ * underlying connection. Closing a previously closed change record
+ * writer has no effect.
+ */
+ public void close()
+ {
+ connection.close();
+ }
+
+
+
+ /**
+ * Connection change record writers do not require flushing, so this
+ * method has no effect.
+ */
+ public void flush()
+ {
+ // Do nothing.
+ }
+
+
+
+ /**
+ * Writes the provided Add request to the underlying connection,
+ * blocking until the request completes.
+ *
+ * @param change
+ * The {@code AddRequest} to be written.
+ * @return A reference to this connection change record writer.
+ * @throws ErrorResultIOException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedIOException
+ * If the current thread was interrupted while waiting.
+ * @throws NullPointerException
+ * If {@code change} was {@code null}.
+ */
+ public ConnectionChangeRecordWriter writeChangeRecord(
+ AddRequest change) throws ErrorResultIOException,
+ InterruptedIOException, NullPointerException
+ {
+ Validator.ensureNotNull(change);
+ try
+ {
+ connection.add(change);
+ }
+ catch (final ErrorResultException e)
+ {
+ throw new ErrorResultIOException(e);
+ }
+ catch (InterruptedException e)
+ {
+ throw new InterruptedIOException(e.getMessage());
+ }
+ return this;
+ }
+
+
+
+ /**
+ * Writes the provided change record to the underlying connection,
+ * blocking until the request completes.
+ *
+ * @param change
+ * The change record to be written.
+ * @return A reference to this connection change record writer.
+ * @throws ErrorResultIOException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedIOException
+ * If the current thread was interrupted while waiting.
+ * @throws NullPointerException
+ * If {@code change} was {@code null}.
+ */
+ public ConnectionChangeRecordWriter writeChangeRecord(
+ ChangeRecord change) throws ErrorResultIOException,
+ InterruptedIOException, NullPointerException
+ {
+ Validator.ensureNotNull(change);
+
+ final IOException e = change.accept(ChangeRecordVisitorWriter
+ .getInstance(), this);
+ try
+ {
+ if (e != null)
+ {
+ throw e;
+ }
+ }
+ catch (final ErrorResultIOException e1)
+ {
+ throw e1;
+ }
+ catch (InterruptedIOException e1)
+ {
+ throw e1;
+ }
+ catch (final IOException e1)
+ {
+ // Should not happen.
+ throw new RuntimeException(e1);
+ }
+ return this;
+ }
+
+
+
+ /**
+ * Writes the provided Delete request to the underlying connection,
+ * blocking until the request completes.
+ *
+ * @param change
+ * The {@code DeleteRequest} to be written.
+ * @return A reference to this connection change record writer.
+ * @throws ErrorResultIOException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedIOException
+ * If the current thread was interrupted while waiting.
+ * @throws NullPointerException
+ * If {@code change} was {@code null}.
+ */
+ public ConnectionChangeRecordWriter writeChangeRecord(
+ DeleteRequest change) throws ErrorResultIOException,
+ InterruptedIOException, NullPointerException
+ {
+ Validator.ensureNotNull(change);
+ try
+ {
+ connection.delete(change);
+ }
+ catch (final ErrorResultException e)
+ {
+ throw new ErrorResultIOException(e);
+ }
+ catch (InterruptedException e)
+ {
+ throw new InterruptedIOException(e.getMessage());
+ }
+ return this;
+ }
+
+
+
+ /**
+ * Writes the provided ModifyDN request to the underlying connection,
+ * blocking until the request completes.
+ *
+ * @param change
+ * The {@code ModifyDNRequest} to be written.
+ * @return A reference to this connection change record writer.
+ * @throws ErrorResultIOException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedIOException
+ * If the current thread was interrupted while waiting.
+ * @throws NullPointerException
+ * If {@code change} was {@code null}.
+ */
+ public ConnectionChangeRecordWriter writeChangeRecord(
+ ModifyDNRequest change) throws ErrorResultIOException,
+ InterruptedIOException, NullPointerException
+ {
+ Validator.ensureNotNull(change);
+ try
+ {
+ connection.modifyDN(change);
+ }
+ catch (final ErrorResultException e)
+ {
+ throw new ErrorResultIOException(e);
+ }
+ catch (InterruptedException e)
+ {
+ throw new InterruptedIOException(e.getMessage());
+ }
+ return this;
+ }
+
+
+
+ /**
+ * Writes the provided Modify request to the underlying connection,
+ * blocking until the request completes.
+ *
+ * @param change
+ * The {@code ModifyRequest} to be written.
+ * @return A reference to this connection change record writer.
+ * @throws ErrorResultIOException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedIOException
+ * If the current thread was interrupted while waiting.
+ * @throws NullPointerException
+ * If {@code change} was {@code null}.
+ */
+ public ConnectionChangeRecordWriter writeChangeRecord(
+ ModifyRequest change) throws ErrorResultIOException,
+ InterruptedIOException, NullPointerException
+ {
+ Validator.ensureNotNull(change);
+ try
+ {
+ connection.modify(change);
+ }
+ catch (final ErrorResultException e)
+ {
+ throw new ErrorResultIOException(e);
+ }
+ catch (InterruptedException e)
+ {
+ throw new InterruptedIOException(e.getMessage());
+ }
+ return this;
+ }
+
+
+
+ /**
+ * Connection change record writers do not support comments, so the
+ * provided comment will be ignored.
+ *
+ * @param comment
+ * The {@code CharSequence} to be written as a comment.
+ * @return A reference to this connection change record writer.
+ * @throws NullPointerException
+ * If {@code comment} was {@code null}.
+ */
+ public ConnectionChangeRecordWriter writeComment(CharSequence comment)
+ {
+ Validator.ensureNotNull(comment);
+
+ // Do nothing.
+ return this;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/ldif/ConnectionEntryWriter.java b/sdk/src/org/opends/sdk/ldif/ConnectionEntryWriter.java
new file mode 100644
index 0000000..71acb1d
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/ConnectionEntryWriter.java
@@ -0,0 +1,156 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import java.io.InterruptedIOException;
+
+import org.opends.sdk.Connection;
+import org.opends.sdk.Entry;
+import org.opends.sdk.ErrorResultException;
+import org.opends.sdk.ErrorResultIOException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * A {@code ConnectionEntryWriter} is a bridge from {@code Connection}s
+ * to {@code EntryWriter}s. A connection entry writer writes entries by
+ * sending Add requests to an underlying connection.
+ * <p>
+ * All Add requests are performed synchronously, blocking until an Add
+ * result is received. If an Add result indicates that an Add request
+ * has failed for some reason then the error result is propagated to the
+ * caller using an {@code ErrorResultIOException}.
+ * <p>
+ * <b>Note:</b> comments are not supported by connection change record
+ * writers. Attempts to write comments will be ignored.
+ */
+public final class ConnectionEntryWriter implements EntryWriter
+{
+ private final Connection connection;
+
+
+
+ /**
+ * Creates a new connection entry writer whose destination is the
+ * provided connection.
+ *
+ * @param connection
+ * The connection to use.
+ * @throws NullPointerException
+ * If {@code connection} was {@code null}.
+ */
+ public ConnectionEntryWriter(Connection connection)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(connection);
+ this.connection = connection;
+ }
+
+
+
+ /**
+ * Closes this connection entry writer, including the underlying
+ * connection. Closing a previously closed entry writer has no effect.
+ */
+ public void close()
+ {
+ connection.close();
+ }
+
+
+
+ /**
+ * Connection entry writers do not require flushing, so this method
+ * has no effect.
+ */
+ public void flush()
+ {
+ // Do nothing.
+ }
+
+
+
+ /**
+ * Connection entry writers do not support comments, so the provided
+ * comment will be ignored.
+ *
+ * @param comment
+ * The {@code CharSequence} to be written as a comment.
+ * @return A reference to this connection entry writer.
+ * @throws NullPointerException
+ * If {@code comment} was {@code null}.
+ */
+ public ConnectionEntryWriter writeComment(CharSequence comment)
+ {
+ Validator.ensureNotNull(comment);
+
+ // Do nothing.
+ return this;
+ }
+
+
+
+ /**
+ * Writes an entry to the underlying connection using an Add request,
+ * blocking until the request completes.
+ *
+ * @param entry
+ * The {@code Entry} to be written.
+ * @return A reference to this connection entry writer.
+ * @throws ErrorResultIOException
+ * If the result code indicates that the request failed for
+ * some reason.
+ * @throws InterruptedIOException
+ * If the current thread was interrupted while waiting.
+ * @throws NullPointerException
+ * If {@code entry} was {@code null}.
+ */
+ public ConnectionEntryWriter writeEntry(Entry entry)
+ throws ErrorResultIOException, InterruptedIOException,
+ NullPointerException
+ {
+ Validator.ensureNotNull(entry);
+ try
+ {
+ connection.add(entry);
+ }
+ catch (final ErrorResultException e)
+ {
+ throw new ErrorResultIOException(e);
+ }
+ catch (InterruptedException e)
+ {
+ throw new InterruptedIOException(e.getMessage());
+ }
+ return this;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/ldif/EntryReader.java b/sdk/src/org/opends/sdk/ldif/EntryReader.java
new file mode 100644
index 0000000..0a01687
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/EntryReader.java
@@ -0,0 +1,85 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import java.io.Closeable;
+import java.io.IOException;
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.Entry;
+
+
+
+/**
+ * An interface for reading entries from a data source, typically an
+ * LDIF file.
+ * <p>
+ * Implementations must specify the following:
+ * <ul>
+ * <li>Whether or not it is possible for the implementation to encounter
+ * malformed change records and, if it is possible, how they are
+ * handled.
+ * <li>Any synchronization limitations.
+ * </ul>
+ * <p>
+ * TODO: LDIFInputStreamReader
+ * <p>
+ * TODO: SearchResultEntryReader
+ */
+public interface EntryReader extends Closeable
+{
+
+ /**
+ * Closes this entry reader if it is not already closed. Note that
+ * this method does not need to be called if a previous call of
+ * {@link #readEntry()} has returned {@code null}.
+ *
+ * @throws IOException
+ * If an unexpected IO error occurred while closing.
+ */
+ void close() throws IOException;
+
+
+
+ /**
+ * Reads the next entry, blocking if necessary until an entry is
+ * available.
+ *
+ * @return The next entry or {@code null} if there are no more entries
+ * to be read.
+ * @throws DecodeException
+ * If the entry could not be decoded because it was
+ * malformed.
+ * @throws IOException
+ * If an unexpected IO error occurred while reading the
+ * entry.
+ */
+ Entry readEntry() throws DecodeException, IOException;
+}
diff --git a/sdk/src/org/opends/sdk/ldif/EntryWriter.java b/sdk/src/org/opends/sdk/ldif/EntryWriter.java
new file mode 100644
index 0000000..594a2b6
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/EntryWriter.java
@@ -0,0 +1,110 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import java.io.Closeable;
+import java.io.Flushable;
+import java.io.IOException;
+
+import org.opends.sdk.Entry;
+
+
+
+/**
+ * An interface for writing entries to a data source, typically an LDIF
+ * file.
+ * <p>
+ * TODO: FilteredChangeRecordWriter
+ */
+public interface EntryWriter extends Closeable, Flushable
+{
+ /**
+ * Closes this entry writer, flushing it first. Closing a previously
+ * closed entry writer has no effect.
+ *
+ * @throws IOException
+ * If an unexpected IO error occurred while closing.
+ */
+ void close() throws IOException;
+
+
+
+ /**
+ * Flushes this entry writer so that any buffered data is written
+ * immediately to underlying stream, flushing the stream if it is also
+ * {@code Flushable}.
+ * <p>
+ * If the intended destination of this stream is an abstraction
+ * provided by the underlying operating system, for example a file,
+ * then flushing the stream guarantees only that bytes previously
+ * written to the stream are passed to the operating system for
+ * writing; it does not guarantee that they are actually written to a
+ * physical device such as a disk drive.
+ *
+ * @throws IOException
+ * If an unexpected IO error occurred while flushing.
+ */
+ void flush() throws IOException;
+
+
+
+ /**
+ * Writes a comment.
+ *
+ * @param comment
+ * The {@code CharSequence} to be written as a comment.
+ * @return A reference to this entry writer.
+ * @throws IOException
+ * If an unexpected IO error occurred while writing the
+ * comment.
+ * @throws NullPointerException
+ * If {@code comment} was {@code null}.
+ */
+ EntryWriter writeComment(CharSequence comment) throws IOException,
+ NullPointerException;
+
+
+
+ /**
+ * Writes an entry.
+ *
+ * @param entry
+ * The {@code Entry} to be written.
+ * @return A reference to this entry writer.
+ * @throws IOException
+ * If an unexpected IO error occurred while writing the
+ * entry.
+ * @throws NullPointerException
+ * If {@code entry} was {@code null}.
+ */
+ EntryWriter writeEntry(Entry entry) throws IOException,
+ NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/ldif/LDIFChangeRecordReader.java b/sdk/src/org/opends/sdk/ldif/LDIFChangeRecordReader.java
new file mode 100644
index 0000000..c9cc3a8
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/LDIFChangeRecordReader.java
@@ -0,0 +1,719 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import static org.opends.messages.UtilityMessages.*;
+import static org.opends.sdk.util.StaticUtils.toLowerCase;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.opends.messages.Message;
+import org.opends.sdk.*;
+import org.opends.sdk.requests.ModifyDNRequest;
+import org.opends.sdk.requests.ModifyRequest;
+import org.opends.sdk.requests.Requests;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * An LDIF change record reader reads change records using the LDAP Data
+ * Interchange Format (LDIF) from a user defined source.
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc2849">RFC 2849 - The LDAP
+ * Data Interchange Format (LDIF) - Technical Specification </a>
+ */
+public final class LDIFChangeRecordReader extends AbstractLDIFReader
+ implements ChangeRecordReader
+{
+ /**
+ * Parses the provided array of LDIF lines as a single LDIF change
+ * record.
+ *
+ * @param ldifLines
+ * The lines of LDIF to be parsed.
+ * @return The parsed LDIF change record.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code ldifLines} did not contain an LDIF change
+ * record, if it contained multiple change records, if
+ * contained malformed LDIF, or if the change record could
+ * not be decoded using the default schema.
+ * @throws NullPointerException
+ * If {@code ldifLines} was {@code null}.
+ */
+ public static ChangeRecord valueOfLDIFChangeRecord(
+ String... ldifLines) throws LocalizedIllegalArgumentException,
+ NullPointerException
+ {
+ // LDIF change record reader is tolerant to missing change types.
+ LDIFChangeRecordReader reader = new LDIFChangeRecordReader(
+ ldifLines);
+ try
+ {
+ ChangeRecord record = reader.readChangeRecord();
+
+ if (record == null)
+ {
+ // No change record found.
+ Message message = WARN_READ_LDIF_RECORD_NO_CHANGE_RECORD_FOUND
+ .get();
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ if (reader.readChangeRecord() != null)
+ {
+ // Multiple change records found.
+ Message message = WARN_READ_LDIF_RECORD_MULTIPLE_CHANGE_RECORDS_FOUND
+ .get();
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ return record;
+ }
+ catch (DecodeException e)
+ {
+ // Badly formed LDIF.
+ throw new LocalizedIllegalArgumentException(e.getMessageObject());
+ }
+ catch (IOException e)
+ {
+ // This should never happen for a String based reader.
+ Message message = WARN_READ_LDIF_RECORD_UNEXPECTED_IO_ERROR.get(e
+ .getMessage());
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ }
+
+
+
+ /**
+ * Creates a new LDIF change record reader whose source is the
+ * provided input stream.
+ *
+ * @param in
+ * The input stream to use.
+ * @throws NullPointerException
+ * If {@code in} was {@code null}.
+ */
+ public LDIFChangeRecordReader(InputStream in)
+ throws NullPointerException
+ {
+ super(in);
+ }
+
+
+
+ /**
+ * Creates a new LDIF change record reader which will read lines of
+ * LDIF from the provided list of LDIF lines.
+ *
+ * @param ldifLines
+ * The lines of LDIF to be read.
+ * @throws NullPointerException
+ * If {@code ldifLines} was {@code null}.
+ */
+ public LDIFChangeRecordReader(List<String> ldifLines)
+ throws NullPointerException
+ {
+ super(ldifLines);
+ }
+
+
+
+ /**
+ * Creates a new LDIF change record reader which will read lines of
+ * LDIF from the provided array of LDIF lines.
+ *
+ * @param ldifLines
+ * The lines of LDIF to be read.
+ * @throws NullPointerException
+ * If {@code ldifLines} was {@code null}.
+ */
+ public LDIFChangeRecordReader(String... ldifLines)
+ throws NullPointerException
+ {
+ super(Arrays.asList(ldifLines));
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void close() throws IOException
+ {
+ close0();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ChangeRecord readChangeRecord() throws DecodeException,
+ IOException
+ {
+ // Continue until an unfiltered entry is obtained.
+ while (true)
+ {
+ LDIFRecord record = null;
+
+ // Read the set of lines that make up the next entry.
+ record = readLDIFRecord();
+ if (record == null)
+ {
+ return null;
+ }
+
+ // Read the DN of the entry and see if it is one that should be
+ // included in the import.
+ DN entryDN;
+ try
+ {
+ entryDN = readLDIFRecordDN(record);
+ if (entryDN == null)
+ {
+ // Skip version record.
+ continue;
+ }
+ }
+ catch (final DecodeException e)
+ {
+ rejectLDIFRecord(record, e.getMessageObject());
+ continue;
+ }
+
+ // Skip if branch containing the entry DN is excluded.
+ if (isBranchExcluded(entryDN))
+ {
+ final Message message = Message
+ .raw("Skipping entry because it is in excluded branch");
+ skipLDIFRecord(record, message);
+ continue;
+ }
+
+ ChangeRecord changeRecord = null;
+ try
+ {
+ if (!record.iterator.hasNext())
+ {
+ // FIXME: improve error.
+ final Message message = Message.raw("Missing changetype");
+ throw DecodeException.error(message);
+ }
+
+ final KeyValuePair pair = new KeyValuePair();
+ final String ldifLine = readLDIFRecordKeyValuePair(record,
+ pair, false);
+
+ if (!toLowerCase(pair.key).equals("changetype"))
+ {
+ // Default to add change record.
+ changeRecord = parseAddChangeRecordEntry(entryDN, ldifLine,
+ record);
+ }
+ else
+ {
+ final String changeType = toLowerCase(pair.value);
+ if (changeType.equals("add"))
+ {
+ changeRecord = parseAddChangeRecordEntry(entryDN, null,
+ record);
+ }
+ else if (changeType.equals("delete"))
+ {
+ changeRecord = parseDeleteChangeRecordEntry(entryDN, record);
+ }
+ else if (changeType.equals("modify"))
+ {
+ changeRecord = parseModifyChangeRecordEntry(entryDN, record);
+ }
+ else if (changeType.equals("modrdn"))
+ {
+ changeRecord = parseModifyDNChangeRecordEntry(entryDN,
+ record);
+ }
+ else if (changeType.equals("moddn"))
+ {
+ changeRecord = parseModifyDNChangeRecordEntry(entryDN,
+ record);
+ }
+ else
+ {
+ // FIXME: improve error.
+ final Message message = ERR_LDIF_INVALID_CHANGETYPE_ATTRIBUTE
+ .get(pair.value, "add, delete, modify, moddn, modrdn");
+ throw DecodeException.error(message);
+ }
+ }
+ }
+ catch (final DecodeException e)
+ {
+ rejectLDIFRecord(record, e.getMessageObject());
+ continue;
+ }
+
+ if (changeRecord != null)
+ {
+ return changeRecord;
+ }
+ }
+ }
+
+
+
+ /**
+ * Specifies whether or not all operational attributes should be
+ * excluded from any change records that are read from LDIF. The
+ * default is {@code false}.
+ *
+ * @param excludeOperationalAttributes
+ * {@code true} if all operational attributes should be
+ * excluded, or {@code false} otherwise.
+ * @return A reference to this {@code LDIFChangeRecordReader}.
+ */
+ public LDIFChangeRecordReader setExcludeAllOperationalAttributes(
+ boolean excludeOperationalAttributes)
+ {
+ this.excludeOperationalAttributes = excludeOperationalAttributes;
+ return this;
+ }
+
+
+
+ /**
+ * Specifies whether or not all user attributes should be excluded
+ * from any change records that are read from LDIF. The default is
+ * {@code false}.
+ *
+ * @param excludeUserAttributes
+ * {@code true} if all user attributes should be excluded, or
+ * {@code false} otherwise.
+ * @return A reference to this {@code LDIFChangeRecordReader}.
+ */
+ public LDIFChangeRecordReader setExcludeAllUserAttributes(
+ boolean excludeUserAttributes)
+ {
+ this.excludeUserAttributes = excludeUserAttributes;
+ return this;
+ }
+
+
+
+ /**
+ * Excludes the named attribute from any change records that are read
+ * from LDIF. By default all attributes are included unless explicitly
+ * excluded.
+ *
+ * @param attributeDescription
+ * The name of the attribute to be excluded.
+ * @return A reference to this {@code LDIFChangeRecordReader}.
+ */
+ public LDIFChangeRecordReader setExcludeAttribute(
+ AttributeDescription attributeDescription)
+ {
+ Validator.ensureNotNull(attributeDescription);
+ excludeAttributes.add(attributeDescription);
+ return this;
+ }
+
+
+
+ /**
+ * Excludes all change records which target entries beneath the named
+ * entry (inclusive) from being read from LDIF. By default all change
+ * records are read unless explicitly excluded or included.
+ *
+ * @param excludeBranch
+ * The distinguished name of the branch to be excluded.
+ * @return A reference to this {@code LDIFChangeRecordReader}.
+ */
+ public LDIFChangeRecordReader setExcludeBranch(DN excludeBranch)
+ {
+ Validator.ensureNotNull(excludeBranch);
+ excludeBranches.add(excludeBranch);
+ return this;
+ }
+
+
+
+ /**
+ * Ensures that the named attribute is not excluded from any change
+ * records that are read from LDIF. By default all attributes are
+ * included unless explicitly excluded.
+ *
+ * @param attributeDescription
+ * The name of the attribute to be included.
+ * @return A reference to this {@code LDIFChangeRecordReader}.
+ */
+ public LDIFChangeRecordReader setIncludeAttribute(
+ AttributeDescription attributeDescription)
+ {
+ Validator.ensureNotNull(attributeDescription);
+ includeAttributes.add(attributeDescription);
+ return this;
+ }
+
+
+
+ /**
+ * Ensures that all change records which target entries beneath the
+ * named entry (inclusive) are read from LDIF. By default all change
+ * records are read unless explicitly excluded or included.
+ *
+ * @param includeBranch
+ * The distinguished name of the branch to be included.
+ * @return A reference to this {@code LDIFChangeRecordReader}.
+ */
+ public LDIFChangeRecordReader setIncludeBranch(DN includeBranch)
+ {
+ Validator.ensureNotNull(includeBranch);
+ includeBranches.add(includeBranch);
+ return this;
+ }
+
+
+
+ /**
+ * Sets the schema which should be used for decoding change records
+ * that are read from LDIF. The default schema is used if no other is
+ * specified.
+ *
+ * @param schema
+ * The schema which should be used for decoding change
+ * records that are read from LDIF.
+ * @return A reference to this {@code LDIFChangeRecordReader}.
+ */
+ public LDIFChangeRecordReader setSchema(Schema schema)
+ {
+ Validator.ensureNotNull(schema);
+ this.schema = schema;
+ return this;
+ }
+
+
+
+ /**
+ * Specifies whether or not schema validation should be performed for
+ * change records that are read from LDIF. The default is {@code true}
+ * .
+ *
+ * @param validateSchema
+ * {@code true} if schema validation should be performed, or
+ * {@code false} otherwise.
+ * @return A reference to this {@code LDIFChangeRecordReader}.
+ */
+ public LDIFChangeRecordReader setValidateSchema(boolean validateSchema)
+ {
+ this.validateSchema = validateSchema;
+ return this;
+ }
+
+
+
+ private ChangeRecord parseAddChangeRecordEntry(DN entryDN,
+ String lastLDIFLine, LDIFRecord record) throws DecodeException
+ {
+ // Use an Entry for the AttributeSequence.
+ final Entry entry = new SortedEntry(entryDN);
+
+ if (lastLDIFLine != null)
+ {
+ // This line was read when looking for the change type.
+ readLDIFRecordAttributeValue(record, lastLDIFLine, entry);
+ }
+
+ while (record.iterator.hasNext())
+ {
+ final String ldifLine = record.iterator.next();
+ readLDIFRecordAttributeValue(record, ldifLine, entry);
+ }
+
+ return Requests.newAddRequest(entry);
+ }
+
+
+
+ private ChangeRecord parseDeleteChangeRecordEntry(DN entryDN,
+ LDIFRecord record) throws DecodeException
+ {
+ if (record.iterator.hasNext())
+ {
+ // FIXME: include line number in error.
+ final Message message = ERR_LDIF_INVALID_DELETE_ATTRIBUTES.get();
+ throw DecodeException.error(message);
+ }
+
+ return Requests.newDeleteRequest(entryDN);
+ }
+
+
+
+ private ChangeRecord parseModifyChangeRecordEntry(DN entryDN,
+ LDIFRecord record) throws DecodeException
+ {
+ final ModifyRequest modifyRequest = Requests
+ .newModifyRequest(entryDN);
+
+ final KeyValuePair pair = new KeyValuePair();
+ final List<ByteString> attributeValues = new ArrayList<ByteString>();
+
+ while (record.iterator.hasNext())
+ {
+ readLDIFRecordKeyValuePair(record, pair, false);
+ final String changeType = toLowerCase(pair.key);
+
+ ModificationType modType;
+ if (changeType.equals("add"))
+ {
+ modType = ModificationType.ADD;
+ }
+ else if (changeType.equals("delete"))
+ {
+ modType = ModificationType.DELETE;
+ }
+ else if (changeType.equals("replace"))
+ {
+ modType = ModificationType.REPLACE;
+ }
+ else if (changeType.equals("increment"))
+ {
+ modType = ModificationType.INCREMENT;
+ }
+ else
+ {
+ // FIXME: improve error.
+ final Message message = ERR_LDIF_INVALID_MODIFY_ATTRIBUTE.get(
+ pair.key, "add, delete, replace, increment");
+ throw DecodeException.error(message);
+ }
+
+ AttributeDescription attributeDescription;
+ try
+ {
+ attributeDescription = AttributeDescription.valueOf(pair.value,
+ schema);
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ throw DecodeException.error(e.getMessageObject());
+ }
+
+ // Skip the attribute if requested before performing any schema
+ // checking: the attribute may have been excluded because it is
+ // known to violate the schema.
+ if (isAttributeExcluded(attributeDescription))
+ {
+ continue;
+ }
+
+ // Ensure that the binary option is present if required.
+ if (!attributeDescription.getAttributeType().getSyntax()
+ .isBEREncodingRequired())
+ {
+ if (validateSchema
+ && attributeDescription.containsOption("binary"))
+ {
+ final Message message = ERR_LDIF_INVALID_ATTR_OPTION.get(
+ entryDN.toString(), record.lineNumber, pair.value);
+ throw DecodeException.error(message);
+ }
+ }
+ else
+ {
+ attributeDescription = AttributeDescription.create(
+ attributeDescription, "binary");
+ }
+
+ // Now go through the rest of the attributes until the "-" line is
+ // reached.
+ attributeValues.clear();
+ while (record.iterator.hasNext())
+ {
+ final String ldifLine = record.iterator.next();
+ if (ldifLine.equals("-"))
+ {
+ break;
+ }
+
+ // Parse the attribute description.
+ final int colonPos = parseColonPosition(record, ldifLine);
+ final String attrDescr = ldifLine.substring(0, colonPos);
+
+ AttributeDescription attributeDescription2;
+ try
+ {
+ attributeDescription2 = AttributeDescription.valueOf(
+ attrDescr, schema);
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ throw DecodeException.error(e.getMessageObject());
+ }
+
+ // Ensure that the binary option is present if required.
+ if (attributeDescription.getAttributeType().getSyntax()
+ .isBEREncodingRequired())
+ {
+ attributeDescription2 = AttributeDescription.create(
+ attributeDescription2, "binary");
+ }
+
+ if (!attributeDescription2.equals(attributeDescription))
+ {
+ // TODO: include line number.
+ final Message message = ERR_LDIF_INVALID_CHANGERECORD_ATTRIBUTE
+ .get(attributeDescription2.toString(),
+ attributeDescription.toString());
+ throw DecodeException.error(message);
+ }
+
+ // Now parse the attribute value.
+ attributeValues.add(parseSingleValue(record, ldifLine, entryDN,
+ colonPos, attrDescr));
+ }
+
+ Change change = new Change(modType, new LinkedAttribute(
+ attributeDescription, attributeValues));
+ modifyRequest.addChange(change);
+ }
+
+ return modifyRequest;
+ }
+
+
+
+ private ChangeRecord parseModifyDNChangeRecordEntry(DN entryDN,
+ LDIFRecord record) throws DecodeException
+ {
+ ModifyDNRequest modifyDNRequest;
+
+ // Parse the newrdn.
+ if (!record.iterator.hasNext())
+ {
+ // TODO: include line number.
+ final Message message = ERR_LDIF_NO_MOD_DN_ATTRIBUTES.get();
+ throw DecodeException.error(message);
+ }
+
+ final KeyValuePair pair = new KeyValuePair();
+ String ldifLine = record.iterator.next();
+ readLDIFRecordKeyValuePair(record, pair, true);
+ if (!toLowerCase(pair.key).equals("newrdn"))
+ {
+ // FIXME: improve error.
+ final Message message = Message.raw("Missing newrdn");
+ throw DecodeException.error(message);
+ }
+
+ try
+ {
+ final RDN newRDN = RDN.valueOf(pair.value, schema);
+ modifyDNRequest = Requests.newModifyDNRequest(entryDN, newRDN);
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ final Message message = ERR_LDIF_INVALID_DN.get(
+ record.lineNumber, ldifLine, e.getMessageObject());
+ throw DecodeException.error(message);
+ }
+
+ // Parse the deleteoldrdn.
+ if (!record.iterator.hasNext())
+ {
+ // TODO: include line number.
+ final Message message = ERR_LDIF_NO_DELETE_OLDRDN_ATTRIBUTE.get();
+ throw DecodeException.error(message);
+ }
+
+ ldifLine = record.iterator.next();
+ readLDIFRecordKeyValuePair(record, pair, true);
+ if (!toLowerCase(pair.key).equals("deleteoldrdn"))
+ {
+ // FIXME: improve error.
+ final Message message = Message.raw("Missing deleteoldrdn");
+ throw DecodeException.error(message);
+ }
+
+ final String delStr = toLowerCase(pair.value);
+ if (delStr.equals("false") || delStr.equals("no")
+ || delStr.equals("0"))
+ {
+ modifyDNRequest.setDeleteOldRDN(false);
+ }
+ else if (delStr.equals("true") || delStr.equals("yes")
+ || delStr.equals("1"))
+ {
+ modifyDNRequest.setDeleteOldRDN(true);
+ }
+ else
+ {
+ // FIXME: improve error.
+ final Message message = ERR_LDIF_INVALID_DELETE_OLDRDN_ATTRIBUTE
+ .get(pair.value);
+ throw DecodeException.error(message);
+ }
+
+ // Parse the newsuperior if present.
+ if (record.iterator.hasNext())
+ {
+ ldifLine = record.iterator.next();
+ readLDIFRecordKeyValuePair(record, pair, true);
+ if (!toLowerCase(pair.key).equals("newsuperior"))
+ {
+ // FIXME: improve error.
+ final Message message = Message.raw("Missing newsuperior");
+ throw DecodeException.error(message);
+ }
+
+ try
+ {
+ final DN newSuperiorDN = DN.valueOf(pair.value, schema);
+ modifyDNRequest.setNewSuperior(newSuperiorDN.toString());
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ final Message message = ERR_LDIF_INVALID_DN.get(
+ record.lineNumber, ldifLine, e.getMessageObject());
+ throw DecodeException.error(message);
+ }
+ }
+
+ return modifyDNRequest;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/ldif/LDIFChangeRecordWriter.java b/sdk/src/org/opends/sdk/ldif/LDIFChangeRecordWriter.java
new file mode 100644
index 0000000..2346a23
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/LDIFChangeRecordWriter.java
@@ -0,0 +1,478 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+
+import org.opends.sdk.*;
+import org.opends.sdk.requests.AddRequest;
+import org.opends.sdk.requests.DeleteRequest;
+import org.opends.sdk.requests.ModifyDNRequest;
+import org.opends.sdk.requests.ModifyRequest;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * An LDIF change record writer writes change records using the LDAP
+ * Data Interchange Format (LDIF) to a user defined destination.
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc2849">RFC 2849 - The LDAP
+ * Data Interchange Format (LDIF) - Technical Specification </a>
+ */
+public final class LDIFChangeRecordWriter extends AbstractLDIFWriter
+ implements ChangeRecordWriter
+{
+
+ /**
+ * Creates a new LDIF change record writer which will append lines of
+ * LDIF to the provided list.
+ *
+ * @param ldifLines
+ * The list to which lines of LDIF should be appended.
+ */
+ public LDIFChangeRecordWriter(List<String> ldifLines)
+ {
+ super(ldifLines);
+ }
+
+
+
+ /**
+ * Creates a new LDIF change record writer whose destination is the
+ * provided output stream.
+ *
+ * @param out
+ * The output stream to use.
+ */
+ public LDIFChangeRecordWriter(OutputStream out)
+ {
+ super(out);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void close() throws IOException
+ {
+ close0();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void flush() throws IOException
+ {
+ flush0();
+ }
+
+
+
+ /**
+ * Specifies whether or not user-friendly comments should be added
+ * whenever distinguished names or UTF-8 attribute values are
+ * encountered which contained non-ASCII characters. The default is
+ * {@code false}.
+ *
+ * @param addUserFriendlyComments
+ * {@code true} if user-friendly comments should be added, or
+ * {@code false} otherwise.
+ * @return A reference to this {@code LDIFEntryWriter}.
+ */
+ public LDIFChangeRecordWriter setAddUserFriendlyComments(
+ boolean addUserFriendlyComments)
+ {
+ this.addUserFriendlyComments = addUserFriendlyComments;
+ return this;
+ }
+
+
+
+ /**
+ * Specifies whether or not all operational attributes should be
+ * excluded from any change records that are written to LDIF. The
+ * default is {@code false}.
+ *
+ * @param excludeOperationalAttributes
+ * {@code true} if all operational attributes should be
+ * excluded, or {@code false} otherwise.
+ * @return A reference to this {@code LDIFChangeRecordWriter}.
+ */
+ public LDIFChangeRecordWriter setExcludeAllOperationalAttributes(
+ boolean excludeOperationalAttributes)
+ {
+ this.excludeOperationalAttributes = excludeOperationalAttributes;
+ return this;
+ }
+
+
+
+ /**
+ * Specifies whether or not all user attributes should be excluded
+ * from any change records that are written to LDIF. The default is
+ * {@code false}.
+ *
+ * @param excludeUserAttributes
+ * {@code true} if all user attributes should be excluded, or
+ * {@code false} otherwise.
+ * @return A reference to this {@code LDIFChangeRecordWriter}.
+ */
+ public LDIFChangeRecordWriter setExcludeAllUserAttributes(
+ boolean excludeUserAttributes)
+ {
+ this.excludeUserAttributes = excludeUserAttributes;
+ return this;
+ }
+
+
+
+ /**
+ * Excludes the named attribute from any change records that are
+ * written to LDIF. By default all attributes are included unless
+ * explicitly excluded.
+ *
+ * @param attributeDescription
+ * The name of the attribute to be excluded.
+ * @return A reference to this {@code LDIFChangeRecordWriter}.
+ */
+ public LDIFChangeRecordWriter setExcludeAttribute(
+ AttributeDescription attributeDescription)
+ {
+ Validator.ensureNotNull(attributeDescription);
+ excludeAttributes.add(attributeDescription);
+ return this;
+ }
+
+
+
+ /**
+ * Excludes all change records which target entries beneath the named
+ * entry (inclusive) from being written to LDIF. By default all change
+ * records are written unless explicitly excluded or included.
+ *
+ * @param excludeBranch
+ * The distinguished name of the branch to be excluded.
+ * @return A reference to this {@code LDIFChangeRecordWriter}.
+ */
+ public LDIFChangeRecordWriter setExcludeBranch(DN excludeBranch)
+ {
+ Validator.ensureNotNull(excludeBranch);
+ excludeBranches.add(excludeBranch);
+ return this;
+ }
+
+
+
+ /**
+ * Ensures that the named attribute is not excluded from any change
+ * records that are written to LDIF. By default all attributes are
+ * included unless explicitly excluded.
+ *
+ * @param attributeDescription
+ * The name of the attribute to be included.
+ * @return A reference to this {@code LDIFChangeRecordWriter}.
+ */
+ public LDIFChangeRecordWriter setIncludeAttribute(
+ AttributeDescription attributeDescription)
+ {
+ Validator.ensureNotNull(attributeDescription);
+ includeAttributes.add(attributeDescription);
+ return this;
+ }
+
+
+
+ /**
+ * Ensures that all change records which target entries beneath the
+ * named entry (inclusive) are written to LDIF. By default all change
+ * records are written unless explicitly excluded or included.
+ *
+ * @param includeBranch
+ * The distinguished name of the branch to be included.
+ * @return A reference to this {@code LDIFChangeRecordWriter}.
+ */
+ public LDIFChangeRecordWriter setIncludeBranch(DN includeBranch)
+ {
+ Validator.ensureNotNull(includeBranch);
+ includeBranches.add(includeBranch);
+ return this;
+ }
+
+
+
+ /**
+ * Sets the schema which should be used when filtering change records
+ * (not required if no filtering is to be performed). The default
+ * schema is used if no other is specified.
+ *
+ * @param schema
+ * The schema which should be used when filtering change
+ * records.
+ * @return A reference to this {@code LDIFChangeRecordWriter}.
+ */
+ public LDIFChangeRecordWriter setSchema(Schema schema)
+ {
+ Validator.ensureNotNull(schema);
+ this.schema = schema;
+ return this;
+ }
+
+
+
+ /**
+ * Specifies the column at which long lines should be wrapped. A value
+ * less than or equal to zero (the default) indicates that no wrapping
+ * should be performed.
+ *
+ * @param wrapColumn
+ * The column at which long lines should be wrapped.
+ * @return A reference to this {@code LDIFEntryWriter}.
+ */
+ public LDIFChangeRecordWriter setWrapColumn(int wrapColumn)
+ {
+ this.wrapColumn = wrapColumn;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public LDIFChangeRecordWriter writeChangeRecord(AddRequest change)
+ throws IOException, NullPointerException
+ {
+ Validator.ensureNotNull(change);
+
+ // Skip if branch containing the entry is excluded.
+ if (isBranchExcluded(change.getName()))
+ {
+ return this;
+ }
+
+ writeKeyAndValue("dn", change.getName().toString());
+ writeControls(change.getControls());
+ writeLine("changetype: add");
+ for (final Attribute attribute : change.getAttributes())
+ {
+ // Filter the attribute if required.
+ if (isAttributeExcluded(attribute.getAttributeDescription()))
+ {
+ continue;
+ }
+
+ final String attributeDescription =
+ attribute.getAttributeDescriptionAsString();
+ for (final ByteString value : attribute)
+ {
+ writeKeyAndValue(attributeDescription, value);
+ }
+ }
+
+ // Make sure there is a blank line after the entry.
+ impl.println();
+
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public LDIFChangeRecordWriter writeChangeRecord(ChangeRecord change)
+ throws IOException, NullPointerException
+ {
+ Validator.ensureNotNull(change);
+
+ // Skip if branch containing the entry is excluded.
+ if (isBranchExcluded(change.getName()))
+ {
+ return this;
+ }
+
+ final IOException e =
+ change.accept(ChangeRecordVisitorWriter.getInstance(), this);
+ if (e != null)
+ {
+ throw e;
+ }
+ else
+ {
+ return this;
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public LDIFChangeRecordWriter writeChangeRecord(DeleteRequest change)
+ throws IOException, NullPointerException
+ {
+ Validator.ensureNotNull(change);
+
+ // Skip if branch containing the entry is excluded.
+ if (isBranchExcluded(change.getName()))
+ {
+ return this;
+ }
+
+ writeKeyAndValue("dn", change.getName().toString());
+ writeControls(change.getControls());
+ writeLine("changetype: delete");
+
+ // Make sure there is a blank line after the entry.
+ impl.println();
+
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public LDIFChangeRecordWriter writeChangeRecord(ModifyDNRequest change)
+ throws IOException, NullPointerException
+ {
+ Validator.ensureNotNull(change);
+
+ // Skip if branch containing the entry is excluded.
+ if (isBranchExcluded(change.getName()))
+ {
+ return this;
+ }
+
+ writeKeyAndValue("dn", change.getName().toString());
+ writeControls(change.getControls());
+
+ // Write the changetype. Some older tools may not support the
+ // "moddn" changetype, so only use it if a newSuperior element has
+ // been provided, but use modrdn elsewhere.
+ if (change.getNewSuperior() == null)
+ {
+ writeLine("changetype: modrdn");
+ }
+ else
+ {
+ writeLine("changetype: moddn");
+ }
+
+ writeKeyAndValue("newrdn", change.getNewRDN().toString());
+ writeKeyAndValue("deleteoldrdn", change.isDeleteOldRDN() ? "1"
+ : "0");
+ if (change.getNewSuperior() != null)
+ {
+ writeKeyAndValue("newsuperior", change.getNewSuperior()
+ .toString());
+ }
+
+ // Make sure there is a blank line after the entry.
+ impl.println();
+
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public LDIFChangeRecordWriter writeChangeRecord(ModifyRequest change)
+ throws IOException, NullPointerException
+ {
+ Validator.ensureNotNull(change);
+
+ // If there aren't any modifications, then there's nothing to do.
+ if (!change.hasChanges())
+ {
+ return this;
+ }
+
+ // Skip if branch containing the entry is excluded.
+ if (isBranchExcluded(change.getName()))
+ {
+ return this;
+ }
+
+ writeKeyAndValue("dn", change.getName().toString());
+ writeControls(change.getControls());
+ writeLine("changetype: modify");
+
+ for (final Change modification : change.getChanges())
+ {
+ final ModificationType type = modification.getModificationType();
+ final Attribute attribute = modification.getAttribute();
+ final String attributeDescription =
+ attribute.getAttributeDescriptionAsString();
+
+ // Filter the attribute if required.
+ if (isAttributeExcluded(attribute.getAttributeDescription()))
+ {
+ continue;
+ }
+
+ writeKeyAndValue(type.toString(), attributeDescription);
+ for (final ByteString value : attribute)
+ {
+ writeKeyAndValue(attributeDescription, value);
+ }
+ writeLine("-");
+ }
+
+ // Make sure there is a blank line after the entry.
+ impl.println();
+
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public LDIFChangeRecordWriter writeComment(CharSequence comment)
+ throws IOException, NullPointerException
+ {
+ writeComment0(comment);
+ return this;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/ldif/LDIFEntryReader.java b/sdk/src/org/opends/sdk/ldif/LDIFEntryReader.java
new file mode 100644
index 0000000..6c7a475
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/LDIFEntryReader.java
@@ -0,0 +1,432 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import static org.opends.messages.UtilityMessages.WARN_READ_LDIF_RECORD_MULTIPLE_CHANGE_RECORDS_FOUND;
+import static org.opends.messages.UtilityMessages.WARN_READ_LDIF_RECORD_NO_CHANGE_RECORD_FOUND;
+import static org.opends.messages.UtilityMessages.WARN_READ_LDIF_RECORD_UNEXPECTED_IO_ERROR;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
+
+import org.opends.messages.Message;
+import org.opends.sdk.*;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * An LDIF entry reader reads attribute value records (entries) using
+ * the LDAP Data Interchange Format (LDIF) from a user defined source.
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc2849">RFC 2849 - The LDAP
+ * Data Interchange Format (LDIF) - Technical Specification </a>
+ */
+public final class LDIFEntryReader extends AbstractLDIFReader implements
+ EntryReader
+{
+ /**
+ * Parses the provided array of LDIF lines as a single LDIF entry.
+ *
+ * @param ldifLines
+ * The lines of LDIF to be parsed.
+ * @return The parsed LDIF entry.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code ldifLines} did not contain an LDIF entry, if it
+ * contained multiple entries, if contained malformed LDIF,
+ * or if the entry could not be decoded using the default
+ * schema.
+ * @throws NullPointerException
+ * If {@code ldifLines} was {@code null}.
+ */
+ public static Entry valueOfLDIFEntry(String... ldifLines)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ LDIFEntryReader reader = new LDIFEntryReader(ldifLines);
+ try
+ {
+ Entry entry = reader.readEntry();
+
+ if (entry == null)
+ {
+ // No change record found.
+ Message message = WARN_READ_LDIF_RECORD_NO_CHANGE_RECORD_FOUND
+ .get();
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ if (reader.readEntry() != null)
+ {
+ // Multiple change records found.
+ Message message = WARN_READ_LDIF_RECORD_MULTIPLE_CHANGE_RECORDS_FOUND
+ .get();
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ return entry;
+ }
+ catch (DecodeException e)
+ {
+ // Badly formed LDIF.
+ throw new LocalizedIllegalArgumentException(e.getMessageObject());
+ }
+ catch (IOException e)
+ {
+ // This should never happen for a String based reader.
+ Message message = WARN_READ_LDIF_RECORD_UNEXPECTED_IO_ERROR.get(e
+ .getMessage());
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ }
+
+
+
+ /**
+ * Creates a new LDIF entry reader whose source is the provided input
+ * stream.
+ *
+ * @param in
+ * The input stream to use.
+ * @throws NullPointerException
+ * If {@code in} was {@code null}.
+ */
+ public LDIFEntryReader(InputStream in) throws NullPointerException
+ {
+ super(in);
+ }
+
+
+
+ /**
+ * Creates a new LDIF entry reader which will read lines of LDIF from
+ * the provided list of LDIF lines.
+ *
+ * @param ldifLines
+ * The lines of LDIF to be read.
+ * @throws NullPointerException
+ * If {@code ldifLines} was {@code null}.
+ */
+ public LDIFEntryReader(List<String> ldifLines)
+ throws NullPointerException
+ {
+ super(ldifLines);
+ }
+
+
+
+ /**
+ * Creates a new LDIF entry reader which will read lines of LDIF from
+ * the provided array of LDIF lines.
+ *
+ * @param ldifLines
+ * The lines of LDIF to be read.
+ * @throws NullPointerException
+ * If {@code ldifLines} was {@code null}.
+ */
+ public LDIFEntryReader(String... ldifLines)
+ throws NullPointerException
+ {
+ super(Arrays.asList(ldifLines));
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void close() throws IOException
+ {
+ close0();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Entry readEntry() throws DecodeException, IOException
+ {
+ // Continue until an unfiltered entry is obtained.
+ while (true)
+ {
+ LDIFRecord record = null;
+
+ // Read the set of lines that make up the next entry.
+ record = readLDIFRecord();
+ if (record == null)
+ {
+ return null;
+ }
+
+ // Read the DN of the entry and see if it is one that should be
+ // included in the import.
+ DN entryDN;
+ try
+ {
+ entryDN = readLDIFRecordDN(record);
+ if (entryDN == null)
+ {
+ // Skip version record.
+ continue;
+ }
+ }
+ catch (final DecodeException e)
+ {
+ rejectLDIFRecord(record, e.getMessageObject());
+ continue;
+ }
+
+ // Skip if branch containing the entry DN is excluded.
+ if (isBranchExcluded(entryDN))
+ {
+ final Message message = Message
+ .raw("Skipping entry because it is in excluded branch");
+ skipLDIFRecord(record, message);
+ continue;
+ }
+
+ // Use an Entry for the AttributeSequence.
+ final Entry entry = new SortedEntry(entryDN);
+ try
+ {
+ while (record.iterator.hasNext())
+ {
+ final String ldifLine = record.iterator.next();
+ readLDIFRecordAttributeValue(record, ldifLine, entry);
+ }
+ }
+ catch (final DecodeException e)
+ {
+ rejectLDIFRecord(record, e.getMessageObject());
+ continue;
+ }
+
+ // Skip if the entry is excluded by any filters.
+ if (isEntryExcluded(entry))
+ {
+ final Message message = Message
+ .raw("Skipping entry due to exclusing filters");
+ skipLDIFRecord(record, message);
+ continue;
+ }
+
+ return entry;
+ }
+ }
+
+
+
+ /**
+ * Specifies whether or not all operational attributes should be
+ * excluded from any entries that are read from LDIF. The default is
+ * {@code false}.
+ *
+ * @param excludeOperationalAttributes
+ * {@code true} if all operational attributes should be
+ * excluded, or {@code false} otherwise.
+ * @return A reference to this {@code LDIFEntryReader}.
+ */
+ public LDIFEntryReader setExcludeAllOperationalAttributes(
+ boolean excludeOperationalAttributes)
+ {
+ this.excludeOperationalAttributes = excludeOperationalAttributes;
+ return this;
+ }
+
+
+
+ /**
+ * Specifies whether or not all user attributes should be excluded
+ * from any entries that are read from LDIF. The default is {@code
+ * false}.
+ *
+ * @param excludeUserAttributes
+ * {@code true} if all user attributes should be excluded, or
+ * {@code false} otherwise.
+ * @return A reference to this {@code LDIFEntryReader}.
+ */
+ public LDIFEntryReader setExcludeAllUserAttributes(
+ boolean excludeUserAttributes)
+ {
+ this.excludeUserAttributes = excludeUserAttributes;
+ return this;
+ }
+
+
+
+ /**
+ * Excludes the named attribute from any entries that are read from
+ * LDIF. By default all attributes are included unless explicitly
+ * excluded.
+ *
+ * @param attributeDescription
+ * The name of the attribute to be excluded.
+ * @return A reference to this {@code LDIFEntryReader}.
+ */
+ public LDIFEntryReader setExcludeAttribute(
+ AttributeDescription attributeDescription)
+ {
+ Validator.ensureNotNull(attributeDescription);
+ excludeAttributes.add(attributeDescription);
+ return this;
+ }
+
+
+
+ /**
+ * Excludes all entries beneath the named entry (inclusive) from being
+ * read from LDIF. By default all entries are written unless
+ * explicitly excluded or included by branches or filters.
+ *
+ * @param excludeBranch
+ * The distinguished name of the branch to be excluded.
+ * @return A reference to this {@code LDIFEntryReader}.
+ */
+ public LDIFEntryReader setExcludeBranch(DN excludeBranch)
+ {
+ Validator.ensureNotNull(excludeBranch);
+ excludeBranches.add(excludeBranch);
+ return this;
+ }
+
+
+
+ /**
+ * Excludes all entries which match the provided filter matcher from
+ * being read from LDIF. By default all entries are read unless
+ * explicitly excluded or included by branches or filters.
+ *
+ * @param excludeFilter
+ * The filter matcher.
+ * @return A reference to this {@code LDIFEntryReader}.
+ */
+ public LDIFEntryReader setExcludeFilter(Matcher excludeFilter)
+ {
+ Validator.ensureNotNull(excludeFilter);
+ excludeFilters.add(excludeFilter);
+ return this;
+ }
+
+
+
+ /**
+ * Ensures that the named attribute is not excluded from any entries
+ * that are read from LDIF. By default all attributes are included
+ * unless explicitly excluded.
+ *
+ * @param attributeDescription
+ * The name of the attribute to be included.
+ * @return A reference to this {@code LDIFEntryReader}.
+ */
+ public LDIFEntryReader setIncludeAttribute(
+ AttributeDescription attributeDescription)
+ {
+ Validator.ensureNotNull(attributeDescription);
+ includeAttributes.add(attributeDescription);
+ return this;
+ }
+
+
+
+ /**
+ * Ensures that all entries beneath the named entry (inclusive) are
+ * read from LDIF. By default all entries are written unless
+ * explicitly excluded or included by branches or filters.
+ *
+ * @param includeBranch
+ * The distinguished name of the branch to be included.
+ * @return A reference to this {@code LDIFEntryReader}.
+ */
+ public LDIFEntryReader setIncludeBranch(DN includeBranch)
+ {
+ Validator.ensureNotNull(includeBranch);
+ includeBranches.add(includeBranch);
+ return this;
+ }
+
+
+
+ /**
+ * Ensures that all entries which match the provided filter matcher
+ * are read from LDIF. By default all entries are read unless
+ * explicitly excluded or included by branches or filters.
+ *
+ * @param includeFilter
+ * The filter matcher.
+ * @return A reference to this {@code LDIFEntryReader}.
+ */
+ public LDIFEntryReader setIncludeFilter(Matcher includeFilter)
+ {
+ Validator.ensureNotNull(includeFilter);
+ includeFilters.add(includeFilter);
+ return this;
+ }
+
+
+
+ /**
+ * Sets the schema which should be used for decoding entries that are
+ * read from LDIF. The default schema is used if no other is
+ * specified.
+ *
+ * @param schema
+ * The schema which should be used for decoding entries that
+ * are read from LDIF.
+ * @return A reference to this {@code LDIFEntryReader}.
+ */
+ public LDIFEntryReader setSchema(Schema schema)
+ {
+ Validator.ensureNotNull(schema);
+ this.schema = schema;
+ return this;
+ }
+
+
+
+ /**
+ * Specifies whether or not schema validation should be performed for
+ * entries that are read from LDIF. The default is {@code true}.
+ *
+ * @param validateSchema
+ * {@code true} if schema validation should be performed, or
+ * {@code false} otherwise.
+ * @return A reference to this {@code LDIFEntryReader}.
+ */
+ public LDIFEntryReader setValidateSchema(boolean validateSchema)
+ {
+ this.validateSchema = validateSchema;
+ return this;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/ldif/LDIFEntryWriter.java b/sdk/src/org/opends/sdk/ldif/LDIFEntryWriter.java
new file mode 100644
index 0000000..eed7202
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/LDIFEntryWriter.java
@@ -0,0 +1,360 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+
+import org.opends.sdk.*;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * An LDIF entry writer writes attribute value records (entries) using
+ * the LDAP Data Interchange Format (LDIF) to a user defined
+ * destination.
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc2849">RFC 2849 - The LDAP
+ * Data Interchange Format (LDIF) - Technical Specification </a>
+ */
+public final class LDIFEntryWriter extends AbstractLDIFWriter implements
+ EntryWriter
+{
+
+ /**
+ * Creates a new LDIF entry writer which will append lines of LDIF to
+ * the provided list.
+ *
+ * @param ldifLines
+ * The list to which lines of LDIF should be appended.
+ */
+ public LDIFEntryWriter(List<String> ldifLines)
+ {
+ super(ldifLines);
+ }
+
+
+
+ /**
+ * Creates a new LDIF entry writer whose destination is the provided
+ * output stream.
+ *
+ * @param out
+ * The output stream to use.
+ */
+ public LDIFEntryWriter(OutputStream out)
+ {
+ super(out);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void close() throws IOException
+ {
+ close0();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void flush() throws IOException
+ {
+ flush0();
+ }
+
+
+
+ /**
+ * Specifies whether or not user-friendly comments should be added
+ * whenever distinguished names or UTF-8 attribute values are
+ * encountered which contained non-ASCII characters. The default is
+ * {@code false}.
+ *
+ * @param addUserFriendlyComments
+ * {@code true} if user-friendly comments should be added, or
+ * {@code false} otherwise.
+ * @return A reference to this {@code LDIFEntryWriter}.
+ */
+ public LDIFEntryWriter setAddUserFriendlyComments(
+ boolean addUserFriendlyComments)
+ {
+ this.addUserFriendlyComments = addUserFriendlyComments;
+ return this;
+ }
+
+
+
+ /**
+ * Specifies whether or not all operational attributes should be
+ * excluded from any entries that are written to LDIF. The default is
+ * {@code false}.
+ *
+ * @param excludeOperationalAttributes
+ * {@code true} if all operational attributes should be
+ * excluded, or {@code false} otherwise.
+ * @return A reference to this {@code LDIFEntryWriter}.
+ */
+ public LDIFEntryWriter setExcludeAllOperationalAttributes(
+ boolean excludeOperationalAttributes)
+ {
+ this.excludeOperationalAttributes = excludeOperationalAttributes;
+ return this;
+ }
+
+
+
+ /**
+ * Specifies whether or not all user attributes should be excluded
+ * from any entries that are written to LDIF. The default is {@code
+ * false}.
+ *
+ * @param excludeUserAttributes
+ * {@code true} if all user attributes should be excluded, or
+ * {@code false} otherwise.
+ * @return A reference to this {@code LDIFEntryWriter}.
+ */
+ public LDIFEntryWriter setExcludeAllUserAttributes(
+ boolean excludeUserAttributes)
+ {
+ this.excludeUserAttributes = excludeUserAttributes;
+ return this;
+ }
+
+
+
+ /**
+ * Excludes the named attribute from any entries that are written to
+ * LDIF. By default all attributes are included unless explicitly
+ * excluded.
+ *
+ * @param attributeDescription
+ * The name of the attribute to be excluded.
+ * @return A reference to this {@code LDIFEntryWriter}.
+ */
+ public LDIFEntryWriter setExcludeAttribute(
+ AttributeDescription attributeDescription)
+ {
+ Validator.ensureNotNull(attributeDescription);
+ excludeAttributes.add(attributeDescription);
+ return this;
+ }
+
+
+
+ /**
+ * Excludes all entries beneath the named entry (inclusive) from being
+ * written to LDIF. By default all entries are written unless
+ * explicitly excluded or included by branches or filters.
+ *
+ * @param excludeBranch
+ * The distinguished name of the branch to be excluded.
+ * @return A reference to this {@code LDIFEntryWriter}.
+ */
+ public LDIFEntryWriter setExcludeBranch(DN excludeBranch)
+ {
+ Validator.ensureNotNull(excludeBranch);
+ excludeBranches.add(excludeBranch);
+ return this;
+ }
+
+
+
+ /**
+ * Excludes all entries which match the provided filter matcher from
+ * being written to LDIF. By default all entries are written unless
+ * explicitly excluded or included by branches or filters.
+ *
+ * @param excludeFilter
+ * The filter matcher.
+ * @return A reference to this {@code LDIFEntryWriter}.
+ */
+ public LDIFEntryWriter setExcludeFilter(Matcher excludeFilter)
+ {
+ Validator.ensureNotNull(excludeFilter);
+ excludeFilters.add(excludeFilter);
+ return this;
+ }
+
+
+
+ /**
+ * Ensures that the named attribute is not excluded from any entries
+ * that are written to LDIF. By default all attributes are included
+ * unless explicitly excluded.
+ *
+ * @param attributeDescription
+ * The name of the attribute to be included.
+ * @return A reference to this {@code LDIFEntryWriter}.
+ */
+ public LDIFEntryWriter setIncludeAttribute(
+ AttributeDescription attributeDescription)
+ {
+ Validator.ensureNotNull(attributeDescription);
+ includeAttributes.add(attributeDescription);
+ return this;
+ }
+
+
+
+ /**
+ * Ensures that all entries beneath the named entry (inclusive) are
+ * written to LDIF. By default all entries are written unless
+ * explicitly excluded or included by branches or filters.
+ *
+ * @param includeBranch
+ * The distinguished name of the branch to be included.
+ * @return A reference to this {@code LDIFEntryWriter}.
+ */
+ public LDIFEntryWriter setIncludeBranch(DN includeBranch)
+ {
+ Validator.ensureNotNull(includeBranch);
+ includeBranches.add(includeBranch);
+ return this;
+ }
+
+
+
+ /**
+ * Ensures that all entries which match the provided filter matcher
+ * are written to LDIF. By default all entries are written unless
+ * explicitly excluded or included by branches or filters.
+ *
+ * @param includeFilter
+ * The filter matcher.
+ * @return A reference to this {@code LDIFEntryWriter}.
+ */
+ public LDIFEntryWriter setIncludeFilter(Matcher includeFilter)
+ {
+ Validator.ensureNotNull(includeFilter);
+ includeFilters.add(includeFilter);
+ return this;
+ }
+
+
+
+ /**
+ * Sets the schema which should be used when filtering entries (not
+ * required if no filtering is to be performed). The default schema is
+ * used if no other is specified.
+ *
+ * @param schema
+ * The schema which should be used when filtering entries.
+ * @return A reference to this {@code LDIFEntryWriter}.
+ */
+ public LDIFEntryWriter setSchema(Schema schema)
+ {
+ Validator.ensureNotNull(schema);
+ this.schema = schema;
+ return this;
+ }
+
+
+
+ /**
+ * Specifies the column at which long lines should be wrapped. A value
+ * less than or equal to zero (the default) indicates that no wrapping
+ * should be performed.
+ *
+ * @param wrapColumn
+ * The column at which long lines should be wrapped.
+ * @return A reference to this {@code LDIFEntryWriter}.
+ */
+ public LDIFEntryWriter setWrapColumn(int wrapColumn)
+ {
+ this.wrapColumn = wrapColumn;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public LDIFEntryWriter writeComment(CharSequence comment)
+ throws IOException, NullPointerException
+ {
+ writeComment0(comment);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public LDIFEntryWriter writeEntry(Entry entry) throws IOException,
+ NullPointerException
+ {
+ Validator.ensureNotNull(entry);
+
+ // Skip if branch containing the entry is excluded.
+ if (isBranchExcluded(entry.getName()))
+ {
+ return this;
+ }
+
+ // Skip if the entry is excluded by any filters.
+ if (isEntryExcluded(entry))
+ {
+ return this;
+ }
+
+ writeKeyAndValue("dn", entry.getName().toString());
+ for (final Attribute attribute : entry.getAttributes())
+ {
+ // Filter the attribute if required.
+ if (isAttributeExcluded(attribute.getAttributeDescription()))
+ {
+ continue;
+ }
+
+ final String attributeDescription =
+ attribute.getAttributeDescriptionAsString();
+ for (final ByteString value : attribute)
+ {
+ writeKeyAndValue(attributeDescription, value);
+ }
+ }
+
+ // Make sure there is a blank line after the entry.
+ impl.println();
+
+ return this;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/ldif/package-info.java b/sdk/src/org/opends/sdk/ldif/package-info.java
new file mode 100755
index 0000000..d471266
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/package-info.java
@@ -0,0 +1,46 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+/**
+ * OpenDS LDIF support.
+ *
+ * <h1>TO DO</h1>
+ * <ul>
+ * <li>Make LDIFEntryReader concurrent and support DN reservation.
+ * <li>LDIF*Reader Reject and skip support
+ * <li>Remaining schema checking (e.g. binary option)
+ * <li>Fix error messages (prefix with file/lineno)
+ * <li>Support multiple LDIF*Reader sources
+ * <li>Support EntryWriter splitting
+ * <li>Support LDIFConnectionFactory
+ * <li>Comments and optional charset encoding?
+ * </ul>
+ */
+package org.opends.sdk.ldif;
+
+
+
diff --git a/sdk/src/org/opends/sdk/package-info.java b/sdk/src/org/opends/sdk/package-info.java
new file mode 100755
index 0000000..a7bc4bb
--- /dev/null
+++ b/sdk/src/org/opends/sdk/package-info.java
@@ -0,0 +1,87 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+/**
+ * OpenDS SDK.
+ *
+ * <h1>TODO</h1>
+ * <ul>
+ * <li>LDIF support <b>[Matt]</b>
+ * <ul>
+ * <li>LDIFReader
+ * <ul>
+ * <li>filtered reader (this should wrap an entry enumeration)
+ * <li>should implement generic entry enumeration API.
+ * </ul>
+ * <li>LDIFWriter
+ * <ul>
+ * <li>add comments for DNs
+ * <li>comments in native charset
+ * <li>rest of output must be in ASCII
+ * </ul>
+ * </ul>
+ * <li>Messages
+ * <li>Logging?
+ * <li>Single entry search, blocking search <b>[Bo]</b>
+ * <li>Exceptions sub-types for ErrorResultException (e.g. referrals, assertion failures, client side errors).
+ * <li>Refactor non-schema aware request / response APIs - how should they handle duplicate attribute descriptions and values? I.e. what matching should be performed: do they have List or Set semantics? [Matt]
+ * <ul>
+ * <li>AttributeValueSequence -> AttributeValueCollection?
+ * <li>AttributeSequence -> AttributeCollection?
+ * <li>SearchResultEntry must be cheap to decode in non schema case.
+ * <li>Schema aware versions of these should provide set semantics w.r.t. attribute descriptions and attribute values.
+ * </ul>
+ * <li>How should non-default Grizzly transport be specified by the application?
+ * <li>Unmodifiable requests and responses
+ * <li>Check that it is possible to create SearchResultEntry objects with empty attributes.
+ * <li>Nameable? All objects that have a getName() method
+ * <li>DN, RDN - check APIs. <b>[Matt]</b>
+ * <li>Schema - clean up abstract stuff. Ensure exception handling is correct. <b>[Matt]</b>
+ * <li>Enum / GeneralizedTime parsing function
+ * <li>LDAP connection request timeouts configured using LDAPConnectionOptions.
+ * <li>Re-instate Connection.isValid()
+ * <li>Support parameters in result handlers.
+ * <li>Javadoc
+ * <li>Unit tests
+ * <li>Move to standalone source tree
+ * <li>LDAP URL support and referral support
+ * <li>Thread safe DN caching
+ * <li>Escapes in substring filter
+ * <li>Threading model for decoding messages and calling result handlers
+ * <li>SASL for CLI tools
+ * <li>IBM JVM SSL support?
+ * <li>Intermediate response support.
+ * <li>Consider using Collections instead of Iterables.
+ * <li>Get rid of write lock on connections so encoding can be done in parallel using Grizzly's buffers
+ * <li>Should we dispose of the SASLContext on rebind?
+ * </ul>
+ *
+ */
+package org.opends.sdk;
+
+
+
diff --git a/sdk/src/org/opends/sdk/requests/AbandonRequest.java b/sdk/src/org/opends/sdk/requests/AbandonRequest.java
new file mode 100644
index 0000000..e1d07e6
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/AbandonRequest.java
@@ -0,0 +1,147 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.controls.Control;
+
+
+
+/**
+ * The Abandon operation allows a client to request that the server
+ * abandon an uncompleted operation.
+ * <p>
+ * Abandon, Bind, Unbind, and StartTLS operations cannot be abandoned.
+ */
+public interface AbandonRequest extends Request
+{
+ /**
+ * Adds the provided control to this request.
+ *
+ * @param control
+ * The control to be added to this request.
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be added.
+ * @throws NullPointerException
+ * If {@code control} was {@code null}.
+ */
+ AbandonRequest addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Removes all the controls included with this request.
+ *
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ */
+ AbandonRequest clearControls() throws UnsupportedOperationException;
+
+
+
+ /**
+ * Returns the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be returned.
+ * @return The control, or {@code null} if the control is not included
+ * with this request.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control getControl(String oid) throws NullPointerException;
+
+
+
+ /**
+ * Returns an {@code Iterable} containing the controls included with
+ * this request. The returned {@code Iterable} may be used to remove
+ * controls if permitted by this request.
+ *
+ * @return An {@code Iterable} containing the controls.
+ */
+ Iterable<Control> getControls();
+
+
+
+ /**
+ * Returns the message ID of the request to be abandoned.
+ *
+ * @return The message ID of the request to be abandoned.
+ */
+ int getMessageID();
+
+
+
+ /**
+ * Indicates whether or not this request has any controls.
+ *
+ * @return {@code true} if this request has any controls, otherwise
+ * {@code false}.
+ */
+ boolean hasControls();
+
+
+
+ /**
+ * Removes the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be removed.
+ * @return The removed control, or {@code null} if the control is not
+ * included with this request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the message ID of the request to be abandoned.
+ *
+ * @param id
+ * The message ID of the request to be abandoned.
+ * @return This abandon request.
+ * @throws UnsupportedOperationException
+ * If this abandon request does not permit the message ID to
+ * be set.
+ */
+ AbandonRequest setMessageID(int id)
+ throws UnsupportedOperationException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/AbandonRequestImpl.java b/sdk/src/org/opends/sdk/requests/AbandonRequestImpl.java
new file mode 100644
index 0000000..88fa737
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/AbandonRequestImpl.java
@@ -0,0 +1,95 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+/**
+ * Abandon request implementation.
+ */
+final class AbandonRequestImpl extends
+ AbstractRequestImpl<AbandonRequest> implements AbandonRequest
+{
+
+ private int messageID;
+
+
+
+ /**
+ * Creates a new abandon request using the provided message ID.
+ *
+ * @param messageID
+ * The message ID of the request to be abandoned.
+ */
+ AbandonRequestImpl(int messageID)
+ {
+ this.messageID = messageID;
+ }
+
+
+
+ public int getMessageID()
+ {
+ return messageID;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public AbandonRequest setMessageID(int id)
+ throws UnsupportedOperationException
+ {
+ this.messageID = id;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("AbandonRequest(messageID=");
+ builder.append(getMessageID());
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+
+
+
+ AbandonRequest getThis()
+ {
+ return this;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/AbstractBindRequest.java b/sdk/src/org/opends/sdk/requests/AbstractBindRequest.java
new file mode 100644
index 0000000..612740b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/AbstractBindRequest.java
@@ -0,0 +1,73 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.DN;
+
+
+
+/**
+ * An abstract Bind request which can be used as the basis for
+ * implementing new authentication methods.
+ *
+ * @param <R>
+ * The type of Bind request.
+ */
+public abstract class AbstractBindRequest<R extends BindRequest>
+ extends AbstractRequestImpl<R> implements BindRequest
+{
+
+ /**
+ * Creates a new abstract bind request.
+ */
+ protected AbstractBindRequest()
+ {
+ // Nothing to do.
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public abstract DN getName();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @SuppressWarnings("unchecked")
+ final R getThis()
+ {
+ return (R) this;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/AbstractExtendedRequest.java b/sdk/src/org/opends/sdk/requests/AbstractExtendedRequest.java
new file mode 100644
index 0000000..9422db6
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/AbstractExtendedRequest.java
@@ -0,0 +1,117 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.extensions.ExtendedOperation;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * An abstract Extended request which can be used as the basis for
+ * implementing new Extended operations.
+ *
+ * @param <R>
+ * The type of extended request.
+ * @param <S>
+ * The type of result.
+ */
+public abstract class AbstractExtendedRequest<R extends ExtendedRequest<S>, S extends Result>
+ extends AbstractRequestImpl<R> implements ExtendedRequest<S>
+{
+
+ /**
+ * Creates a new abstract extended request.
+ */
+ protected AbstractExtendedRequest()
+ {
+ // Nothing to do.
+ }
+
+
+
+ /**
+ * Returns the extended operation associated with this extended
+ * request.
+ * <p>
+ * FIXME: this should not be exposed to clients.
+ *
+ * @return The extended operation associated with this extended
+ * request.
+ */
+ public abstract ExtendedOperation<R, S> getExtendedOperation();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public abstract String getRequestName();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public abstract ByteString getRequestValue();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("ExtendedRequest(requestName=");
+ builder.append(getRequestName());
+ builder.append(", requestValue=");
+ final ByteString value = getRequestValue();
+ builder.append(value == null ? ByteString.empty() : value);
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @SuppressWarnings("unchecked")
+ final R getThis()
+ {
+ return (R) this;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/AbstractRequestImpl.java b/sdk/src/org/opends/sdk/requests/AbstractRequestImpl.java
new file mode 100644
index 0000000..6cdfea1
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/AbstractRequestImpl.java
@@ -0,0 +1,170 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Abstract request implementation.
+ *
+ * @param <R>
+ * The type of request.
+ */
+abstract class AbstractRequestImpl<R extends Request> implements
+ Request
+{
+ private final List<Control> controls = new LinkedList<Control>();
+
+
+
+ /**
+ * Creates a new abstract request implementation.
+ */
+ AbstractRequestImpl()
+ {
+ // No implementation required.
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final R addControl(Control control)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(control);
+ controls.add(control);
+ return getThis();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final R clearControls()
+ {
+ controls.clear();
+ return getThis();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final Control getControl(String oid)
+ {
+ Validator.ensureNotNull(oid);
+
+ // Avoid creating an iterator if possible.
+ if (controls.isEmpty())
+ {
+ return null;
+ }
+
+ for (final Control control : controls)
+ {
+ if (control.getOID().equals(oid))
+ {
+ return control;
+ }
+ }
+
+ return null;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final Iterable<Control> getControls()
+ {
+ return controls;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final boolean hasControls()
+ {
+ return !controls.isEmpty();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final Control removeControl(String oid)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(oid);
+
+ // Avoid creating an iterator if possible.
+ if (controls.isEmpty())
+ {
+ return null;
+ }
+
+ final Iterator<Control> iterator = controls.iterator();
+ while (iterator.hasNext())
+ {
+ final Control control = iterator.next();
+ if (control.getOID().equals(oid))
+ {
+ iterator.remove();
+ return control;
+ }
+ }
+
+ return null;
+ }
+
+
+
+ public abstract String toString();
+
+
+
+ abstract R getThis();
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/AbstractUnmodifiableRequestImpl.java b/sdk/src/org/opends/sdk/requests/AbstractUnmodifiableRequestImpl.java
new file mode 100644
index 0000000..98132db
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/AbstractUnmodifiableRequestImpl.java
@@ -0,0 +1,138 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.Iterables;
+
+
+
+/**
+ * Unmodifiable request implementation.
+ *
+ * @param <R>
+ * The type of request.
+ */
+abstract class AbstractUnmodifiableRequestImpl<R extends Request>
+ implements Request
+{
+
+ private final R impl;
+
+
+
+ /**
+ * Creates a new unmodifiable request implementation.
+ *
+ * @param impl
+ * The underlying request implementation to be made
+ * unmodifiable.
+ */
+ AbstractUnmodifiableRequestImpl(R impl)
+ {
+ this.impl = impl;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final R addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final R clearControls() throws UnsupportedOperationException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final Control getControl(String oid)
+ throws NullPointerException
+ {
+ // FIXME: ensure that controls are immutable.
+ return impl.getControl(oid);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final Iterable<Control> getControls()
+ {
+ // FIXME: ensure that controls are immutable.
+ return Iterables.unmodifiable(impl.getControls());
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final boolean hasControls()
+ {
+ return impl.hasControls();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final String toString()
+ {
+ return impl.toString();
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/AddRequest.java b/sdk/src/org/opends/sdk/requests/AddRequest.java
new file mode 100644
index 0000000..6629aae
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/AddRequest.java
@@ -0,0 +1,341 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import java.util.Collection;
+
+import org.opends.sdk.Attribute;
+import org.opends.sdk.AttributeDescription;
+import org.opends.sdk.DN;
+import org.opends.sdk.Entry;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.ldif.ChangeRecord;
+import org.opends.sdk.ldif.ChangeRecordVisitor;
+import org.opends.sdk.schema.ObjectClass;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * The Add operation allows a client to request the addition of an entry
+ * into the Directory.
+ * <p>
+ * The RDN attribute(s) may or may not be included in the Add request.
+ * NO-USER-MODIFICATION attributes such as the {@code createTimestamp}
+ * or {@code creatorsName} attributes must not be included, since the
+ * server maintains these automatically.
+ * <p>
+ * FIXME: clean up methods, clearly define schema behavior.
+ */
+public interface AddRequest extends Request, ChangeRecord, Entry
+{
+ /**
+ * {@inheritDoc}
+ */
+ <R, P> R accept(ChangeRecordVisitor<R, P> v, P p);
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ boolean addAttribute(Attribute attribute)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ boolean addAttribute(Attribute attribute,
+ Collection<ByteString> duplicateValues)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ AddRequest addAttribute(String attributeDescription, Object... values)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Adds the provided control to this request.
+ *
+ * @param control
+ * The control to be added to this request.
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be added.
+ * @throws NullPointerException
+ * If {@code control} was {@code null}.
+ */
+ AddRequest addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ AddRequest clearAttributes() throws UnsupportedOperationException;
+
+
+
+ /**
+ * Removes all the controls included with this request.
+ *
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ */
+ AddRequest clearControls() throws UnsupportedOperationException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ boolean containsAttribute(AttributeDescription attributeDescription)
+ throws NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ boolean containsAttribute(String attributeDescription)
+ throws LocalizedIllegalArgumentException, NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ boolean containsObjectClass(ObjectClass objectClass)
+ throws NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ boolean containsObjectClass(String objectClass)
+ throws NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Iterable<Attribute> findAttributes(
+ AttributeDescription attributeDescription)
+ throws NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Iterable<Attribute> findAttributes(String attributeDescription)
+ throws LocalizedIllegalArgumentException, NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Attribute getAttribute(AttributeDescription attributeDescription)
+ throws NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Attribute getAttribute(String attributeDescription)
+ throws LocalizedIllegalArgumentException, NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ int getAttributeCount();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Iterable<Attribute> getAttributes();
+
+
+
+ /**
+ * Returns the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be returned.
+ * @return The control, or {@code null} if the control is not included
+ * with this request.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control getControl(String oid) throws NullPointerException;
+
+
+
+ /**
+ * Returns an {@code Iterable} containing the controls included with
+ * this request. The returned {@code Iterable} may be used to remove
+ * controls if permitted by this request.
+ *
+ * @return An {@code Iterable} containing the controls.
+ */
+ Iterable<Control> getControls();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ DN getName();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Iterable<String> getObjectClasses();
+
+
+
+ /**
+ * Indicates whether or not this request has any controls.
+ *
+ * @return {@code true} if this request has any controls, otherwise
+ * {@code false}.
+ */
+ boolean hasControls();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ boolean removeAttribute(Attribute attribute,
+ Collection<ByteString> missingValues)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ boolean removeAttribute(AttributeDescription attributeDescription)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ AddRequest removeAttribute(String attributeDescription)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ AddRequest removeAttribute(String attributeDescription,
+ Object... values) throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Removes the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be removed.
+ * @return The removed control, or {@code null} if the control is not
+ * included with this request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ boolean replaceAttribute(Attribute attribute)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ AddRequest replaceAttribute(String attributeDescription,
+ Object... values) throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ AddRequest setName(DN dn) throws UnsupportedOperationException,
+ NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ AddRequest setName(String dn)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/AddRequestImpl.java b/sdk/src/org/opends/sdk/requests/AddRequestImpl.java
new file mode 100644
index 0000000..91cb043
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/AddRequestImpl.java
@@ -0,0 +1,387 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import java.util.Collection;
+
+import org.opends.sdk.Attribute;
+import org.opends.sdk.AttributeDescription;
+import org.opends.sdk.DN;
+import org.opends.sdk.Entry;
+import org.opends.sdk.ldif.ChangeRecordVisitor;
+import org.opends.sdk.schema.ObjectClass;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * Add request implementation.
+ */
+final class AddRequestImpl extends AbstractRequestImpl<AddRequest>
+ implements AddRequest
+{
+
+ private final Entry entry;
+
+
+
+ /**
+ * Creates a new add request backed by the provided entry.
+ * Modifications made to {@code entry} will be reflected in the
+ * returned add request. The returned add request supports updates to
+ * its list of controls, as well as updates to the name and attributes
+ * if the underlying entry allows.
+ *
+ * @param entry
+ * The entry to be added.
+ * @throws NullPointerException
+ * If {@code entry} was {@code null} .
+ */
+ AddRequestImpl(Entry entry) throws NullPointerException
+ {
+ this.entry = entry;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <R, P> R accept(ChangeRecordVisitor<R, P> v, P p)
+ {
+ return v.visitChangeRecord(p, this);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean addAttribute(Attribute attribute)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ return entry.addAttribute(attribute);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean addAttribute(Attribute attribute,
+ Collection<ByteString> duplicateValues)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ return entry.addAttribute(attribute, duplicateValues);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public AddRequestImpl addAttribute(String attributeDescription,
+ Object... values) throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ entry.addAttribute(attributeDescription, values);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public AddRequestImpl clearAttributes()
+ throws UnsupportedOperationException
+ {
+ entry.clearAttributes();
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean containsAttribute(
+ AttributeDescription attributeDescription)
+ throws NullPointerException
+ {
+ return entry.containsAttribute(attributeDescription);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean containsAttribute(String attributeDescription)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ return entry.containsAttribute(attributeDescription);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean containsObjectClass(ObjectClass objectClass)
+ throws NullPointerException
+ {
+ return entry.containsObjectClass(objectClass);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean containsObjectClass(String objectClass)
+ throws NullPointerException
+ {
+ return entry.containsObjectClass(objectClass);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterable<Attribute> findAttributes(
+ AttributeDescription attributeDescription)
+ throws NullPointerException
+ {
+ return entry.findAttributes(attributeDescription);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterable<Attribute> findAttributes(String attributeDescription)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ return entry.findAttributes(attributeDescription);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Attribute getAttribute(
+ AttributeDescription attributeDescription)
+ throws NullPointerException
+ {
+ return entry.getAttribute(attributeDescription);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Attribute getAttribute(String attributeDescription)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ return entry.getAttribute(attributeDescription);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int getAttributeCount()
+ {
+ return entry.getAttributeCount();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterable<Attribute> getAttributes()
+ {
+ return entry.getAttributes();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public DN getName()
+ {
+ return entry.getName();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterable<String> getObjectClasses()
+ {
+ return entry.getObjectClasses();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean removeAttribute(Attribute attribute,
+ Collection<ByteString> missingValues)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ return entry.removeAttribute(attribute, missingValues);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean removeAttribute(
+ AttributeDescription attributeDescription)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ return entry.removeAttribute(attributeDescription);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public AddRequestImpl removeAttribute(String attributeDescription)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ entry.removeAttribute(attributeDescription);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public AddRequestImpl removeAttribute(String attributeDescription,
+ Object... values) throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ entry.removeAttribute(attributeDescription, values);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean replaceAttribute(Attribute attribute)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ return entry.replaceAttribute(attribute);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public AddRequestImpl replaceAttribute(String attributeDescription,
+ Object... values) throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ entry.replaceAttribute(attributeDescription, values);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public AddRequestImpl setName(DN dn)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ entry.setName(dn);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public AddRequestImpl setName(String dn)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ entry.setName(dn);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("AddRequest(name=");
+ builder.append(getName());
+ builder.append(", attributes=");
+ builder.append(getAttributes());
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+
+
+
+ AddRequest getThis()
+ {
+ return this;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/BindRequest.java b/sdk/src/org/opends/sdk/requests/BindRequest.java
new file mode 100644
index 0000000..62a466d
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/BindRequest.java
@@ -0,0 +1,137 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.DN;
+import org.opends.sdk.controls.Control;
+
+
+
+/**
+ * The Bind operation allows authentication information to be exchanged
+ * between the client and server. The Bind operation should be thought
+ * of as the "authenticate" operation.
+ */
+public interface BindRequest extends Request
+{
+ /**
+ * Adds the provided control to this request.
+ *
+ * @param control
+ * The control to be added to this request.
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be added.
+ * @throws NullPointerException
+ * If {@code control} was {@code null}.
+ */
+ BindRequest addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Removes all the controls included with this request.
+ *
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ */
+ BindRequest clearControls() throws UnsupportedOperationException;
+
+
+
+ /**
+ * Returns the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be returned.
+ * @return The control, or {@code null} if the control is not included
+ * with this request.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control getControl(String oid) throws NullPointerException;
+
+
+
+ /**
+ * Returns an {@code Iterable} containing the controls included with
+ * this request. The returned {@code Iterable} may be used to remove
+ * controls if permitted by this request.
+ *
+ * @return An {@code Iterable} containing the controls.
+ */
+ Iterable<Control> getControls();
+
+
+
+ /**
+ * Returns the distinguished name of the Directory object that the
+ * client wishes to bind as. The distinguished name may be empty (but
+ * never {@code null}) when used for of anonymous binds, or when using
+ * SASL authentication. The server shall not dereference any aliases
+ * in locating the named object.
+ *
+ * @return The distinguished name of the Directory object that the
+ * client wishes to bind as.
+ */
+ DN getName();
+
+
+
+ /**
+ * Indicates whether or not this request has any controls.
+ *
+ * @return {@code true} if this request has any controls, otherwise
+ * {@code false}.
+ */
+ boolean hasControls();
+
+
+
+ /**
+ * Removes the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be removed.
+ * @return The removed control, or {@code null} if the control is not
+ * included with this request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/CompareRequest.java b/sdk/src/org/opends/sdk/requests/CompareRequest.java
new file mode 100644
index 0000000..0172292
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/CompareRequest.java
@@ -0,0 +1,288 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.AttributeDescription;
+import org.opends.sdk.DN;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * The Compare operation allows a client to compare an assertion value
+ * with the values of a particular attribute in a particular entry in
+ * the Directory.
+ * <p>
+ * Note that some directory systems may establish access controls that
+ * permit the values of certain attributes (such as {@code userPassword}
+ * ) to be compared but not interrogated by other means.
+ */
+public interface CompareRequest extends Request
+{
+ /**
+ * Adds the provided control to this request.
+ *
+ * @param control
+ * The control to be added to this request.
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be added.
+ * @throws NullPointerException
+ * If {@code control} was {@code null}.
+ */
+ CompareRequest addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Removes all the controls included with this request.
+ *
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ */
+ CompareRequest clearControls() throws UnsupportedOperationException;
+
+
+
+ /**
+ * Returns the assertion value to be compared.
+ *
+ * @return The assertion value.
+ */
+ ByteString getAssertionValue();
+
+
+
+ /**
+ * Returns the assertion value to be compared decoded as a UTF-8
+ * string.
+ *
+ * @return The assertion value decoded as a UTF-8 string.
+ */
+ String getAssertionValueAsString();
+
+
+
+ /**
+ * Returns the name of the attribute to be compared.
+ *
+ * @return The name of the attribute.
+ */
+ AttributeDescription getAttributeDescription();
+
+
+
+ /**
+ * Returns the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be returned.
+ * @return The control, or {@code null} if the control is not included
+ * with this request.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control getControl(String oid) throws NullPointerException;
+
+
+
+ /**
+ * Returns an {@code Iterable} containing the controls included with
+ * this request. The returned {@code Iterable} may be used to remove
+ * controls if permitted by this request.
+ *
+ * @return An {@code Iterable} containing the controls.
+ */
+ Iterable<Control> getControls();
+
+
+
+ /**
+ * Returns the distinguished name of the entry to be compared. The
+ * server shall not dereference any aliases in locating the entry to
+ * be compared.
+ *
+ * @return The distinguished name of the entry.
+ */
+ DN getName();
+
+
+
+ /**
+ * Indicates whether or not this request has any controls.
+ *
+ * @return {@code true} if this request has any controls, otherwise
+ * {@code false}.
+ */
+ boolean hasControls();
+
+
+
+ /**
+ * Removes the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be removed.
+ * @return The removed control, or {@code null} if the control is not
+ * included with this request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the assertion value to be compared.
+ *
+ * @param value
+ * The assertion value to be compared.
+ * @return This compare request.
+ * @throws UnsupportedOperationException
+ * If this compare request does not permit the assertion
+ * value to be set.
+ * @throws NullPointerException
+ * If {@code value} was {@code null}.
+ */
+ CompareRequest setAssertionValue(ByteString value)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the assertion value to be compared.
+ * <p>
+ * If the assertion value is not an instance of {@code ByteString}
+ * then it will be converted using the
+ * {@link ByteString#valueOf(Object)} method.
+ *
+ * @param value
+ * The assertion value to be compared.
+ * @return This compare request.
+ * @throws UnsupportedOperationException
+ * If this compare request does not permit the assertion
+ * value to be set.
+ * @throws NullPointerException
+ * If {@code value} was {@code null}.
+ */
+ CompareRequest setAssertionValue(Object value)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the name of the attribute to be compared.
+ *
+ * @param attributeDescription
+ * The name of the attribute to be compared.
+ * @return This compare request.
+ * @throws UnsupportedOperationException
+ * If this compare request does not permit the attribute
+ * description to be set.
+ * @throws NullPointerException
+ * If {@code attributeDescription} was {@code null}.
+ */
+ CompareRequest setAttributeDescription(
+ AttributeDescription attributeDescription)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the name of the attribute to be compared.
+ *
+ * @param attributeDescription
+ * The name of the attribute to be compared.
+ * @return This compare request.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code attributeDescription} could not be decoded
+ * using the default schema.
+ * @throws UnsupportedOperationException
+ * If this compare request does not permit the attribute
+ * description to be set.
+ * @throws NullPointerException
+ * If {@code attributeDescription} was {@code null}.
+ */
+ CompareRequest setAttributeDescription(String attributeDescription)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the distinguished name of the entry to be compared. The server
+ * shall not dereference any aliases in locating the entry to be
+ * compared.
+ *
+ * @param dn
+ * The distinguished name of the entry to be compared.
+ * @return This compare request.
+ * @throws UnsupportedOperationException
+ * If this compare request does not permit the distinguished
+ * name to be set.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ CompareRequest setName(DN dn) throws UnsupportedOperationException,
+ NullPointerException;
+
+
+
+ /**
+ * Sets the distinguished name of the entry to be compared. The server
+ * shall not dereference any aliases in locating the entry to be
+ * compared.
+ *
+ * @param dn
+ * The distinguished name of the entry to be compared.
+ * @return This compare request.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code dn} could not be decoded using the default
+ * schema.
+ * @throws UnsupportedOperationException
+ * If this compare request does not permit the distinguished
+ * name to be set.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ CompareRequest setName(String dn)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/CompareRequestImpl.java b/sdk/src/org/opends/sdk/requests/CompareRequestImpl.java
new file mode 100644
index 0000000..1bd45ce
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/CompareRequestImpl.java
@@ -0,0 +1,229 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.AttributeDescription;
+import org.opends.sdk.DN;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Compare request implementation.
+ */
+final class CompareRequestImpl extends
+ AbstractRequestImpl<CompareRequest> implements CompareRequest
+{
+
+ private AttributeDescription attributeDescription;
+
+ private ByteString assertionValue;
+
+ private DN name;
+
+
+
+ /**
+ * Creates a new compare request using the provided distinguished
+ * name, attribute name, and assertion value.
+ *
+ * @param name
+ * The distinguished name of the entry to be compared.
+ * @param attributeDescription
+ * The name of the attribute to be compared.
+ * @param assertionValue
+ * The assertion value to be compared.
+ * @throws NullPointerException
+ * If {@code name}, {@code attributeDescription}, or {@code
+ * assertionValue} was {@code null}.
+ */
+ CompareRequestImpl(DN name,
+ AttributeDescription attributeDescription,
+ ByteString assertionValue) throws NullPointerException
+ {
+ this.name = name;
+ this.attributeDescription = attributeDescription;
+ this.assertionValue = assertionValue;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteString getAssertionValue()
+ {
+ return assertionValue;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getAssertionValueAsString()
+ {
+ return assertionValue.toString();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public AttributeDescription getAttributeDescription()
+ {
+ return attributeDescription;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public DN getName()
+ {
+ return name;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public CompareRequest setAssertionValue(ByteString value)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(value);
+ this.assertionValue = value;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public CompareRequest setAssertionValue(Object value)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(value);
+ this.assertionValue = ByteString.valueOf(value);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public CompareRequest setAttributeDescription(
+ AttributeDescription attributeDescription)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(attributeDescription);
+ this.attributeDescription = attributeDescription;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public CompareRequest setAttributeDescription(
+ String attributeDescription)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(attributeDescription);
+ this.attributeDescription = AttributeDescription
+ .valueOf(attributeDescription);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public CompareRequest setName(DN dn)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(dn);
+ this.name = dn;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public CompareRequest setName(String dn)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(dn);
+ this.name = DN.valueOf(dn);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("CompareRequest(name=");
+ builder.append(getName());
+ builder.append(", attributeDescription=");
+ builder.append(getAttributeDescription());
+ builder.append(", assertionValue=");
+ builder.append(getAssertionValueAsString());
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+
+
+
+ CompareRequest getThis()
+ {
+ return this;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/DeleteRequest.java b/sdk/src/org/opends/sdk/requests/DeleteRequest.java
new file mode 100644
index 0000000..0425c6f
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/DeleteRequest.java
@@ -0,0 +1,190 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.DN;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.ldif.ChangeRecord;
+import org.opends.sdk.ldif.ChangeRecordVisitor;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * The Delete operation allows a client to request the removal of an
+ * entry from the Directory.
+ * <p>
+ * Only leaf entries (those with no subordinate entries) can be deleted
+ * with this operation. However, addition of the {@code
+ * SubtreeDeleteControl} permits whole sub-trees to be deleted using a
+ * single Delete request.
+ */
+public interface DeleteRequest extends Request, ChangeRecord
+{
+ /**
+ * {@inheritDoc}
+ */
+ <R, P> R accept(ChangeRecordVisitor<R, P> v, P p);
+
+
+
+ /**
+ * Adds the provided control to this request.
+ *
+ * @param control
+ * The control to be added to this request.
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be added.
+ * @throws NullPointerException
+ * If {@code control} was {@code null}.
+ */
+ DeleteRequest addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Removes all the controls included with this request.
+ *
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ */
+ DeleteRequest clearControls() throws UnsupportedOperationException;
+
+
+
+ /**
+ * Returns the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be returned.
+ * @return The control, or {@code null} if the control is not included
+ * with this request.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control getControl(String oid) throws NullPointerException;
+
+
+
+ /**
+ * Returns an {@code Iterable} containing the controls included with
+ * this request. The returned {@code Iterable} may be used to remove
+ * controls if permitted by this request.
+ *
+ * @return An {@code Iterable} containing the controls.
+ */
+ Iterable<Control> getControls();
+
+
+
+ /**
+ * Returns the distinguished name of the entry to be deleted. The
+ * server shall not dereference any aliases in locating the entry to
+ * be deleted.
+ *
+ * @return The distinguished name of the entry.
+ */
+ DN getName();
+
+
+
+ /**
+ * Indicates whether or not this request has any controls.
+ *
+ * @return {@code true} if this request has any controls, otherwise
+ * {@code false}.
+ */
+ boolean hasControls();
+
+
+
+ /**
+ * Removes the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be removed.
+ * @return The removed control, or {@code null} if the control is not
+ * included with this request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the distinguished name of the entry to be deleted. The server
+ * shall not dereference any aliases in locating the entry to be
+ * deleted.
+ *
+ * @param dn
+ * The distinguished name of the entry to be deleted.
+ * @return This delete request.
+ * @throws UnsupportedOperationException
+ * If this delete request does not permit the distinguished
+ * name to be set.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ DeleteRequest setName(DN dn) throws UnsupportedOperationException,
+ NullPointerException;
+
+
+
+ /**
+ * Sets the distinguished name of the entry to be deleted. The server
+ * shall not dereference any aliases in locating the entry to be
+ * deleted.
+ *
+ * @param dn
+ * The distinguished name of the entry to be deleted.
+ * @return This delete request.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code dn} could not be decoded using the default
+ * schema.
+ * @throws UnsupportedOperationException
+ * If this delete request does not permit the distinguished
+ * name to be set.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ DeleteRequest setName(String dn)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/DeleteRequestImpl.java b/sdk/src/org/opends/sdk/requests/DeleteRequestImpl.java
new file mode 100644
index 0000000..b9e4646
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/DeleteRequestImpl.java
@@ -0,0 +1,133 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.DN;
+import org.opends.sdk.ldif.ChangeRecordVisitor;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Delete request implementation.
+ */
+final class DeleteRequestImpl extends
+ AbstractRequestImpl<DeleteRequest> implements DeleteRequest
+{
+ private DN name;
+
+
+
+ /**
+ * Creates a new delete request using the provided distinguished name.
+ *
+ * @param name
+ * The distinguished name of the entry to be deleted.
+ * @throws NullPointerException
+ * If {@code name} was {@code null}.
+ */
+ DeleteRequestImpl(DN name) throws NullPointerException
+ {
+ this.name = name;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <R, P> R accept(ChangeRecordVisitor<R, P> v, P p)
+ {
+ return v.visitChangeRecord(p, this);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public DN getName()
+ {
+ return name;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public DeleteRequest setName(DN dn)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(dn);
+ this.name = dn;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public DeleteRequest setName(String dn)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(dn);
+ this.name = DN.valueOf(dn);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("DeleteRequest(name=");
+ builder.append(getName());
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+
+
+
+ DeleteRequest getThis()
+ {
+ return this;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/ExtendedRequest.java b/sdk/src/org/opends/sdk/requests/ExtendedRequest.java
new file mode 100644
index 0000000..26cd503
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/ExtendedRequest.java
@@ -0,0 +1,165 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.extensions.ExtendedOperation;
+import org.opends.sdk.extensions.StartTLSRequest;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * The Extended operation allows additional operations to be defined for
+ * services not already available in the protocol; for example, to
+ * implement an operation which installs transport layer security (see
+ * {@link StartTLSRequest}).
+ *
+ * @param <S>
+ * The type of result.
+ */
+public interface ExtendedRequest<S extends Result> extends Request
+{
+ /**
+ * Adds the provided control to this request.
+ *
+ * @param control
+ * The control to be added to this request.
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be added.
+ * @throws NullPointerException
+ * If {@code control} was {@code null}.
+ */
+ ExtendedRequest<S> addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Removes all the controls included with this request.
+ *
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ */
+ ExtendedRequest<S> clearControls()
+ throws UnsupportedOperationException;
+
+
+
+ /**
+ * Returns the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be returned.
+ * @return The control, or {@code null} if the control is not included
+ * with this request.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control getControl(String oid) throws NullPointerException;
+
+
+
+ /**
+ * Returns an {@code Iterable} containing the controls included with
+ * this request. The returned {@code Iterable} may be used to remove
+ * controls if permitted by this request.
+ *
+ * @return An {@code Iterable} containing the controls.
+ */
+ Iterable<Control> getControls();
+
+
+
+ /**
+ * Returns the extended operation associated with this extended
+ * request.
+ * <p>
+ * FIXME: this should not be exposed in the public API.
+ *
+ * @return The extended operation associated with this extended
+ * request.
+ */
+ ExtendedOperation<?, S> getExtendedOperation();
+
+
+
+ /**
+ * Returns the dotted-decimal representation of the unique OID
+ * corresponding to this extended request.
+ *
+ * @return The dotted-decimal representation of the unique OID.
+ */
+ String getRequestName();
+
+
+
+ /**
+ * Returns the content of this extended request in a form defined by
+ * the extended request.
+ *
+ * @return The content of this extended request, or {@code null} if
+ * there is no content.
+ */
+ ByteString getRequestValue();
+
+
+
+ /**
+ * Indicates whether or not this request has any controls.
+ *
+ * @return {@code true} if this request has any controls, otherwise
+ * {@code false}.
+ */
+ boolean hasControls();
+
+
+
+ /**
+ * Removes the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be removed.
+ * @return The removed control, or {@code null} if the control is not
+ * included with this request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/GenericBindRequest.java b/sdk/src/org/opends/sdk/requests/GenericBindRequest.java
new file mode 100644
index 0000000..c8e1ab5
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/GenericBindRequest.java
@@ -0,0 +1,243 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.DN;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * A generic Bind request which should be used for unsupported
+ * authentication methods. Servers that do not support a choice supplied
+ * by a client return a Bind response with the result code set to
+ * {@link ResultCode#AUTH_METHOD_NOT_SUPPORTED}.
+ */
+public interface GenericBindRequest extends BindRequest
+{
+ /**
+ * Adds the provided control to this request.
+ *
+ * @param control
+ * The control to be added to this request.
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be added.
+ * @throws NullPointerException
+ * If {@code control} was {@code null}.
+ */
+ GenericBindRequest addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Removes all the controls included with this request.
+ *
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ */
+ GenericBindRequest clearControls()
+ throws UnsupportedOperationException;
+
+
+
+ /**
+ * Returns the authentication mechanism identifier for this generic
+ * bind request. Note that value {@code 0} is reserved for simple
+ * authentication, {@code 1} and {@code 2} are reserved but unused,
+ * and {@code 3} is reserved for SASL authentication.
+ *
+ * @return The authentication mechanism identifier.
+ */
+ byte getAuthenticationType();
+
+
+
+ /**
+ * Returns the authentication information for this generic bind
+ * request in a form defined by the authentication mechanism.
+ *
+ * @return The authentication information.
+ */
+ ByteString getAuthenticationValue();
+
+
+
+ /**
+ * Returns the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be returned.
+ * @return The control, or {@code null} if the control is not included
+ * with this request.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control getControl(String oid) throws NullPointerException;
+
+
+
+ /**
+ * Returns an {@code Iterable} containing the controls included with
+ * this request. The returned {@code Iterable} may be used to remove
+ * controls if permitted by this request.
+ *
+ * @return An {@code Iterable} containing the controls.
+ */
+ Iterable<Control> getControls();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ DN getName();
+
+
+
+ /**
+ * Indicates whether or not this request has any controls.
+ *
+ * @return {@code true} if this request has any controls, otherwise
+ * {@code false}.
+ */
+ boolean hasControls();
+
+
+
+ /**
+ * Removes the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be removed.
+ * @return The removed control, or {@code null} if the control is not
+ * included with this request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the authentication mechanism identifier for this generic bind
+ * request. Note that value {@code 0} is reserved for simple
+ * authentication, {@code 1} and {@code 2} are reserved but unused,
+ * and {@code 3} is reserved for SASL authentication.
+ *
+ * @param type
+ * The authentication mechanism identifier for this generic
+ * bind request.
+ * @return This generic bind request.
+ * @throws UnsupportedOperationException
+ * If this generic bind request does not permit the
+ * authentication type to be set.
+ */
+ GenericBindRequest setAuthenticationType(byte type)
+ throws UnsupportedOperationException;
+
+
+
+ /**
+ * Sets the authentication information for this generic bind request
+ * in a form defined by the authentication mechanism.
+ *
+ * @param bytes
+ * The authentication information for this generic bind
+ * request in a form defined by the authentication mechanism.
+ * @return This generic bind request.
+ * @throws UnsupportedOperationException
+ * If this generic bind request does not permit the
+ * authentication bytes to be set.
+ * @throws NullPointerException
+ * If {@code bytes} was {@code null}.
+ */
+ GenericBindRequest setAuthenticationValue(ByteString bytes)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the distinguished name of the Directory object that the client
+ * wishes to bind as. The distinguished name may be empty (but never
+ * {@code null} when used for of anonymous binds, or when using SASL
+ * authentication. The server shall not dereference any aliases in
+ * locating the named object.
+ *
+ * @param dn
+ * The distinguished name of the Directory object that the
+ * client wishes to bind as.
+ * @return This bind request.
+ * @throws UnsupportedOperationException
+ * If this bind request does not permit the distinguished
+ * name to be set.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ GenericBindRequest setName(DN dn)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the distinguished name of the Directory object that the client
+ * wishes to bind as. The distinguished name may be empty (but never
+ * {@code null} when used for of anonymous binds, or when using SASL
+ * authentication. The server shall not dereference any aliases in
+ * locating the named object.
+ *
+ * @param dn
+ * The distinguished name of the Directory object that the
+ * client wishes to bind as.
+ * @return This bind request.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code dn} could not be decoded using the default
+ * schema.
+ * @throws UnsupportedOperationException
+ * If this bind request does not permit the distinguished
+ * name to be set.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ GenericBindRequest setName(String dn)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/GenericBindRequestImpl.java b/sdk/src/org/opends/sdk/requests/GenericBindRequestImpl.java
new file mode 100644
index 0000000..dbaa10e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/GenericBindRequestImpl.java
@@ -0,0 +1,182 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.DN;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Generic bind request implementation.
+ */
+final class GenericBindRequestImpl extends
+ AbstractBindRequest<GenericBindRequest> implements
+ GenericBindRequest
+{
+
+ private DN name;
+
+ private ByteString authenticationValue;
+
+ private byte authenticationType;
+
+
+
+ /**
+ * Creates a new generic bind request using the provided distinguished
+ * name, authentication type, and authentication information.
+ *
+ * @param name
+ * The distinguished name of the Directory object that the
+ * client wishes to bind as (may be empty).
+ * @param authenticationType
+ * The authentication mechanism identifier for this generic
+ * bind request.
+ * @param authenticationValue
+ * The authentication information for this generic bind
+ * request in a form defined by the authentication mechanism.
+ * @throws NullPointerException
+ * If {@code name}, {@code authenticationType}, or {@code
+ * authenticationValue} was {@code null}.
+ */
+ GenericBindRequestImpl(DN name, byte authenticationType,
+ ByteString authenticationValue) throws NullPointerException
+ {
+ this.name = name;
+ this.authenticationType = authenticationType;
+ this.authenticationValue = authenticationValue;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public byte getAuthenticationType()
+ {
+ return authenticationType;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteString getAuthenticationValue()
+ {
+ return authenticationValue;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public DN getName()
+ {
+ return name;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public GenericBindRequest setAuthenticationType(byte type)
+ throws UnsupportedOperationException
+ {
+ this.authenticationType = type;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public GenericBindRequest setAuthenticationValue(ByteString bytes)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(bytes);
+ this.authenticationValue = bytes;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public GenericBindRequest setName(DN dn)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(dn);
+ this.name = dn;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public GenericBindRequest setName(String dn)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(dn);
+ this.name = DN.valueOf(dn);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("GenericBindRequest(name=");
+ builder.append(getName());
+ builder.append(", authenticationType=");
+ builder.append(getAuthenticationType());
+ builder.append(", authenticationValue=");
+ builder.append(getAuthenticationValue());
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/requests/GenericExtendedRequest.java b/sdk/src/org/opends/sdk/requests/GenericExtendedRequest.java
new file mode 100644
index 0000000..ff027f8
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/GenericExtendedRequest.java
@@ -0,0 +1,188 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.extensions.ExtendedOperation;
+import org.opends.sdk.responses.GenericExtendedResult;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * A generic Extended request which should be used for unsupported
+ * extended operations. Servers list the names of Extended requests they
+ * recognize in the {@code supportedExtension} attribute in the root
+ * DSE. Where the name is not recognized, the server returns
+ * {@link ResultCode#PROTOCOL_ERROR} (the server may return this error
+ * in other cases).
+ */
+public interface GenericExtendedRequest extends
+ ExtendedRequest<GenericExtendedResult>
+{
+ /**
+ * Adds the provided control to this request.
+ *
+ * @param control
+ * The control to be added to this request.
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be added.
+ * @throws NullPointerException
+ * If {@code control} was {@code null}.
+ */
+ GenericExtendedRequest addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Removes all the controls included with this request.
+ *
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ */
+ GenericExtendedRequest clearControls()
+ throws UnsupportedOperationException;
+
+
+
+ /**
+ * Returns the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be returned.
+ * @return The control, or {@code null} if the control is not included
+ * with this request.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control getControl(String oid) throws NullPointerException;
+
+
+
+ /**
+ * Returns an {@code Iterable} containing the controls included with
+ * this request. The returned {@code Iterable} may be used to remove
+ * controls if permitted by this request.
+ *
+ * @return An {@code Iterable} containing the controls.
+ */
+ Iterable<Control> getControls();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ ExtendedOperation<GenericExtendedRequest, GenericExtendedResult> getExtendedOperation();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ String getRequestName();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ ByteString getRequestValue();
+
+
+
+ /**
+ * Indicates whether or not this request has any controls.
+ *
+ * @return {@code true} if this request has any controls, otherwise
+ * {@code false}.
+ */
+ boolean hasControls();
+
+
+
+ /**
+ * Removes the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be removed.
+ * @return The removed control, or {@code null} if the control is not
+ * included with this request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the dotted-decimal representation of the unique OID
+ * corresponding to this generic extended request.
+ *
+ * @param oid
+ * The dotted-decimal representation of the unique OID.
+ * @return This generic extended request.
+ * @throws UnsupportedOperationException
+ * If this generic extended request does not permit the
+ * request name to be set.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ GenericExtendedRequest setRequestName(String oid)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the content of this generic extended request in a form defined
+ * by the extended request.
+ *
+ * @param bytes
+ * The content of this generic extended request in a form
+ * defined by the extended request, or {@code null} if there
+ * is no content.
+ * @return This generic extended request.
+ * @throws UnsupportedOperationException
+ * If this generic extended request does not permit the
+ * request value to be set.
+ */
+ GenericExtendedRequest setRequestValue(ByteString bytes)
+ throws UnsupportedOperationException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/GenericExtendedRequestImpl.java b/sdk/src/org/opends/sdk/requests/GenericExtendedRequestImpl.java
new file mode 100644
index 0000000..b7e396e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/GenericExtendedRequestImpl.java
@@ -0,0 +1,192 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.extensions.ExtendedOperation;
+import org.opends.sdk.responses.GenericExtendedResult;
+import org.opends.sdk.responses.Responses;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Generic extended request implementation.
+ */
+final class GenericExtendedRequestImpl
+ extends
+ AbstractExtendedRequest<GenericExtendedRequest, GenericExtendedResult>
+ implements GenericExtendedRequest
+{
+ /**
+ * Generic extended operation singleton.
+ */
+ private static final class Operation implements
+ ExtendedOperation<GenericExtendedRequest, GenericExtendedResult>
+ {
+
+ public GenericExtendedRequest decodeRequest(String requestName,
+ ByteString requestValue) throws DecodeException
+ {
+ return Requests.newGenericExtendedRequest(requestName,
+ requestValue);
+ }
+
+
+
+ public GenericExtendedResult decodeResponse(ResultCode resultCode,
+ String matchedDN, String diagnosticMessage)
+ {
+ return Responses.newGenericExtendedResult(resultCode)
+ .setMatchedDN(matchedDN).setDiagnosticMessage(
+ diagnosticMessage);
+ }
+
+
+
+ public GenericExtendedResult decodeResponse(ResultCode resultCode,
+ String matchedDN, String diagnosticMessage,
+ String responseName, ByteString responseValue)
+ throws DecodeException
+ {
+ return Responses.newGenericExtendedResult(resultCode)
+ .setMatchedDN(matchedDN).setDiagnosticMessage(
+ diagnosticMessage).setResponseName(responseName)
+ .setResponseValue(responseValue);
+ }
+ }
+
+
+
+ private static final Operation OPERATION = new Operation();
+
+ private ByteString requestValue = ByteString.empty();
+
+ private String requestName;
+
+
+
+ /**
+ * Creates a new generic extended request using the provided name and
+ * optional value.
+ *
+ * @param requestName
+ * The dotted-decimal representation of the unique OID
+ * corresponding to this extended request.
+ * @param requestValue
+ * The content of this generic extended request in a form
+ * defined by the extended operation, or {@code null} if
+ * there is no content.
+ * @throws NullPointerException
+ * If {@code requestName} was {@code null}.
+ */
+ GenericExtendedRequestImpl(String requestName, ByteString requestValue)
+ throws NullPointerException
+ {
+ this.requestName = requestName;
+ this.requestValue = requestValue;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ExtendedOperation<GenericExtendedRequest, GenericExtendedResult> getExtendedOperation()
+ {
+ return OPERATION;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getRequestName()
+ {
+ return requestName;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteString getRequestValue()
+ {
+ return requestValue;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public GenericExtendedRequest setRequestName(String oid)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(oid);
+ this.requestName = oid;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public GenericExtendedRequest setRequestValue(ByteString bytes)
+ throws UnsupportedOperationException
+ {
+ this.requestValue = bytes;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("GenericExtendedRequest(requestName=");
+ builder.append(getRequestName());
+ builder.append(", requestValue=");
+ builder.append(getRequestValue());
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/requests/ModifyDNRequest.java b/sdk/src/org/opends/sdk/requests/ModifyDNRequest.java
new file mode 100644
index 0000000..9afbe3f
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/ModifyDNRequest.java
@@ -0,0 +1,334 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.DN;
+import org.opends.sdk.RDN;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.ldif.ChangeRecord;
+import org.opends.sdk.ldif.ChangeRecordVisitor;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * The Modify DN operation allows a client to change the Relative
+ * Distinguished Name (RDN) of an entry in the Directory and/or to move
+ * a subtree of entries to a new location in the Directory.
+ */
+public interface ModifyDNRequest extends Request, ChangeRecord
+{
+ /**
+ * {@inheritDoc}
+ */
+ <R, P> R accept(ChangeRecordVisitor<R, P> v, P p);
+
+
+
+ /**
+ * Adds the provided control to this request.
+ *
+ * @param control
+ * The control to be added to this request.
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be added.
+ * @throws NullPointerException
+ * If {@code control} was {@code null}.
+ */
+ ModifyDNRequest addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Removes all the controls included with this request.
+ *
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ */
+ ModifyDNRequest clearControls() throws UnsupportedOperationException;
+
+
+
+ /**
+ * Returns the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be returned.
+ * @return The control, or {@code null} if the control is not included
+ * with this request.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control getControl(String oid) throws NullPointerException;
+
+
+
+ /**
+ * Returns an {@code Iterable} containing the controls included with
+ * this request. The returned {@code Iterable} may be used to remove
+ * controls if permitted by this request.
+ *
+ * @return An {@code Iterable} containing the controls.
+ */
+ Iterable<Control> getControls();
+
+
+
+ /**
+ * Returns the distinguished name of the entry to be renamed. This
+ * entry may or may not have subordinate entries. The server shall not
+ * dereference any aliases in locating the entry to be renamed.
+ *
+ * @return The distinguished name of the entry.
+ */
+ DN getName();
+
+
+
+ /**
+ * Returns the new RDN of the entry to be renamed. The value of the
+ * old RDN is supplied when moving the entry to a new superior without
+ * changing its RDN. Attribute values of the new RDN not matching any
+ * attribute value of the entry are added to the entry, and an
+ * appropriate error is returned if this fails.
+ *
+ * @return The new RDN of the entry.
+ */
+ RDN getNewRDN();
+
+
+
+ /**
+ * Returns the distinguished name of an existing entry that will
+ * become the immediate superior (parent) of the entry to be renamed.
+ * The server shall not dereference any aliases in locating the new
+ * superior entry.
+ *
+ * @return The distinguished name of the new superior entry, or
+ * {@code null} if the entry is to remain under the same
+ * parent entry.
+ */
+ DN getNewSuperior();
+
+
+
+ /**
+ * Indicates whether or not this request has any controls.
+ *
+ * @return {@code true} if this request has any controls, otherwise
+ * {@code false}.
+ */
+ boolean hasControls();
+
+
+
+ /**
+ * Indicates whether the old RDN attribute values are to be retained
+ * as attributes of the entry or deleted from the entry.
+ *
+ * @return {@code true} if the old RDN attribute values are to be
+ * deleted from the entry, or {@code false} if they are to be
+ * retained.
+ */
+ boolean isDeleteOldRDN();
+
+
+
+ /**
+ * Removes the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be removed.
+ * @return The removed control, or {@code null} if the control is not
+ * included with this request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Specifies whether the old RDN attribute values are to be retained
+ * as attributes of the entry or deleted from the entry.
+ *
+ * @param deleteOldRDN
+ * {@code true} if the old RDN attribute values are to be
+ * deleted from the entry, or {@code false} if they are to be
+ * retained.
+ * @return This modify DN request.
+ * @throws UnsupportedOperationException
+ * If this modify DN request does not permit the delete old
+ * RDN parameter to be set.
+ */
+ ModifyDNRequest setDeleteOldRDN(boolean deleteOldRDN)
+ throws UnsupportedOperationException;
+
+
+
+ /**
+ * Sets the distinguished name of the entry to be renamed. This entry
+ * may or may not have subordinate entries. The server shall not
+ * dereference any aliases in locating the entry to be renamed.
+ *
+ * @param dn
+ * The distinguished name of the entry to be renamed.
+ * @return This modify DN request.
+ * @throws UnsupportedOperationException
+ * If this modify DN request does not permit the
+ * distinguished name to be set.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ ModifyDNRequest setName(DN dn) throws UnsupportedOperationException,
+ NullPointerException;
+
+
+
+ /**
+ * Sets the distinguished name of the entry to be renamed. This entry
+ * may or may not have subordinate entries. The server shall not
+ * dereference any aliases in locating the entry to be renamed.
+ *
+ * @param dn
+ * The distinguished name of the entry to be renamed.
+ * @return This modify DN request.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code dn} could not be decoded using the default
+ * schema.
+ * @throws UnsupportedOperationException
+ * If this modify DN request does not permit the
+ * distinguished name to be set.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ ModifyDNRequest setName(String dn)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the new RDN of the entry to be renamed. The value of the old
+ * RDN is supplied when moving the entry to a new superior without
+ * changing its RDN. Attribute values of the new RDN not matching any
+ * attribute value of the entry are added to the entry, and an
+ * appropriate error is returned if this fails.
+ *
+ * @param rdn
+ * The new RDN of the entry to be renamed.
+ * @return This modify DN request.
+ * @throws UnsupportedOperationException
+ * If this modify DN request does not permit the new RDN to
+ * be set.
+ * @throws NullPointerException
+ * If {@code rdn} was {@code null}.
+ */
+ ModifyDNRequest setNewRDN(RDN rdn)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the new RDN of the entry to be renamed. The value of the old
+ * RDN is supplied when moving the entry to a new superior without
+ * changing its RDN. Attribute values of the new RDN not matching any
+ * attribute value of the entry are added to the entry, and an
+ * appropriate error is returned if this fails.
+ *
+ * @param rdn
+ * The new RDN of the entry to be renamed.
+ * @return This modify DN request.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code rdn} could not be decoded using the default
+ * schema.
+ * @throws UnsupportedOperationException
+ * If this modify DN request does not permit the new RDN to
+ * be set.
+ * @throws NullPointerException
+ * If {@code rdn} was {@code null}.
+ */
+ ModifyDNRequest setNewRDN(String rdn)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the distinguished name of an existing entry that will become
+ * the immediate superior (parent) of the entry to be renamed. The
+ * server shall not dereference any aliases in locating the new
+ * superior entry.
+ *
+ * @param dn
+ * The distinguished name of an existing entry that will
+ * become the immediate superior (parent) of the entry to be
+ * renamed, may be {@code null}.
+ * @return This modify DN request.
+ * @throws UnsupportedOperationException
+ * If this modify DN request does not permit the new
+ * superior to be set.
+ */
+ ModifyDNRequest setNewSuperior(DN dn)
+ throws UnsupportedOperationException;
+
+
+
+ /**
+ * Sets the distinguished name of an existing entry that will become
+ * the immediate superior (parent) of the entry to be renamed. The
+ * server shall not dereference any aliases in locating the new
+ * superior entry.
+ *
+ * @param dn
+ * The distinguished name of an existing entry that will
+ * become the immediate superior (parent) of the entry to be
+ * renamed, may be {@code null}.
+ * @return This modify DN request.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code dn} could not be decoded using the default
+ * schema.
+ * @throws UnsupportedOperationException
+ * If this modify DN request does not permit the new
+ * superior to be set.
+ */
+ ModifyDNRequest setNewSuperior(String dn)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/ModifyDNRequestImpl.java b/sdk/src/org/opends/sdk/requests/ModifyDNRequestImpl.java
new file mode 100644
index 0000000..ae7a5be
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/ModifyDNRequestImpl.java
@@ -0,0 +1,244 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.DN;
+import org.opends.sdk.RDN;
+import org.opends.sdk.ldif.ChangeRecordVisitor;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Modify DN request implementation.
+ */
+final class ModifyDNRequestImpl extends
+ AbstractRequestImpl<ModifyDNRequest> implements ModifyDNRequest
+{
+ private DN name;
+
+ private DN newSuperior = null;
+
+ private RDN newRDN;
+
+ private boolean deleteOldRDN = false;
+
+
+
+ /**
+ * Creates a new modify DN request using the provided distinguished
+ * name and new RDN.
+ *
+ * @param name
+ * The distinguished name of the entry to be renamed.
+ * @param newRDN
+ * The new RDN of the entry.
+ * @throws NullPointerException
+ * If {@code name} or {@code newRDN} was {@code null}.
+ */
+ ModifyDNRequestImpl(DN name, RDN newRDN) throws NullPointerException
+ {
+ this.name = name;
+ this.newRDN = newRDN;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <R, P> R accept(ChangeRecordVisitor<R, P> v, P p)
+ {
+ return v.visitChangeRecord(p, this);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public DN getName()
+ {
+ return name;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public RDN getNewRDN()
+ {
+ return newRDN;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public DN getNewSuperior()
+ {
+ return newSuperior;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isDeleteOldRDN()
+ {
+ return deleteOldRDN;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ModifyDNRequestImpl setDeleteOldRDN(boolean deleteOldRDN)
+ throws UnsupportedOperationException
+ {
+ this.deleteOldRDN = deleteOldRDN;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ModifyDNRequest setName(DN dn)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(dn);
+ this.name = dn;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ModifyDNRequest setName(String dn)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(dn);
+ this.name = DN.valueOf(dn);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ModifyDNRequest setNewRDN(RDN rdn)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(rdn);
+ this.newRDN = rdn;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ModifyDNRequest setNewRDN(String rdn)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(rdn);
+ this.newRDN = RDN.valueOf(rdn);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ModifyDNRequest setNewSuperior(DN dn)
+ throws UnsupportedOperationException
+ {
+ this.newSuperior = dn;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ModifyDNRequest setNewSuperior(String dn)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException
+ {
+ this.newSuperior = (dn != null) ? DN.valueOf(dn) : null;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("ModifyDNRequest(name=");
+ builder.append(getName());
+ builder.append(", newRDN=");
+ builder.append(getNewRDN());
+ builder.append(", deleteOldRDN=");
+ builder.append(isDeleteOldRDN());
+ builder.append(", newSuperior=");
+ builder.append(String.valueOf(getNewSuperior()));
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+
+
+
+ ModifyDNRequest getThis()
+ {
+ return this;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/ModifyRequest.java b/sdk/src/org/opends/sdk/requests/ModifyRequest.java
new file mode 100644
index 0000000..3aa9fa9
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/ModifyRequest.java
@@ -0,0 +1,280 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.Change;
+import org.opends.sdk.DN;
+import org.opends.sdk.ModificationType;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.ldif.ChangeRecord;
+import org.opends.sdk.ldif.ChangeRecordVisitor;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * The Modify operation allows a client to request that a modification
+ * of an entry be performed on its behalf by a server.
+ */
+public interface ModifyRequest extends Request, ChangeRecord
+{
+ /**
+ * {@inheritDoc}
+ */
+ <R, P> R accept(ChangeRecordVisitor<R, P> v, P p);
+
+
+
+ /**
+ * Appends the provided change to the list of changes included with
+ * this modify request.
+ *
+ * @param change
+ * The change to be performed.
+ * @return This modify request.
+ * @throws UnsupportedOperationException
+ * If this modify request does not permit changes to be
+ * added.
+ * @throws NullPointerException
+ * If {@code change} was {@code null}.
+ */
+ ModifyRequest addChange(Change change)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Appends the provided change to the list of changes included with
+ * this modify request.
+ * <p>
+ * If the attribute value is not an instance of {@code ByteString}
+ * then it will be converted using the
+ * {@link ByteString#valueOf(Object)} method.
+ *
+ * @param type
+ * The type of change to be performed.
+ * @param attributeDescription
+ * The name of the attribute to be modified.
+ * @param values
+ * The attribute values to be modified.
+ * @return This modify request.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code attributeDescription} could not be decoded
+ * using the default schema.
+ * @throws UnsupportedOperationException
+ * If this modify request does not permit changes to be
+ * added.
+ * @throws NullPointerException
+ * If {@code type}, {@code attributeDescription}, or {@code
+ * value} was {@code null}.
+ */
+ ModifyRequest addChange(ModificationType type,
+ String attributeDescription, Object... values)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Adds the provided control to this request.
+ *
+ * @param control
+ * The control to be added to this request.
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be added.
+ * @throws NullPointerException
+ * If {@code control} was {@code null}.
+ */
+ ModifyRequest addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Removes all the changes included with this modify request.
+ *
+ * @return This modify request.
+ * @throws UnsupportedOperationException
+ * If this modify request does not permit changes to be
+ * removed.
+ */
+ ModifyRequest clearChanges() throws UnsupportedOperationException;
+
+
+
+ /**
+ * Removes all the controls included with this request.
+ *
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ */
+ ModifyRequest clearControls() throws UnsupportedOperationException;
+
+
+
+ /**
+ * Returns the number of changes included with this modify request.
+ *
+ * @return The number of changes.
+ */
+ int getChangeCount();
+
+
+
+ /**
+ * Returns an {@code Iterable} containing the changes included with
+ * this modify request. The returned {@code Iterable} may be used to
+ * remove changes if permitted by this modify request.
+ *
+ * @return An {@code Iterable} containing the changes.
+ */
+ Iterable<Change> getChanges();
+
+
+
+ /**
+ * Returns the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be returned.
+ * @return The control, or {@code null} if the control is not included
+ * with this request.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control getControl(String oid) throws NullPointerException;
+
+
+
+ /**
+ * Returns an {@code Iterable} containing the controls included with
+ * this request. The returned {@code Iterable} may be used to remove
+ * controls if permitted by this request.
+ *
+ * @return An {@code Iterable} containing the controls.
+ */
+ Iterable<Control> getControls();
+
+
+
+ /**
+ * Returns the distinguished name of the entry to be modified. The
+ * server shall not perform any alias dereferencing in determining the
+ * object to be modified.
+ *
+ * @return The distinguished name of the entry to be modified.
+ */
+ DN getName();
+
+
+
+ /**
+ * Indicates whether or not this modify request has any changes.
+ *
+ * @return {@code true} if this modify request has any changes,
+ * otherwise {@code false}.
+ */
+ boolean hasChanges();
+
+
+
+ /**
+ * Indicates whether or not this request has any controls.
+ *
+ * @return {@code true} if this request has any controls, otherwise
+ * {@code false}.
+ */
+ boolean hasControls();
+
+
+
+ /**
+ * Removes the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be removed.
+ * @return The removed control, or {@code null} if the control is not
+ * included with this request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the distinguished name of the entry to be modified. The server
+ * shall not perform any alias dereferencing in determining the object
+ * to be modified.
+ *
+ * @param dn
+ * The the distinguished name of the entry to be modified.
+ * @return This modify request.
+ * @throws UnsupportedOperationException
+ * If this modify request does not permit the distinguished
+ * name to be set.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ ModifyRequest setName(DN dn) throws UnsupportedOperationException,
+ NullPointerException;
+
+
+
+ /**
+ * Sets the distinguished name of the entry to be modified. The server
+ * shall not perform any alias dereferencing in determining the object
+ * to be modified.
+ *
+ * @param dn
+ * The the distinguished name of the entry to be modified.
+ * @return This modify request.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code dn} could not be decoded using the default
+ * schema.
+ * @throws UnsupportedOperationException
+ * If this modify request does not permit the distinguished
+ * name to be set.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ ModifyRequest setName(String dn)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/ModifyRequestImpl.java b/sdk/src/org/opends/sdk/requests/ModifyRequestImpl.java
new file mode 100644
index 0000000..0f7f6be
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/ModifyRequestImpl.java
@@ -0,0 +1,226 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opends.sdk.LinkedAttribute;
+import org.opends.sdk.Change;
+import org.opends.sdk.DN;
+import org.opends.sdk.ModificationType;
+import org.opends.sdk.ldif.ChangeRecordVisitor;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Modify request implementation.
+ */
+final class ModifyRequestImpl extends
+ AbstractRequestImpl<ModifyRequest> implements ModifyRequest
+{
+ private final List<Change> changes = new LinkedList<Change>();
+
+ private DN name;
+
+
+
+ /**
+ * Creates a new modify request using the provided distinguished name.
+ *
+ * @param name
+ * The distinguished name of the entry to be modified.
+ * @throws NullPointerException
+ * If {@code name} was {@code null}.
+ */
+ ModifyRequestImpl(DN name) throws NullPointerException
+ {
+ this.name = name;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <R, P> R accept(ChangeRecordVisitor<R, P> v, P p)
+ {
+ return v.visitChangeRecord(p, this);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ModifyRequest addChange(Change change)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(change);
+ changes.add(change);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ModifyRequest addChange(ModificationType type,
+ String attributeDescription, Object... values)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(type, attributeDescription, values);
+ changes.add(new Change(type, new LinkedAttribute(
+ attributeDescription, values)));
+ return this;
+ }
+
+
+
+ public ModifyRequest addChange(ModificationType type,
+ String attributeDescription, Object firstValue,
+ Object... remainingValues)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ModifyRequest clearChanges()
+ throws UnsupportedOperationException
+ {
+ changes.clear();
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int getChangeCount()
+ {
+ return changes.size();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterable<Change> getChanges()
+ {
+ return changes;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public DN getName()
+ {
+ return name;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean hasChanges()
+ {
+ return !changes.isEmpty();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ModifyRequest setName(DN dn)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(dn);
+ this.name = dn;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ModifyRequest setName(String dn)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(dn);
+ this.name = DN.valueOf(dn);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("ModifyRequest(dn=");
+ builder.append(getName());
+ builder.append(", changes=");
+ builder.append(getChanges());
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+
+
+
+ ModifyRequest getThis()
+ {
+ return this;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/Request.java b/sdk/src/org/opends/sdk/requests/Request.java
new file mode 100644
index 0000000..ed7c60a
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/Request.java
@@ -0,0 +1,124 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.controls.Control;
+
+
+
+/**
+ * The base class of all Requests provides methods for querying and
+ * manipulating the set of Controls included with a Request.
+ * <p>
+ * TODO: added complete description including sub-types.
+ */
+public interface Request
+{
+
+ /**
+ * Adds the provided control to this request.
+ *
+ * @param control
+ * The control to be added to this request.
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be added.
+ * @throws NullPointerException
+ * If {@code control} was {@code null}.
+ */
+ Request addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Removes all the controls included with this request.
+ *
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ */
+ Request clearControls() throws UnsupportedOperationException;
+
+
+
+ /**
+ * Returns the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be returned.
+ * @return The control, or {@code null} if the control is not included
+ * with this request.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control getControl(String oid) throws NullPointerException;
+
+
+
+ /**
+ * Returns an {@code Iterable} containing the controls included with
+ * this request. The returned {@code Iterable} may be used to remove
+ * controls if permitted by this request.
+ *
+ * @return An {@code Iterable} containing the controls.
+ */
+ Iterable<Control> getControls();
+
+
+
+ /**
+ * Indicates whether or not this request has any controls.
+ *
+ * @return {@code true} if this request has any controls, otherwise
+ * {@code false}.
+ */
+ boolean hasControls();
+
+
+
+ /**
+ * Removes the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be removed.
+ * @return The removed control, or {@code null} if the control is not
+ * included with this request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/Requests.java b/sdk/src/org/opends/sdk/requests/Requests.java
new file mode 100644
index 0000000..69c1dcc
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/Requests.java
@@ -0,0 +1,699 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import static org.opends.messages.UtilityMessages.WARN_READ_LDIF_RECORD_CHANGE_RECORD_WRONG_TYPE;
+
+import org.opends.messages.Message;
+import org.opends.sdk.*;
+import org.opends.sdk.ldif.ChangeRecord;
+import org.opends.sdk.ldif.LDIFChangeRecordReader;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class contains various methods for creating and manipulating
+ * requests.
+ * <p>
+ * TODO: search request from LDAP URL.
+ * <p>
+ * TODO: update request from persistent search result.
+ * <p>
+ * TODO: synchronized requests?
+ * <p>
+ * TODO: copy constructors.
+ */
+public final class Requests
+{
+
+ /**
+ * Creates a new abandon request using the provided message ID.
+ *
+ * @param messageID
+ * The message ID of the request to be abandoned.
+ * @return The new abandon request.
+ */
+ public static AbandonRequest newAbandonRequest(int messageID)
+ {
+ return new AbandonRequestImpl(messageID);
+ }
+
+
+
+ /**
+ * Creates a new add request using the provided distinguished name.
+ *
+ * @param name
+ * The distinguished name of the entry to be added.
+ * @return The new add request.
+ * @throws NullPointerException
+ * If {@code name} was {@code null}.
+ */
+ public static AddRequest newAddRequest(DN name)
+ throws NullPointerException
+ {
+ final Entry entry = new SortedEntry().setName(name);
+ return new AddRequestImpl(entry);
+ }
+
+
+
+ /**
+ * Creates a new add request backed by the provided entry.
+ * Modifications made to {@code entry} will be reflected in the
+ * returned add request. The returned add request supports updates to
+ * its list of controls, as well as updates to the name and attributes
+ * if the underlying entry allows.
+ *
+ * @param entry
+ * The entry to be added.
+ * @return The new add request.
+ * @throws NullPointerException
+ * If {@code entry} was {@code null} .
+ */
+ public static AddRequest newAddRequest(Entry entry)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(entry);
+ return new AddRequestImpl(entry);
+ }
+
+
+
+ /**
+ * Creates a new add request using the provided distinguished name
+ * decoded using the default schema.
+ *
+ * @param name
+ * The distinguished name of the entry to be added.
+ * @return The new add request.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code name} could not be decoded using the default
+ * schema.
+ * @throws NullPointerException
+ * If {@code name} was {@code null}.
+ */
+ public static AddRequest newAddRequest(String name)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ final Entry entry = new SortedEntry().setName(name);
+ return new AddRequestImpl(entry);
+ }
+
+
+
+ /**
+ * Creates a new add request using the provided lines of LDIF decoded
+ * using the default schema.
+ *
+ * @param ldifLines
+ * Lines of LDIF containing an LDIF add change record or an
+ * LDIF entry record.
+ * @return The new add request.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code ldifLines} was empty, or contained invalid
+ * LDIF, or could not be decoded using the default schema.
+ * @throws NullPointerException
+ * If {@code ldifLines} was {@code null} .
+ */
+ public static AddRequest newAddRequest(String... ldifLines)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ // LDIF change record reader is tolerant to missing change types.
+ ChangeRecord record = LDIFChangeRecordReader
+ .valueOfLDIFChangeRecord(ldifLines);
+
+ if (record instanceof AddRequest)
+ {
+ return (AddRequest) record;
+ }
+ else
+ {
+ // Wrong change type.
+ Message message = WARN_READ_LDIF_RECORD_CHANGE_RECORD_WRONG_TYPE
+ .get("add");
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ }
+
+
+
+ /**
+ * Creates a new change record (an add, delete, modify, or modify DN
+ * request) using the provided lines of LDIF decoded using the default
+ * schema.
+ *
+ * @param ldifLines
+ * Lines of LDIF containing an LDIF change record or an LDIF
+ * entry record.
+ * @return The new change record.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code ldifLines} was empty, or contained invalid
+ * LDIF, or could not be decoded using the default schema.
+ * @throws NullPointerException
+ * If {@code ldifLines} was {@code null} .
+ */
+ public static ChangeRecord newChangeRecord(String... ldifLines)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ // LDIF change record reader is tolerant to missing change types.
+ return LDIFChangeRecordReader.valueOfLDIFChangeRecord(ldifLines);
+ }
+
+
+
+ /**
+ * Creates a new compare request using the provided distinguished
+ * name, attribute name, and assertion value.
+ *
+ * @param name
+ * The distinguished name of the entry to be compared.
+ * @param attributeDescription
+ * The name of the attribute to be compared.
+ * @param assertionValue
+ * The assertion value to be compared.
+ * @return The new compare request.
+ * @throws NullPointerException
+ * If {@code name}, {@code attributeDescription}, or {@code
+ * assertionValue} was {@code null}.
+ */
+ public static CompareRequest newCompareRequest(DN name,
+ AttributeDescription attributeDescription,
+ ByteString assertionValue) throws NullPointerException
+ {
+ Validator.ensureNotNull(name, attributeDescription, assertionValue);
+ return new CompareRequestImpl(name, attributeDescription,
+ assertionValue);
+ }
+
+
+
+ /**
+ * Creates a new compare request using the provided distinguished
+ * name, attribute name, and assertion value decoded using the default
+ * schema.
+ * <p>
+ * If the assertion value is not an instance of {@code ByteString}
+ * then it will be converted using the
+ * {@link ByteString#valueOf(Object)} method.
+ *
+ * @param name
+ * The distinguished name of the entry to be compared.
+ * @param attributeDescription
+ * The name of the attribute to be compared.
+ * @param assertionValue
+ * The assertion value to be compared.
+ * @return The new compare request.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code name} or {@code attributeDescription} could not
+ * be decoded using the default schema.
+ * @throws NullPointerException
+ * If {@code name}, {@code attributeDescription}, or {@code
+ * assertionValue} was {@code null}.
+ */
+ public static CompareRequest newCompareRequest(String name,
+ String attributeDescription, Object assertionValue)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ Validator.ensureNotNull(name, attributeDescription, assertionValue);
+ return new CompareRequestImpl(DN.valueOf(name),
+ AttributeDescription.valueOf(attributeDescription), ByteString
+ .valueOf(assertionValue));
+ }
+
+
+
+ /**
+ * Creates a new delete request using the provided distinguished name.
+ *
+ * @param name
+ * The distinguished name of the entry to be deleted.
+ * @return The new delete request.
+ * @throws NullPointerException
+ * If {@code name} was {@code null}.
+ */
+ public static DeleteRequest newDeleteRequest(DN name)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(name);
+ return new DeleteRequestImpl(name);
+ }
+
+
+
+ /**
+ * Creates a new delete request using the provided distinguished name
+ * decoded using the default schema.
+ *
+ * @param name
+ * The distinguished name of the entry to be deleted.
+ * @return The new delete request.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code name} could not be decoded using the default
+ * schema.
+ * @throws NullPointerException
+ * If {@code name} was {@code null}.
+ */
+ public static DeleteRequest newDeleteRequest(String name)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ Validator.ensureNotNull(name);
+ return new DeleteRequestImpl(DN.valueOf(name));
+ }
+
+
+
+ /**
+ * Creates a new generic bind request using an empty distinguished
+ * name, authentication type, and authentication information.
+ *
+ * @param authenticationType
+ * The authentication mechanism identifier for this generic
+ * bind request.
+ * @param authenticationValue
+ * The authentication information for this generic bind
+ * request in a form defined by the authentication mechanism.
+ * @return The new generic bind request.
+ * @throws NullPointerException
+ * If {@code authenticationValue} was {@code null}.
+ */
+ public static GenericBindRequest newGenericBindRequest(
+ byte authenticationType, ByteString authenticationValue)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(authenticationValue);
+ return new GenericBindRequestImpl(DN.rootDN(), authenticationType,
+ authenticationValue);
+ }
+
+
+
+ /**
+ * Creates a new generic bind request using the provided distinguished
+ * name, authentication type, and authentication information.
+ *
+ * @param name
+ * The distinguished name of the Directory object that the
+ * client wishes to bind as (may be empty).
+ * @param authenticationType
+ * The authentication mechanism identifier for this generic
+ * bind request.
+ * @param authenticationValue
+ * The authentication information for this generic bind
+ * request in a form defined by the authentication mechanism.
+ * @return The new generic bind request.
+ * @throws NullPointerException
+ * If {@code name} or {@code authenticationValue} was
+ * {@code null}.
+ */
+ public static GenericBindRequest newGenericBindRequest(DN name,
+ byte authenticationType, ByteString authenticationValue)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(name, authenticationValue);
+ return new GenericBindRequestImpl(name, authenticationType,
+ authenticationValue);
+ }
+
+
+
+ /**
+ * Creates a new generic bind request using the provided distinguished
+ * name, authentication type, and authentication information.
+ *
+ * @param name
+ * The distinguished name of the Directory object that the
+ * client wishes to bind as (may be empty).
+ * @param authenticationType
+ * The authentication mechanism identifier for this generic
+ * bind request.
+ * @param authenticationValue
+ * The authentication information for this generic bind
+ * request in a form defined by the authentication mechanism.
+ * @return The new generic bind request.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code name} could not be decoded using the default
+ * schema.
+ * @throws NullPointerException
+ * If {@code name} or {@code authenticationValue} was
+ * {@code null}.
+ */
+ public static GenericBindRequest newGenericBindRequest(String name,
+ byte authenticationType, ByteString authenticationValue)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ Validator.ensureNotNull(name, authenticationValue);
+ return new GenericBindRequestImpl(DN.valueOf(name),
+ authenticationType, authenticationValue);
+ }
+
+
+
+ /**
+ * Creates a new generic extended request using the provided name and
+ * no value.
+ *
+ * @param requestName
+ * The dotted-decimal representation of the unique OID
+ * corresponding to this extended request.
+ * @return The new generic extended request.
+ * @throws NullPointerException
+ * If {@code requestName} was {@code null}.
+ */
+ public static GenericExtendedRequest newGenericExtendedRequest(
+ String requestName) throws NullPointerException
+ {
+ Validator.ensureNotNull(requestName);
+ return new GenericExtendedRequestImpl(requestName, null);
+ }
+
+
+
+ /**
+ * Creates a new generic extended request using the provided name and
+ * optional value.
+ *
+ * @param requestName
+ * The dotted-decimal representation of the unique OID
+ * corresponding to this extended request.
+ * @param requestValue
+ * The content of this generic extended request in a form
+ * defined by the extended operation, or {@code null} if
+ * there is no content.
+ * @return The new generic extended request.
+ * @throws NullPointerException
+ * If {@code requestName} was {@code null}.
+ */
+ public static GenericExtendedRequest newGenericExtendedRequest(
+ String requestName, ByteString requestValue)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(requestName);
+ return new GenericExtendedRequestImpl(requestName, requestValue);
+ }
+
+
+
+ /**
+ * Creates a new modify DN request using the provided distinguished
+ * name and new RDN.
+ *
+ * @param name
+ * The distinguished name of the entry to be renamed.
+ * @param newRDN
+ * The new RDN of the entry.
+ * @return The new modify DN request.
+ * @throws NullPointerException
+ * If {@code name} or {@code newRDN} was {@code null}.
+ */
+ public static ModifyDNRequest newModifyDNRequest(DN name, RDN newRDN)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(name, newRDN);
+ return new ModifyDNRequestImpl(name, newRDN);
+ }
+
+
+
+ /**
+ * Creates a new modify DN request using the provided distinguished
+ * name and new RDN decoded using the default schema.
+ *
+ * @param name
+ * The distinguished name of the entry to be renamed.
+ * @param newRDN
+ * The new RDN of the entry.
+ * @return The new modify DN request.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code name} or {@code newRDN} could not be decoded
+ * using the default schema.
+ * @throws NullPointerException
+ * If {@code name} or {@code newRDN} was {@code null}.
+ */
+ public static ModifyDNRequest newModifyDNRequest(String name,
+ String newRDN) throws LocalizedIllegalArgumentException,
+ NullPointerException
+ {
+ Validator.ensureNotNull(name, newRDN);
+ return new ModifyDNRequestImpl(DN.valueOf(name), RDN
+ .valueOf(newRDN));
+ }
+
+
+
+ /**
+ * Creates a new modify request using the provided distinguished name.
+ *
+ * @param name
+ * The distinguished name of the entry to be modified.
+ * @return The new modify request.
+ * @throws NullPointerException
+ * If {@code name} was {@code null}.
+ */
+ public static ModifyRequest newModifyRequest(DN name)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(name);
+ return new ModifyRequestImpl(name);
+ }
+
+
+
+ /**
+ * Creates a new modify request using the provided distinguished name
+ * decoded using the default schema.
+ *
+ * @param name
+ * The distinguished name of the entry to be modified.
+ * @return The new modify request.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code name} could not be decoded using the default
+ * schema.
+ * @throws NullPointerException
+ * If {@code name} was {@code null}.
+ */
+ public static ModifyRequest newModifyRequest(String name)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ Validator.ensureNotNull(name);
+ return new ModifyRequestImpl(DN.valueOf(name));
+ }
+
+
+
+ /**
+ * Creates a new modify request using the provided lines of LDIF
+ * decoded using the default schema.
+ *
+ * @param ldifLines
+ * Lines of LDIF containing a single LDIF modify change
+ * record.
+ * @return The new modify request.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code ldifLines} was empty, or contained invalid
+ * LDIF, or could not be decoded using the default schema.
+ * @throws NullPointerException
+ * If {@code ldifLines} was {@code null} .
+ */
+ public static ModifyRequest newModifyRequest(String... ldifLines)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ // LDIF change record reader is tolerant to missing change types.
+ ChangeRecord record = LDIFChangeRecordReader
+ .valueOfLDIFChangeRecord(ldifLines);
+
+ if (record instanceof ModifyRequest)
+ {
+ return (ModifyRequest) record;
+ }
+ else
+ {
+ // Wrong change type.
+ Message message = WARN_READ_LDIF_RECORD_CHANGE_RECORD_WRONG_TYPE
+ .get("modify");
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ }
+
+
+
+ /**
+ * Creates a new search request using the provided distinguished name,
+ * scope, and filter, decoded using the default schema.
+ *
+ * @param name
+ * The distinguished name of the base entry relative to which
+ * the search is to be performed.
+ * @param scope
+ * The scope of the search.
+ * @param filter
+ * The filter that defines the conditions that must be
+ * fulfilled in order for an entry to be returned.
+ * @param attributeDescriptions
+ * The names of the attributes to be included with each
+ * entry.
+ * @return The new search request.
+ * @throws NullPointerException
+ * If the {@code name}, {@code scope}, or {@code filter}
+ * were {@code null}.
+ */
+ public static SearchRequest newSearchRequest(DN name,
+ SearchScope scope, Filter filter, String... attributeDescriptions)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(name, scope, filter);
+ return new SearchRequestImpl(name, scope, filter)
+ .addAttribute(attributeDescriptions);
+ }
+
+
+
+ /**
+ * Creates a new search request using the provided distinguished name,
+ * scope, and filter, decoded using the default schema.
+ *
+ * @param name
+ * The distinguished name of the base entry relative to which
+ * the search is to be performed.
+ * @param scope
+ * The scope of the search.
+ * @param filter
+ * The filter that defines the conditions that must be
+ * fulfilled in order for an entry to be returned.
+ * @param attributeDescriptions
+ * The names of the attributes to be included with each
+ * entry.
+ * @return The new search request.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code name} could not be decoded using the default
+ * schema, or if {@code filter} is not a valid LDAP string
+ * representation of a filter.
+ * @throws NullPointerException
+ * If the {@code name}, {@code scope}, or {@code filter}
+ * were {@code null}.
+ */
+ public static SearchRequest newSearchRequest(String name,
+ SearchScope scope, String filter, String... attributeDescriptions)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ Validator.ensureNotNull(name, scope, filter);
+ return new SearchRequestImpl(DN.valueOf(name), scope, Filter
+ .valueOf(filter)).addAttribute(attributeDescriptions);
+ }
+
+
+
+ /**
+ * Creates a new simple bind request having an empty name and password
+ * suitable for anonymous authentication.
+ *
+ * @return The new simple bind request.
+ */
+ public static SimpleBindRequest newSimpleBindRequest()
+ {
+ return new SimpleBindRequestImpl(DN.rootDN(), ByteString.empty());
+ }
+
+
+
+ /**
+ * Creates a new simple bind request having the provided name and
+ * password suitable for name/password authentication.
+ *
+ * @param name
+ * The distinguished name of the Directory object that the
+ * client wishes to bind as, which may be empty.
+ * @param password
+ * The password of the Directory object that the client
+ * wishes to bind as, which may be empty indicating that an
+ * unauthenticated bind is to be performed.
+ * @return The new simple bind request.
+ * @throws NullPointerException
+ * If {@code name} or {@code password} was {@code null}.
+ */
+ public static SimpleBindRequest newSimpleBindRequest(DN name,
+ ByteString password) throws NullPointerException
+ {
+ Validator.ensureNotNull(name, password);
+ return new SimpleBindRequestImpl(name, password);
+ }
+
+
+
+ /**
+ * Creates a new simple bind request having the provided name and
+ * password suitable for name/password authentication. The name will
+ * be decoded using the default schema.
+ *
+ * @param name
+ * The distinguished name of the Directory object that the
+ * client wishes to bind as, which may be empty..
+ * @param password
+ * The password of the Directory object that the client
+ * wishes to bind as, which may be empty indicating that an
+ * unauthenticated bind is to be performed.
+ * @return The new simple bind request.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code name} could not be decoded using the default
+ * schema.
+ * @throws NullPointerException
+ * If {@code name} or {@code password} was {@code null}.
+ */
+ public static SimpleBindRequest newSimpleBindRequest(String name,
+ String password) throws LocalizedIllegalArgumentException,
+ NullPointerException
+ {
+ Validator.ensureNotNull(name, password);
+ return new SimpleBindRequestImpl(DN.valueOf(name), ByteString
+ .valueOf(password));
+ }
+
+
+
+ /**
+ * Creates a new unbind request.
+ *
+ * @return The new unbind request.
+ */
+ public static UnbindRequest newUnbindRequest()
+ {
+ return new UnbindRequestImpl();
+ }
+
+
+
+ private Requests()
+ {
+ // Prevent instantiation.
+ }
+}
diff --git a/sdk/src/org/opends/sdk/requests/SearchRequest.java b/sdk/src/org/opends/sdk/requests/SearchRequest.java
new file mode 100644
index 0000000..5e60480
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/SearchRequest.java
@@ -0,0 +1,527 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import java.util.Collection;
+
+import org.opends.sdk.DN;
+import org.opends.sdk.DereferenceAliasesPolicy;
+import org.opends.sdk.Filter;
+import org.opends.sdk.SearchScope;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * The Search operation is used to request a server to return, subject
+ * to access controls and other restrictions, a set of entries matching
+ * a complex search criterion. This can be used to read attributes from
+ * a single entry, from entries immediately subordinate to a particular
+ * entry, or from a whole subtree of entries.
+ */
+public interface SearchRequest extends Request
+{
+ /**
+ * Adds the provided attribute names to the list of attributes to be
+ * included with each entry that matches the search criteria.
+ * Attributes that are sub-types of listed attributes are implicitly
+ * included.
+ *
+ * @param attributeDescriptions
+ * The names of the attributes to be included with each
+ * entry.
+ * @return This search request.
+ * @throws UnsupportedOperationException
+ * If this search request does not permit attribute names to
+ * be added.
+ * @throws NullPointerException
+ * If {@code attributeDescriptions} was {@code null}, or if
+ * it contained a {@code null} element.
+ */
+ SearchRequest addAttribute(Collection<String> attributeDescriptions)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Adds the provided attribute name to the list of attributes to be
+ * included with each entry that matches the search criteria.
+ * Attributes that are sub-types of listed attributes are implicitly
+ * included.
+ *
+ * @param attributeDescription
+ * The name of the attribute to be included with each entry.
+ * @return This search request.
+ * @throws UnsupportedOperationException
+ * If this search request does not permit attribute names to
+ * be added.
+ * @throws NullPointerException
+ * If {@code attributeDescription} was {@code null}.
+ */
+ SearchRequest addAttribute(String attributeDescription)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Adds the provided attribute names to the list of attributes to be
+ * included with each entry that matches the search criteria.
+ * Attributes that are sub-types of listed attributes are implicitly
+ * included.
+ *
+ * @param attributeDescriptions
+ * The names of the attributes to be included with each
+ * entry.
+ * @return This search request.
+ * @throws UnsupportedOperationException
+ * If this search request does not permit attribute names to
+ * be added.
+ * @throws NullPointerException
+ * If {@code attributeDescriptions} was {@code null}, or if
+ * it contained a {@code null} element.
+ */
+ SearchRequest addAttribute(String... attributeDescriptions)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Adds the provided control to this request.
+ *
+ * @param control
+ * The control to be added to this request.
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be added.
+ * @throws NullPointerException
+ * If {@code control} was {@code null}.
+ */
+ SearchRequest addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Clears the list of attributes to be included with each entry that
+ * matches the search criteria. Attributes that are sub-types of
+ * listed attributes are implicitly included.
+ *
+ * @return This search request.
+ * @throws UnsupportedOperationException
+ * If this search request does not permit attributes to be
+ * removed.
+ */
+ SearchRequest clearAttributes() throws UnsupportedOperationException;
+
+
+
+ /**
+ * Removes all the controls included with this request.
+ *
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ */
+ SearchRequest clearControls() throws UnsupportedOperationException;
+
+
+
+ /**
+ * Returns an {@code Iterable} containing the list of attributes to be
+ * included with each entry that matches the search criteria.
+ * Attributes that are sub-types of listed attributes are implicitly
+ * included. The returned {@code Iterable} may be used to remove
+ * attribute names if permitted by this search request.
+ *
+ * @return An {@code Iterable} containing the list of attributes.
+ */
+ Iterable<String> getAttributes();
+
+
+
+ /**
+ * Returns the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be returned.
+ * @return The control, or {@code null} if the control is not included
+ * with this request.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control getControl(String oid) throws NullPointerException;
+
+
+
+ /**
+ * Returns an {@code Iterable} containing the controls included with
+ * this request. The returned {@code Iterable} may be used to remove
+ * controls if permitted by this request.
+ *
+ * @return An {@code Iterable} containing the controls.
+ */
+ Iterable<Control> getControls();
+
+
+
+ /**
+ * Returns an indication as to whether or not alias entries are to be
+ * dereferenced during the search.
+ *
+ * @return The alias dereferencing policy.
+ */
+ DereferenceAliasesPolicy getDereferenceAliasesPolicy();
+
+
+
+ /**
+ * Returns the filter that defines the conditions that must be
+ * fulfilled in order for an entry to be returned.
+ *
+ * @return The search filter.
+ */
+ Filter getFilter();
+
+
+
+ /**
+ * Returns the distinguished name of the base entry relative to which
+ * the search is to be performed.
+ *
+ * @return The distinguished name of the base entry.
+ */
+ DN getName();
+
+
+
+ /**
+ * Returns the scope of the search.
+ *
+ * @return The search scope.
+ */
+ SearchScope getScope();
+
+
+
+ /**
+ * Returns the size limit that should be used in order to restrict the
+ * maximum number of entries returned by the search.
+ * <p>
+ * A value of zero (the default) in this field indicates that no
+ * client-requested size limit restrictions are in effect. Servers may
+ * also enforce a maximum number of entries to return.
+ *
+ * @return The size limit that should be used in order to restrict the
+ * maximum number of entries returned by the search.
+ */
+ int getSizeLimit();
+
+
+
+ /**
+ * Returns the time limit that should be used in order to restrict the
+ * maximum time (in seconds) allowed for the search.
+ * <p>
+ * A value of zero (the default) in this field indicates that no
+ * client-requested time limit restrictions are in effect for the
+ * search. Servers may also enforce a maximum time limit for the
+ * search.
+ *
+ * @return The time limit that should be used in order to restrict the
+ * maximum time (in seconds) allowed for the search.
+ */
+ int getTimeLimit();
+
+
+
+ /**
+ * Indicates whether or not this search request has a list of
+ * attributes to be included with each entry that matches the search
+ * criteria.
+ *
+ * @return {@code true} if this search request has a list of
+ * attributes to be included with each entry, otherwise
+ * {@code false}.
+ */
+ boolean hasAttributes();
+
+
+
+ /**
+ * Indicates whether or not this request has any controls.
+ *
+ * @return {@code true} if this request has any controls, otherwise
+ * {@code false}.
+ */
+ boolean hasControls();
+
+
+
+ /**
+ * Indicates whether search results are to contain both attribute
+ * descriptions and values, or just attribute descriptions.
+ *
+ * @return {@code true} if only attribute descriptions (and not
+ * values) are to be returned, or {@code false} (the default)
+ * if both attribute descriptions and values are to be
+ * returned.
+ */
+ boolean isTypesOnly();
+
+
+
+ /**
+ * Removes the provided attribute name from the list of attributes to
+ * be included with each entry that matches the search criteria.
+ * Attributes that are sub-types of listed attributes are implicitly
+ * included.
+ *
+ * @param attributeDescription
+ * The name of the attribute to be removed.
+ * @return {@code true} if the attribute name was found in the list of
+ * attributes.
+ * @throws UnsupportedOperationException
+ * If this search request does not permit attribute names to
+ * be removed.
+ * @throws NullPointerException
+ * If {@code attributeDescription} was {@code null}.
+ */
+ boolean removeAttribute(String attributeDescription)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Removes the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be removed.
+ * @return The removed control, or {@code null} if the control is not
+ * included with this request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the alias dereferencing policy to be used during the search.
+ *
+ * @param policy
+ * The alias dereferencing policy to be used during the
+ * search.
+ * @return This search request.
+ * @throws UnsupportedOperationException
+ * If this search request does not permit the alias
+ * dereferencing policy to be set.
+ * @throws NullPointerException
+ * If {@code policy} was {@code null}.
+ */
+ SearchRequest setDereferenceAliasesPolicy(
+ DereferenceAliasesPolicy policy)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the filter that defines the conditions that must be fulfilled
+ * in order for an entry to be returned.
+ *
+ * @param filter
+ * The filter that defines the conditions that must be
+ * fulfilled in order for an entry to be returned.
+ * @return This search request.
+ * @throws UnsupportedOperationException
+ * If this search request does not permit the filter to be
+ * set.
+ * @throws NullPointerException
+ * If {@code filter} was {@code null}.
+ */
+ SearchRequest setFilter(Filter filter)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the filter that defines the conditions that must be fulfilled
+ * in order for an entry to be returned.
+ *
+ * @param filter
+ * The filter that defines the conditions that must be
+ * fulfilled in order for an entry to be returned.
+ * @return This search request.
+ * @throws UnsupportedOperationException
+ * If this search request does not permit the filter to be
+ * set.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code filter} is not a valid LDAP string
+ * representation of a filter.
+ * @throws NullPointerException
+ * If {@code filter} was {@code null}.
+ */
+ SearchRequest setFilter(String filter)
+ throws UnsupportedOperationException,
+ LocalizedIllegalArgumentException, NullPointerException;
+
+
+
+ /**
+ * Sets the distinguished name of the base entry relative to which the
+ * search is to be performed.
+ *
+ * @param dn
+ * The distinguished name of the base entry relative to which
+ * the search is to be performed.
+ * @return This search request.
+ * @throws UnsupportedOperationException
+ * If this search request does not permit the distinguished
+ * name to be set.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ SearchRequest setName(DN dn) throws UnsupportedOperationException,
+ NullPointerException;
+
+
+
+ /**
+ * Sets the distinguished name of the base entry relative to which the
+ * search is to be performed.
+ *
+ * @param dn
+ * The distinguished name of the base entry relative to which
+ * the search is to be performed.
+ * @return This search request.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code dn} could not be decoded using the default
+ * schema.
+ * @throws UnsupportedOperationException
+ * If this search request does not permit the distinguished
+ * name to be set.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ SearchRequest setName(String dn)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the scope of the search.
+ *
+ * @param scope
+ * The scope of the search.
+ * @return This search request.
+ * @throws UnsupportedOperationException
+ * If this search request does not permit the scope to be
+ * set.
+ * @throws NullPointerException
+ * If {@code scope} was {@code null}.
+ */
+ SearchRequest setScope(SearchScope scope)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the size limit that should be used in order to restrict the
+ * maximum number of entries returned by the search.
+ * <p>
+ * A value of zero (the default) in this field indicates that no
+ * client-requested size limit restrictions are in effect. Servers may
+ * also enforce a maximum number of entries to return.
+ *
+ * @param limit
+ * The size limit that should be used in order to restrict
+ * the maximum number of entries returned by the search.
+ * @return This search request.
+ * @throws UnsupportedOperationException
+ * If this search request does not permit the size limit to
+ * be set.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code limit} was negative.
+ */
+ SearchRequest setSizeLimit(int limit)
+ throws UnsupportedOperationException,
+ LocalizedIllegalArgumentException;
+
+
+
+ /**
+ * Sets the time limit that should be used in order to restrict the
+ * maximum time (in seconds) allowed for the search.
+ * <p>
+ * A value of zero (the default) in this field indicates that no
+ * client-requested time limit restrictions are in effect for the
+ * search. Servers may also enforce a maximum time limit for the
+ * search.
+ *
+ * @param limit
+ * The time limit that should be used in order to restrict
+ * the maximum time (in seconds) allowed for the search.
+ * @return This search request.
+ * @throws UnsupportedOperationException
+ * If this search request does not permit the time limit to
+ * be set.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code limit} was negative.
+ */
+ SearchRequest setTimeLimit(int limit)
+ throws UnsupportedOperationException,
+ LocalizedIllegalArgumentException;
+
+
+
+ /**
+ * Specifies whether search results are to contain both attribute
+ * descriptions and values, or just attribute descriptions.
+ *
+ * @param typesOnly
+ * {@code true} if only attribute descriptions (and not
+ * values) are to be returned, or {@code false} (the default)
+ * if both attribute descriptions and values are to be
+ * returned.
+ * @return This search request.
+ * @throws UnsupportedOperationException
+ * If this search request does not permit the types-only
+ * parameter to be set.
+ */
+ SearchRequest setTypesOnly(boolean typesOnly)
+ throws UnsupportedOperationException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/SearchRequestImpl.java b/sdk/src/org/opends/sdk/requests/SearchRequestImpl.java
new file mode 100644
index 0000000..5c83d43
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/SearchRequestImpl.java
@@ -0,0 +1,416 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opends.sdk.DN;
+import org.opends.sdk.DereferenceAliasesPolicy;
+import org.opends.sdk.Filter;
+import org.opends.sdk.SearchScope;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Search request implementation.
+ */
+final class SearchRequestImpl extends
+ AbstractRequestImpl<SearchRequest> implements SearchRequest
+{
+
+ private final List<String> attributes = new LinkedList<String>();
+
+ private DN name;
+
+ private DereferenceAliasesPolicy dereferenceAliasesPolicy = DereferenceAliasesPolicy.NEVER;
+
+ private Filter filter;
+
+ private SearchScope scope;
+
+ private int sizeLimit = 0;
+
+ private int timeLimit = 0;
+
+ private boolean typesOnly = false;
+
+
+
+ /**
+ * Creates a new search request using the provided distinguished name,
+ * scope, and filter, decoded using the default schema.
+ *
+ * @param name
+ * The distinguished name of the base entry relative to which
+ * the search is to be performed.
+ * @param scope
+ * The scope of the search.
+ * @param filter
+ * The filter that defines the conditions that must be
+ * fulfilled in order for an entry to be returned.
+ * @param attributeDescriptions
+ * The names of the attributes to be included with each
+ * entry.
+ * @throws NullPointerException
+ * If the {@code name}, {@code scope}, or {@code filter}
+ * were {@code null}.
+ */
+ SearchRequestImpl(DN name, SearchScope scope, Filter filter)
+ throws NullPointerException
+ {
+ this.name = name;
+ this.scope = scope;
+ this.filter = filter;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SearchRequest addAttribute(
+ Collection<String> attributeDescriptions)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(attributeDescriptions);
+
+ attributes.addAll(attributeDescriptions);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SearchRequest addAttribute(String attributeDescription)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(attributeDescription);
+
+ attributes.add(attributeDescription);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SearchRequest addAttribute(String... attributeDescriptions)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull((Object) attributeDescriptions);
+
+ for (final String attributeDescription : attributeDescriptions)
+ {
+ attributes.add(attributeDescription);
+ }
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SearchRequest clearAttributes()
+ {
+ attributes.clear();
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterable<String> getAttributes()
+ {
+ return attributes;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public DereferenceAliasesPolicy getDereferenceAliasesPolicy()
+ {
+ return dereferenceAliasesPolicy;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Filter getFilter()
+ {
+ return filter;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public DN getName()
+ {
+ return name;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SearchScope getScope()
+ {
+ return scope;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int getSizeLimit()
+ {
+ return sizeLimit;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int getTimeLimit()
+ {
+ return timeLimit;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean hasAttributes()
+ {
+ return !attributes.isEmpty();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isTypesOnly()
+ {
+ return typesOnly;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean removeAttribute(String attributeDescription)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(attributeDescription);
+
+ return attributes.remove(attributeDescription);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SearchRequest setDereferenceAliasesPolicy(
+ DereferenceAliasesPolicy policy) throws NullPointerException
+ {
+ Validator.ensureNotNull(policy);
+
+ this.dereferenceAliasesPolicy = policy;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SearchRequest setFilter(Filter filter)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(filter);
+
+ this.filter = filter;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SearchRequest setFilter(String filter)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ this.filter = Filter.valueOf(filter);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SearchRequest setName(DN dn) throws NullPointerException
+ {
+ Validator.ensureNotNull(dn);
+
+ this.name = dn;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SearchRequest setName(String dn)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ Validator.ensureNotNull(dn);
+
+ this.name = DN.valueOf(dn);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SearchRequest setScope(SearchScope scope)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(scope);
+
+ this.scope = scope;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SearchRequest setSizeLimit(int limit)
+ throws LocalizedIllegalArgumentException
+ {
+ // FIXME: I18N error message.
+ Validator.ensureTrue(limit >= 0, "negative size limit");
+
+ this.sizeLimit = limit;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SearchRequest setTimeLimit(int limit)
+ throws LocalizedIllegalArgumentException
+ {
+ // FIXME: I18N error message.
+ Validator.ensureTrue(limit >= 0, "negative time limit");
+
+ this.timeLimit = limit;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SearchRequest setTypesOnly(boolean typesOnly)
+ {
+ this.typesOnly = typesOnly;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("SearchRequest(name=");
+ builder.append(getName());
+ builder.append(", scope=");
+ builder.append(getScope());
+ builder.append(", dereferenceAliasesPolicy=");
+ builder.append(getDereferenceAliasesPolicy());
+ builder.append(", sizeLimit=");
+ builder.append(getSizeLimit());
+ builder.append(", timeLimit=");
+ builder.append(getTimeLimit());
+ builder.append(", typesOnly=");
+ builder.append(isTypesOnly());
+ builder.append(", filter=");
+ builder.append(getFilter());
+ builder.append(", attributes=");
+ builder.append(getAttributes());
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+
+
+
+ SearchRequest getThis()
+ {
+ return this;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/requests/SimpleBindRequest.java b/sdk/src/org/opends/sdk/requests/SimpleBindRequest.java
new file mode 100644
index 0000000..8b0c07d
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/SimpleBindRequest.java
@@ -0,0 +1,254 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.DN;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * The simple authentication method of the Bind Operation provides three
+ * authentication mechanisms:
+ * <ul>
+ * <li>An anonymous authentication mechanism, in which both the name
+ * (the bind DN) and password are zero length.
+ * <li>An unauthenticated authentication mechanism using credentials
+ * consisting of a name (the bind DN) and a zero length password.
+ * <li>A name/password authentication mechanism using credentials
+ * consisting of a name (the bind DN) and a password.
+ * </ul>
+ */
+public interface SimpleBindRequest extends BindRequest
+{
+ /**
+ * Adds the provided control to this request.
+ *
+ * @param control
+ * The control to be added to this request.
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be added.
+ * @throws NullPointerException
+ * If {@code control} was {@code null}.
+ */
+ SimpleBindRequest addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Removes all the controls included with this request.
+ *
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ */
+ SimpleBindRequest clearControls()
+ throws UnsupportedOperationException;
+
+
+
+ /**
+ * Returns the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be returned.
+ * @return The control, or {@code null} if the control is not included
+ * with this request.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control getControl(String oid) throws NullPointerException;
+
+
+
+ /**
+ * Returns an {@code Iterable} containing the controls included with
+ * this request. The returned {@code Iterable} may be used to remove
+ * controls if permitted by this request.
+ *
+ * @return An {@code Iterable} containing the controls.
+ */
+ Iterable<Control> getControls();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ DN getName();
+
+
+
+ /**
+ * Returns the password of the Directory object that the client wishes
+ * to bind as. The password may be empty (but never {@code null}) when
+ * used for of anonymous or unauthenticated binds.
+ *
+ * @return The password of the Directory object that the client wishes
+ * to bind as.
+ */
+ ByteString getPassword();
+
+
+
+ /**
+ * Returns the password of the Directory object that the client wishes
+ * to bind as decoded as a UTF-8 string. The password may be empty
+ * (but never {@code null}) when used for of anonymous or
+ * unauthenticated binds.
+ *
+ * @return The password of the Directory object that the client wishes
+ * to bind as decoded as a UTF-8 string.
+ */
+ String getPasswordAsString();
+
+
+
+ /**
+ * Indicates whether or not this request has any controls.
+ *
+ * @return {@code true} if this request has any controls, otherwise
+ * {@code false}.
+ */
+ boolean hasControls();
+
+
+
+ /**
+ * Removes the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be removed.
+ * @return The removed control, or {@code null} if the control is not
+ * included with this request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the distinguished name of the Directory object that the client
+ * wishes to bind as. The distinguished name may be empty (but never
+ * {@code null} when used for of anonymous binds, or when using SASL
+ * authentication. The server shall not dereference any aliases in
+ * locating the named object.
+ *
+ * @param dn
+ * The distinguished name of the Directory object that the
+ * client wishes to bind as.
+ * @return This bind request.
+ * @throws UnsupportedOperationException
+ * If this bind request does not permit the distinguished
+ * name to be set.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ SimpleBindRequest setName(DN dn)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the distinguished name of the Directory object that the client
+ * wishes to bind as. The distinguished name may be empty (but never
+ * {@code null} when used for of anonymous binds, or when using SASL
+ * authentication. The server shall not dereference any aliases in
+ * locating the named object.
+ *
+ * @param dn
+ * The distinguished name of the Directory object that the
+ * client wishes to bind as.
+ * @return This bind request.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code dn} could not be decoded using the default
+ * schema.
+ * @throws UnsupportedOperationException
+ * If this bind request does not permit the distinguished
+ * name to be set.
+ * @throws NullPointerException
+ * If {@code dn} was {@code null}.
+ */
+ SimpleBindRequest setName(String dn)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the password of the Directory object that the client wishes to
+ * bind as. The password may be empty (but never {@code null}) when
+ * used for of anonymous or unauthenticated binds.
+ *
+ * @param password
+ * The password of the Directory object that the client
+ * wishes to bind as, which may be empty.
+ * @return This simple bind request.
+ * @throws UnsupportedOperationException
+ * If this simple bind request does not permit the password
+ * to be set.
+ * @throws NullPointerException
+ * If {@code password} was {@code null}.
+ */
+ SimpleBindRequest setPassword(ByteString password)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the password of the Directory object that the client wishes to
+ * bind as. The password will be converted to a UTF-8 octet string.
+ * The password may be empty (but never {@code null}) when used for of
+ * anonymous or unauthenticated binds.
+ *
+ * @param password
+ * The password of the Directory object that the client
+ * wishes to bind as, which may be empty.
+ * @return This simple bind request.
+ * @throws UnsupportedOperationException
+ * If this simple bind request does not permit the password
+ * to be set.
+ * @throws NullPointerException
+ * If {@code password} was {@code null}.
+ */
+ SimpleBindRequest setPassword(String password)
+ throws UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/SimpleBindRequestImpl.java b/sdk/src/org/opends/sdk/requests/SimpleBindRequestImpl.java
new file mode 100644
index 0000000..5856934
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/SimpleBindRequestImpl.java
@@ -0,0 +1,174 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.DN;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Simple bind request implementation.
+ */
+final class SimpleBindRequestImpl extends
+ AbstractBindRequest<SimpleBindRequest> implements SimpleBindRequest
+{
+ private ByteString password = ByteString.empty();
+
+ private DN name = DN.rootDN();
+
+
+
+ /**
+ * Creates a new simple bind request having the provided name and
+ * password suitable for name/password authentication.
+ *
+ * @param name
+ * The distinguished name of the Directory object that the
+ * client wishes to bind as, which may be empty.
+ * @param password
+ * The password of the Directory object that the client
+ * wishes to bind as, which may be empty indicating that an
+ * unauthenticated bind is to be performed.
+ * @throws NullPointerException
+ * If {@code name} or {@code password} was {@code null}.
+ */
+ SimpleBindRequestImpl(DN name, ByteString password)
+ throws NullPointerException
+ {
+ this.name = name;
+ this.password = password;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public DN getName()
+ {
+ return name;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteString getPassword()
+ {
+ return password;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getPasswordAsString()
+ {
+ return password.toString();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SimpleBindRequest setName(DN dn)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(dn);
+ this.name = dn;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SimpleBindRequest setName(String dn)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(dn);
+ this.name = DN.valueOf(dn);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SimpleBindRequest setPassword(ByteString password)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(password);
+ this.password = password;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SimpleBindRequest setPassword(String password)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(password);
+ this.password = ByteString.valueOf(password);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("SimpleBindRequest(name=");
+ builder.append(getName());
+ builder.append(", authentication=simple");
+ builder.append(", password=");
+ builder.append(getPasswordAsString());
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/requests/UnbindRequest.java b/sdk/src/org/opends/sdk/requests/UnbindRequest.java
new file mode 100644
index 0000000..2fc1739
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/UnbindRequest.java
@@ -0,0 +1,119 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.controls.Control;
+
+
+
+/**
+ * The Unbind operation allows a client to terminate an LDAP session.
+ */
+public interface UnbindRequest extends Request
+{
+ /**
+ * Adds the provided control to this request.
+ *
+ * @param control
+ * The control to be added to this request.
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be added.
+ * @throws NullPointerException
+ * If {@code control} was {@code null}.
+ */
+ UnbindRequest addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Removes all the controls included with this request.
+ *
+ * @return This request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ */
+ UnbindRequest clearControls() throws UnsupportedOperationException;
+
+
+
+ /**
+ * Returns the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be returned.
+ * @return The control, or {@code null} if the control is not included
+ * with this request.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control getControl(String oid) throws NullPointerException;
+
+
+
+ /**
+ * Returns an {@code Iterable} containing the controls included with
+ * this request. The returned {@code Iterable} may be used to remove
+ * controls if permitted by this request.
+ *
+ * @return An {@code Iterable} containing the controls.
+ */
+ Iterable<Control> getControls();
+
+
+
+ /**
+ * Indicates whether or not this request has any controls.
+ *
+ * @return {@code true} if this request has any controls, otherwise
+ * {@code false}.
+ */
+ boolean hasControls();
+
+
+
+ /**
+ * Removes the first control contained in this request having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be removed.
+ * @return The removed control, or {@code null} if the control is not
+ * included with this request.
+ * @throws UnsupportedOperationException
+ * If this request does not permit controls to be removed.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException;
+}
diff --git a/sdk/src/org/opends/sdk/requests/UnbindRequestImpl.java b/sdk/src/org/opends/sdk/requests/UnbindRequestImpl.java
new file mode 100644
index 0000000..72a5485
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/UnbindRequestImpl.java
@@ -0,0 +1,67 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+/**
+ * Unbind request implementation.
+ */
+final class UnbindRequestImpl extends
+ AbstractRequestImpl<UnbindRequest> implements UnbindRequest
+{
+
+ /**
+ * Creates a new unbind request.
+ */
+ UnbindRequestImpl()
+ {
+ // Do nothing.
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("UnbindRequest(controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+
+
+
+ UnbindRequest getThis()
+ {
+ return this;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/AbstractExtendedResult.java b/sdk/src/org/opends/sdk/responses/AbstractExtendedResult.java
new file mode 100644
index 0000000..be40ebf
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/AbstractExtendedResult.java
@@ -0,0 +1,113 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * An abstract Extended result which can be used as the basis for
+ * implementing new Extended operations.
+ *
+ * @param <S>
+ * The type of Extended result.
+ */
+public abstract class AbstractExtendedResult<S extends ExtendedResult>
+ extends AbstractResultImpl<S> implements ExtendedResult
+{
+
+ /**
+ * Creates a new extended result using the provided result code.
+ *
+ * @param resultCode
+ * The result code.
+ * @throws NullPointerException
+ * If {@code resultCode} was {@code null}.
+ */
+ protected AbstractExtendedResult(ResultCode resultCode)
+ throws NullPointerException
+ {
+ super(resultCode);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public abstract String getResponseName();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public abstract ByteString getResponseValue();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String toString()
+ {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("ExtendedResult(resultCode=");
+ builder.append(getResultCode());
+ builder.append(", matchedDN=");
+ builder.append(getMatchedDN());
+ builder.append(", diagnosticMessage=");
+ builder.append(getDiagnosticMessage());
+ builder.append(", referrals=");
+ builder.append(getReferralURIs());
+ builder.append(", responseName=");
+ builder.append(getResponseName() == null ? "" : getResponseName());
+ builder.append(", responseValue=");
+ final ByteString value = getResponseValue();
+ builder.append(value == null ? ByteString.empty() : value);
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @SuppressWarnings("unchecked")
+ final S getThis()
+ {
+ return (S) this;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/responses/AbstractIntermediateResponse.java b/sdk/src/org/opends/sdk/responses/AbstractIntermediateResponse.java
new file mode 100644
index 0000000..f2278e8
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/AbstractIntermediateResponse.java
@@ -0,0 +1,98 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * An abstract Intermediate response which can be used as the basis for
+ * implementing new Intermediate responses.
+ *
+ * @param <S>
+ * The type of Intermediate response.
+ */
+public abstract class AbstractIntermediateResponse<S extends IntermediateResponse>
+ extends AbstractResponseImpl<S> implements IntermediateResponse
+{
+
+ /**
+ * Creates a new intermediate response.
+ */
+ protected AbstractIntermediateResponse()
+ {
+ // Nothing to do.
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public abstract String getResponseName();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public abstract ByteString getResponseValue();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String toString()
+ {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("IntermediateResponse(responseName=");
+ builder.append(getResponseName() == null ? "" : getResponseName());
+ builder.append(", responseValue=");
+ final ByteString value = getResponseValue();
+ builder.append(value == null ? ByteString.empty() : value);
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @SuppressWarnings("unchecked")
+ final S getThis()
+ {
+ return (S) this;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/responses/AbstractResponseImpl.java b/sdk/src/org/opends/sdk/responses/AbstractResponseImpl.java
new file mode 100644
index 0000000..26e74e2
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/AbstractResponseImpl.java
@@ -0,0 +1,170 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Modifiable response implementation.
+ *
+ * @param <S>
+ * The type of response.
+ */
+abstract class AbstractResponseImpl<S extends Response> implements
+ Response
+{
+ private final List<Control> controls = new LinkedList<Control>();
+
+
+
+ /**
+ * Creates a new modifiable response implementation.
+ */
+ AbstractResponseImpl()
+ {
+ // No implementation required.
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final S addControl(Control control)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(control);
+ controls.add(control);
+ return getThis();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final S clearControls()
+ {
+ controls.clear();
+ return getThis();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final Control getControl(String oid)
+ {
+ Validator.ensureNotNull(oid);
+
+ // Avoid creating an iterator if possible.
+ if (controls.isEmpty())
+ {
+ return null;
+ }
+
+ for (final Control control : controls)
+ {
+ if (control.getOID().equals(oid))
+ {
+ return control;
+ }
+ }
+
+ return null;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final Iterable<Control> getControls()
+ {
+ return controls;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final boolean hasControls()
+ {
+ return !controls.isEmpty();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final Control removeControl(String oid)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(oid);
+
+ // Avoid creating an iterator if possible.
+ if (controls.isEmpty())
+ {
+ return null;
+ }
+
+ final Iterator<Control> iterator = controls.iterator();
+ while (iterator.hasNext())
+ {
+ final Control control = iterator.next();
+ if (control.getOID().equals(oid))
+ {
+ iterator.remove();
+ return control;
+ }
+ }
+
+ return null;
+ }
+
+
+
+ public abstract String toString();
+
+
+
+ abstract S getThis();
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/AbstractResultImpl.java b/sdk/src/org/opends/sdk/responses/AbstractResultImpl.java
new file mode 100644
index 0000000..33c55f8
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/AbstractResultImpl.java
@@ -0,0 +1,245 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Modifiable result implementation.
+ *
+ * @param <S>
+ * The type of result.
+ */
+abstract class AbstractResultImpl<S extends Result> extends
+ AbstractResponseImpl<S> implements Result
+{
+ // For local errors caused by internal exceptions.
+ private Throwable cause = null;
+
+ private String diagnosticMessage = "";
+
+ private String matchedDN = "";
+
+ private final List<String> referralURIs = new LinkedList<String>();
+
+ private ResultCode resultCode;
+
+
+
+ /**
+ * Creates a new modifiable result implementation using the provided
+ * result code.
+ *
+ * @param resultCode
+ * The result code.
+ * @throws NullPointerException
+ * If {@code resultCode} was {@code null}.
+ */
+ AbstractResultImpl(ResultCode resultCode) throws NullPointerException
+ {
+ this.resultCode = resultCode;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final S addReferralURI(String uri) throws NullPointerException
+ {
+ Validator.ensureNotNull(uri);
+
+ referralURIs.add(uri);
+ return getThis();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final S clearReferralURIs()
+ {
+ referralURIs.clear();
+ return getThis();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final Throwable getCause()
+ {
+ return cause;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final String getDiagnosticMessage()
+ {
+ return diagnosticMessage;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final String getMatchedDN()
+ {
+ return matchedDN;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final Iterable<String> getReferralURIs()
+ {
+ return referralURIs;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final ResultCode getResultCode()
+ {
+ return resultCode;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final boolean hasReferralURIs()
+ {
+ return !referralURIs.isEmpty();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final boolean isReferral()
+ {
+ final ResultCode code = getResultCode();
+ return code.equals(ResultCode.REFERRAL);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final boolean isSuccess()
+ {
+ final ResultCode code = getResultCode();
+ return !code.isExceptional();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final S setCause(Throwable cause)
+ {
+ this.cause = cause;
+ return getThis();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final S setDiagnosticMessage(String message)
+ {
+ if (message == null)
+ {
+ this.diagnosticMessage = "";
+ }
+ else
+ {
+ this.diagnosticMessage = message;
+ }
+
+ return getThis();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final S setMatchedDN(String dn)
+ {
+ if (dn == null)
+ {
+ this.matchedDN = "";
+ }
+ else
+ {
+ this.matchedDN = dn;
+ }
+
+ return getThis();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final S setResultCode(ResultCode resultCode)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(resultCode);
+
+ this.resultCode = resultCode;
+ return getThis();
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/AbstractUnmodifiableResponseImpl.java b/sdk/src/org/opends/sdk/responses/AbstractUnmodifiableResponseImpl.java
new file mode 100644
index 0000000..d2fad61
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/AbstractUnmodifiableResponseImpl.java
@@ -0,0 +1,137 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.Iterables;
+
+
+
+/**
+ * Unmodifiable response implementation.
+ *
+ * @param <S>
+ * The type of response.
+ */
+abstract class AbstractUnmodifiableResponseImpl<S extends Response>
+ implements Response
+{
+
+ private final S impl;
+
+
+
+ /**
+ * Creates a new unmodifiable response implementation.
+ *
+ * @param impl
+ * The underlying response implementation to be made
+ * unmodifiable.
+ */
+ AbstractUnmodifiableResponseImpl(S impl)
+ {
+ this.impl = impl;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final S addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final S clearControls() throws UnsupportedOperationException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final Control getControl(String oid)
+ throws NullPointerException
+ {
+ // FIXME: ensure that controls are immutable.
+ return impl.getControl(oid);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final Iterable<Control> getControls()
+ {
+ // FIXME: ensure that controls are immutable.
+ return Iterables.unmodifiable(impl.getControls());
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final boolean hasControls()
+ {
+ return impl.hasControls();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final String toString()
+ {
+ return impl.toString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/responses/AbstractUnmodifiableResultImpl.java b/sdk/src/org/opends/sdk/responses/AbstractUnmodifiableResultImpl.java
new file mode 100644
index 0000000..5873fd4
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/AbstractUnmodifiableResultImpl.java
@@ -0,0 +1,167 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.ResultCode;
+
+
+
+/**
+ * Unmodifiable result implementation.
+ *
+ * @param <S>
+ * The type of result.
+ */
+abstract class AbstractUnmodifiableResultImpl<S extends Result> extends
+ AbstractUnmodifiableResponseImpl<S> implements Result
+{
+
+ private final S impl;
+
+
+
+ /**
+ * Creates a new unmodifiable result implementation.
+ *
+ * @param impl
+ * The underlying result implementation to be made
+ * unmodifiable.
+ */
+ AbstractUnmodifiableResultImpl(S impl)
+ {
+ super(impl);
+ this.impl = impl;
+ }
+
+
+
+ public final S addReferralURI(String uri)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ public final S clearReferralURIs()
+ throws UnsupportedOperationException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ public final Throwable getCause()
+ {
+ return impl.getCause();
+ }
+
+
+
+ public final String getDiagnosticMessage()
+ {
+ return impl.getDiagnosticMessage();
+ }
+
+
+
+ public final String getMatchedDN()
+ {
+ return impl.getMatchedDN();
+ }
+
+
+
+ public final Iterable<String> getReferralURIs()
+ {
+ return impl.getReferralURIs();
+ }
+
+
+
+ public final ResultCode getResultCode()
+ {
+ return impl.getResultCode();
+ }
+
+
+
+ public final boolean hasReferralURIs()
+ {
+ return impl.hasReferralURIs();
+ }
+
+
+
+ public final boolean isReferral()
+ {
+ return impl.isReferral();
+ }
+
+
+
+ public final boolean isSuccess()
+ {
+ return impl.isSuccess();
+ }
+
+
+
+ public final S setCause(Throwable cause)
+ throws UnsupportedOperationException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ public final S setDiagnosticMessage(String message)
+ throws UnsupportedOperationException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ public final S setMatchedDN(String dn)
+ throws UnsupportedOperationException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ public final S setResultCode(ResultCode resultCode)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/BindResult.java b/sdk/src/org/opends/sdk/responses/BindResult.java
new file mode 100644
index 0000000..359ddd7
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/BindResult.java
@@ -0,0 +1,208 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * A Bind result indicates the status of the client's request for
+ * authentication.
+ * <p>
+ * A successful Bind operation is indicated by a Bind result with a
+ * result code set to {@link ResultCode#SUCCESS} and can be determined
+ * by invoking the {@link #isSuccess} method.
+ * <p>
+ * The server SASL credentials field is used as part of a SASL-defined
+ * bind mechanism to allow the client to authenticate the server to
+ * which it is communicating, or to perform "challenge-response"
+ * authentication. If the client bound using a form of simple
+ * authentication, or the SASL mechanism does not require the server to
+ * return information to the client, then this field shall not be
+ * included in the Bind result.
+ * <p>
+ * If the server requires the client to send a new SASL Bind request in
+ * order to continue the authentication process then the result code is
+ * set to {@link ResultCode#SASL_BIND_IN_PROGRESS} and can be determined
+ * by invoking the {@link #isSASLBindInProgress} method.
+ */
+public interface BindResult extends Result
+{
+ /**
+ * {@inheritDoc}
+ */
+ BindResult addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ BindResult addReferralURI(String uri)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ BindResult clearControls() throws UnsupportedOperationException;
+
+
+
+ BindResult clearReferralURIs() throws UnsupportedOperationException;
+
+
+
+ Throwable getCause();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Control getControl(String oid) throws NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Iterable<Control> getControls();
+
+
+
+ String getDiagnosticMessage();
+
+
+
+ String getMatchedDN();
+
+
+
+ Iterable<String> getReferralURIs();
+
+
+
+ ResultCode getResultCode();
+
+
+
+ /**
+ * Returns the server SASL credentials associated with this bind
+ * result.
+ *
+ * @return The server SASL credentials, or {@code null} indicating
+ * that none was provided.
+ */
+ ByteString getServerSASLCredentials();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ boolean hasControls();
+
+
+
+ boolean hasReferralURIs();
+
+
+
+ boolean isReferral();
+
+
+
+ /**
+ * Indicates whether or not the server requires the client to send a
+ * new SASL Bind request with the same SASL mechanism in order to
+ * continue the authentication process. This typically occurs during
+ * multi-stage (challenge response) authentication.
+ * <p>
+ * Specifically, this method returns {@code true} if the result code
+ * is equal to {@link ResultCode#SASL_BIND_IN_PROGRESS}.
+ *
+ * @return {@code true} if the server requires the client to send a
+ * new SASL Bind request, otherwise {@code false}.
+ */
+ boolean isSASLBindInProgress();
+
+
+
+ boolean isSuccess();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ BindResult setCause(Throwable cause)
+ throws UnsupportedOperationException;
+
+
+
+ BindResult setDiagnosticMessage(String message)
+ throws UnsupportedOperationException;
+
+
+
+ BindResult setMatchedDN(String dn)
+ throws UnsupportedOperationException;
+
+
+
+ BindResult setResultCode(ResultCode resultCode)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the server SASL credentials associated with this bind result.
+ *
+ * @param credentials
+ * The server SASL credentials associated with this bind
+ * result, which may be {@code null} indicating that none was
+ * provided.
+ * @return This bind result.
+ * @throws UnsupportedOperationException
+ * If this bind result does not permit the server SASL
+ * credentials to be set.
+ */
+ BindResult setServerSASLCredentials(ByteString credentials)
+ throws UnsupportedOperationException;
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/BindResultImpl.java b/sdk/src/org/opends/sdk/responses/BindResultImpl.java
new file mode 100644
index 0000000..df56a42
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/BindResultImpl.java
@@ -0,0 +1,126 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * Bind result implementation.
+ */
+final class BindResultImpl extends AbstractResultImpl<BindResult>
+ implements BindResult
+{
+ private ByteString credentials = null;
+
+
+
+ /**
+ * Creates a new bind result using the provided result code.
+ *
+ * @param resultCode
+ * The result code.
+ * @throws NullPointerException
+ * If {@code resultCode} was {@code null}.
+ */
+ BindResultImpl(ResultCode resultCode) throws NullPointerException
+ {
+ super(resultCode);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteString getServerSASLCredentials()
+ {
+ return credentials;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isSASLBindInProgress()
+ {
+ final ResultCode code = getResultCode();
+ return code.equals(ResultCode.SASL_BIND_IN_PROGRESS);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public BindResult setServerSASLCredentials(ByteString credentials)
+ throws UnsupportedOperationException
+ {
+ this.credentials = credentials;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("BindResult(resultCode=");
+ builder.append(getResultCode());
+ builder.append(", matchedDN=");
+ builder.append(getMatchedDN());
+ builder.append(", diagnosticMessage=");
+ builder.append(getDiagnosticMessage());
+ builder.append(", referrals=");
+ builder.append(getReferralURIs());
+ builder.append(", serverSASLCreds=");
+ builder.append(getServerSASLCredentials() == null ? ByteString
+ .empty() : getServerSASLCredentials());
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+
+
+
+ BindResult getThis()
+ {
+ return this;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/CompareResult.java b/sdk/src/org/opends/sdk/responses/CompareResult.java
new file mode 100644
index 0000000..bf6fe63
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/CompareResult.java
@@ -0,0 +1,166 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * compare the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.controls.Control;
+
+
+
+/**
+ * An Compare result indicates the final status of an Compare operation.
+ * <p>
+ * If the attribute value assertion in the Compare request matched a
+ * value of the attribute or sub-type according to the attribute's
+ * equality matching rule then the result code is set to
+ * {@link ResultCode#COMPARE_TRUE} and can be determined by invoking the
+ * {@link #matched} method.
+ */
+public interface CompareResult extends Result
+{
+ /**
+ * {@inheritDoc}
+ */
+ CompareResult addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ CompareResult addReferralURI(String uri)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ CompareResult clearControls() throws UnsupportedOperationException;
+
+
+
+ CompareResult clearReferralURIs()
+ throws UnsupportedOperationException;
+
+
+
+ Throwable getCause();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Control getControl(String oid) throws NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Iterable<Control> getControls();
+
+
+
+ String getDiagnosticMessage();
+
+
+
+ String getMatchedDN();
+
+
+
+ Iterable<String> getReferralURIs();
+
+
+
+ ResultCode getResultCode();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ boolean hasControls();
+
+
+
+ boolean hasReferralURIs();
+
+
+
+ boolean isReferral();
+
+
+
+ boolean isSuccess();
+
+
+
+ /**
+ * Indicates whether or not the attribute value assertion in the
+ * Compare request matched a value of the attribute or sub-type
+ * according to the attribute's equality matching rule.
+ * <p>
+ * Specifically, this method returns {@code true} if the result code
+ * is equal to {@link ResultCode#COMPARE_TRUE}.
+ *
+ * @return {@code true} if the attribute value assertion matched,
+ * otherwise {@code false}.
+ */
+ boolean matched();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ CompareResult setCause(Throwable cause)
+ throws UnsupportedOperationException;
+
+
+
+ CompareResult setDiagnosticMessage(String message)
+ throws UnsupportedOperationException;
+
+
+
+ CompareResult setMatchedDN(String dn)
+ throws UnsupportedOperationException;
+
+
+
+ CompareResult setResultCode(ResultCode resultCode)
+ throws UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/CompareResultImpl.java b/sdk/src/org/opends/sdk/responses/CompareResultImpl.java
new file mode 100644
index 0000000..14baaa5
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/CompareResultImpl.java
@@ -0,0 +1,97 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * compare the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.ResultCode;
+
+
+
+/**
+ * Compare result implementation.
+ */
+final class CompareResultImpl extends AbstractResultImpl<CompareResult>
+ implements CompareResult
+{
+
+ /**
+ * Creates a new compare result using the provided result code.
+ *
+ * @param resultCode
+ * The result code.
+ * @throws NullPointerException
+ * If {@code resultCode} was {@code null}.
+ */
+ CompareResultImpl(ResultCode resultCode) throws NullPointerException
+ {
+ super(resultCode);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean matched()
+ {
+ final ResultCode code = getResultCode();
+ return code.equals(ResultCode.COMPARE_TRUE);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("CompareResult(resultCode=");
+ builder.append(getResultCode());
+ builder.append(", matchedDN=");
+ builder.append(getMatchedDN());
+ builder.append(", diagnosticMessage=");
+ builder.append(getDiagnosticMessage());
+ builder.append(", referrals=");
+ builder.append(getReferralURIs());
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+
+
+
+ CompareResult getThis()
+ {
+ return this;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/ExtendedResult.java b/sdk/src/org/opends/sdk/responses/ExtendedResult.java
new file mode 100644
index 0000000..61ecc35
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/ExtendedResult.java
@@ -0,0 +1,172 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * A Extended result indicates the status of an Extended operation and
+ * any additional information associated with the Extended operation,
+ * including the optional response name and value. These can be
+ * retrieved using the {@link #getResponseName} and
+ * {@link #getResponseValue} methods respectively.
+ */
+public interface ExtendedResult extends Result
+{
+ /**
+ * {@inheritDoc}
+ */
+ ExtendedResult addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ ExtendedResult addReferralURI(String uri)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ ExtendedResult clearControls() throws UnsupportedOperationException;
+
+
+
+ ExtendedResult clearReferralURIs()
+ throws UnsupportedOperationException;
+
+
+
+ Throwable getCause();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Control getControl(String oid) throws NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Iterable<Control> getControls();
+
+
+
+ String getDiagnosticMessage();
+
+
+
+ String getMatchedDN();
+
+
+
+ Iterable<String> getReferralURIs();
+
+
+
+ /**
+ * Returns the dotted-decimal representation of the unique OID
+ * corresponding to this extended result.
+ *
+ * @return The dotted-decimal representation of the unique OID, or
+ * {@code null} if none was provided.
+ */
+ String getResponseName();
+
+
+
+ /**
+ * Returns the content of this extended result in a form defined by
+ * the extended result.
+ *
+ * @return The content of this extended result, or {@code null} if
+ * there is no content.
+ */
+ ByteString getResponseValue();
+
+
+
+ ResultCode getResultCode();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ boolean hasControls();
+
+
+
+ boolean hasReferralURIs();
+
+
+
+ boolean isReferral();
+
+
+
+ boolean isSuccess();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ ExtendedResult setCause(Throwable cause)
+ throws UnsupportedOperationException;
+
+
+
+ ExtendedResult setDiagnosticMessage(String message)
+ throws UnsupportedOperationException;
+
+
+
+ ExtendedResult setMatchedDN(String dn)
+ throws UnsupportedOperationException;
+
+
+
+ ExtendedResult setResultCode(ResultCode resultCode)
+ throws UnsupportedOperationException, NullPointerException;
+}
diff --git a/sdk/src/org/opends/sdk/responses/GenericExtendedResult.java b/sdk/src/org/opends/sdk/responses/GenericExtendedResult.java
new file mode 100644
index 0000000..6e2090b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/GenericExtendedResult.java
@@ -0,0 +1,198 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * generic extended the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * A Generic Extended result indicates the final status of an Generic
+ * Extended operation.
+ */
+public interface GenericExtendedResult extends ExtendedResult
+{
+ /**
+ * {@inheritDoc}
+ */
+ GenericExtendedResult addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ GenericExtendedResult addReferralURI(String uri)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ GenericExtendedResult clearControls()
+ throws UnsupportedOperationException;
+
+
+
+ GenericExtendedResult clearReferralURIs()
+ throws UnsupportedOperationException;
+
+
+
+ Throwable getCause();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Control getControl(String oid) throws NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Iterable<Control> getControls();
+
+
+
+ String getDiagnosticMessage();
+
+
+
+ String getMatchedDN();
+
+
+
+ Iterable<String> getReferralURIs();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ String getResponseName();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ ByteString getResponseValue();
+
+
+
+ ResultCode getResultCode();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ boolean hasControls();
+
+
+
+ boolean hasReferralURIs();
+
+
+
+ boolean isReferral();
+
+
+
+ boolean isSuccess();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ GenericExtendedResult setCause(Throwable cause)
+ throws UnsupportedOperationException;
+
+
+
+ GenericExtendedResult setDiagnosticMessage(String message)
+ throws UnsupportedOperationException;
+
+
+
+ GenericExtendedResult setMatchedDN(String dn)
+ throws UnsupportedOperationException;
+
+
+
+ /**
+ * Sets the dotted-decimal representation of the unique OID
+ * corresponding to this generic extended result.
+ *
+ * @param oid
+ * The dotted-decimal representation of the unique OID, or
+ * {@code null} if there is no response name.
+ * @return This generic extended result.
+ * @throws UnsupportedOperationException
+ * If this generic extended result does not permit the
+ * response name to be set.
+ */
+ GenericExtendedResult setResponseName(String oid)
+ throws UnsupportedOperationException;
+
+
+
+ /**
+ * Sets the content of this generic extended result in a form defined
+ * by the extended result.
+ *
+ * @param bytes
+ * The content of this generic extended result in a form
+ * defined by the extended result, or {@code null} if there
+ * is no content.
+ * @return This generic extended result.
+ * @throws UnsupportedOperationException
+ * If this generic extended result does not permit the
+ * response value to be set.
+ */
+ GenericExtendedResult setResponseValue(ByteString bytes)
+ throws UnsupportedOperationException;
+
+
+
+ GenericExtendedResult setResultCode(ResultCode resultCode)
+ throws UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/GenericExtendedResultImpl.java b/sdk/src/org/opends/sdk/responses/GenericExtendedResultImpl.java
new file mode 100644
index 0000000..98abda5
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/GenericExtendedResultImpl.java
@@ -0,0 +1,144 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * generic extended the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * Generic extended result implementation.
+ */
+final class GenericExtendedResultImpl extends
+ AbstractResultImpl<GenericExtendedResult> implements
+ ExtendedResult, GenericExtendedResult
+{
+
+ private String responseName = null;
+
+ private ByteString responseValue = null;
+
+
+
+ /**
+ * Creates a new generic extended result using the provided result
+ * code.
+ *
+ * @param resultCode
+ * The result code.
+ * @throws NullPointerException
+ * If {@code resultCode} was {@code null}.
+ */
+ GenericExtendedResultImpl(ResultCode resultCode)
+ throws NullPointerException
+ {
+ super(resultCode);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getResponseName()
+ {
+ return responseName;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteString getResponseValue()
+ {
+ return responseValue;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public GenericExtendedResult setResponseName(String oid)
+ throws UnsupportedOperationException
+ {
+ this.responseName = oid;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public GenericExtendedResult setResponseValue(ByteString bytes)
+ throws UnsupportedOperationException
+ {
+ this.responseValue = bytes;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("ExtendedResult(resultCode=");
+ builder.append(getResultCode());
+ builder.append(", matchedDN=");
+ builder.append(getMatchedDN());
+ builder.append(", diagnosticMessage=");
+ builder.append(getDiagnosticMessage());
+ builder.append(", referrals=");
+ builder.append(getReferralURIs());
+ builder.append(", responseName=");
+ builder.append(getResponseName() == null ? "" : getResponseName());
+ builder.append(", responseValue=");
+ final ByteString value = getResponseValue();
+ builder.append(value == null ? ByteString.empty() : value);
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+
+
+
+ GenericExtendedResult getThis()
+ {
+ return this;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/responses/GenericIntermediateResponse.java b/sdk/src/org/opends/sdk/responses/GenericIntermediateResponse.java
new file mode 100644
index 0000000..9ec733e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/GenericIntermediateResponse.java
@@ -0,0 +1,137 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * generic extended the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * A Generic Intermediate response provides a mechanism for
+ * communicating unrecognized or unsupported Intermediate responses to
+ * the client.
+ */
+public interface GenericIntermediateResponse extends
+ IntermediateResponse
+{
+ /**
+ * {@inheritDoc}
+ */
+ GenericIntermediateResponse addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ GenericIntermediateResponse clearControls()
+ throws UnsupportedOperationException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Control getControl(String oid) throws NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Iterable<Control> getControls();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ String getResponseName();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ ByteString getResponseValue();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ boolean hasControls();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the dotted-decimal representation of the unique OID
+ * corresponding to this generic intermediate response.
+ *
+ * @param oid
+ * The dotted-decimal representation of the unique OID, or
+ * {@code null} if there is no response name.
+ * @return This generic intermediate response.
+ * @throws UnsupportedOperationException
+ * If this generic intermediate response does not permit the
+ * response name to be set.
+ */
+ GenericIntermediateResponse setResponseName(String oid)
+ throws UnsupportedOperationException;
+
+
+
+ /**
+ * Sets the content of this generic intermediate response in a form
+ * defined by the extended result.
+ *
+ * @param bytes
+ * The content of this generic intermediate response in a
+ * form defined by the intermediate response, or {@code null}
+ * if there is no content.
+ * @return This generic intermediate response.
+ * @throws UnsupportedOperationException
+ * If this generic intermediate response does not permit the
+ * response value to be set.
+ */
+ GenericIntermediateResponse setResponseValue(ByteString bytes)
+ throws UnsupportedOperationException;
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/GenericIntermediateResponseImpl.java b/sdk/src/org/opends/sdk/responses/GenericIntermediateResponseImpl.java
new file mode 100644
index 0000000..c5a5bf7
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/GenericIntermediateResponseImpl.java
@@ -0,0 +1,133 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * generic extended the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * Generic intermediate response implementation.
+ */
+final class GenericIntermediateResponseImpl extends
+ AbstractIntermediateResponse<GenericIntermediateResponse> implements
+ GenericIntermediateResponse
+{
+
+ private String responseName = null;
+
+ private ByteString responseValue = null;
+
+
+
+ /**
+ * Creates a new generic intermediate response using the provided
+ * response name and value.
+ *
+ * @param responseName
+ * The dotted-decimal representation of the unique OID
+ * corresponding to this intermediate response, which may be
+ * {@code null} indicating that none was provided.
+ * @param responseValue
+ * The response value associated with this generic
+ * intermediate response, which may be {@code null}
+ * indicating that none was provided.
+ */
+ GenericIntermediateResponseImpl(String responseName,
+ ByteString responseValue)
+ {
+ this.responseName = responseName;
+ this.responseValue = responseValue;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getResponseName()
+ {
+ return responseName;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteString getResponseValue()
+ {
+ return responseValue;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public GenericIntermediateResponse setResponseName(String oid)
+ throws UnsupportedOperationException
+ {
+ this.responseName = oid;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public GenericIntermediateResponse setResponseValue(ByteString bytes)
+ throws UnsupportedOperationException
+ {
+ this.responseValue = bytes;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("IntermediateResponse(responseName=");
+ builder.append(getResponseName() == null ? "" : getResponseName());
+ builder.append(", responseValue=");
+ final ByteString value = getResponseValue();
+ builder.append(value == null ? ByteString.empty() : value);
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/responses/IntermediateResponse.java b/sdk/src/org/opends/sdk/responses/IntermediateResponse.java
new file mode 100644
index 0000000..51d83e9
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/IntermediateResponse.java
@@ -0,0 +1,116 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * An Intermediate response provides a general mechanism for defining
+ * single-request/multiple-response operations. This response is
+ * intended to be used in conjunction with the Extended operation to
+ * define new single-request/multiple-response operations or in
+ * conjunction with a control when extending existing operations in a
+ * way that requires them to return Intermediate response information.
+ * <p>
+ * An Intermediate response may convey an optional response name and
+ * value. These can be retrieved using the {@link #getResponseName} and
+ * {@link #getResponseValue} methods respectively.
+ */
+public interface IntermediateResponse extends Response
+{
+ /**
+ * {@inheritDoc}
+ */
+ IntermediateResponse addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ IntermediateResponse clearControls()
+ throws UnsupportedOperationException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Control getControl(String oid) throws NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Iterable<Control> getControls();
+
+
+
+ /**
+ * Returns the dotted-decimal representation of the unique OID
+ * corresponding to this intermediate response.
+ *
+ * @return The dotted-decimal representation of the unique OID, or
+ * {@code null} if none was provided.
+ */
+ String getResponseName();
+
+
+
+ /**
+ * Returns the content of this intermediate response in a form defined
+ * by the intermediate response.
+ *
+ * @return The content of this intermediate response, or {@code null}
+ * if there is no content.
+ */
+ ByteString getResponseValue();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ boolean hasControls();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/Response.java b/sdk/src/org/opends/sdk/responses/Response.java
new file mode 100644
index 0000000..940fc3b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/Response.java
@@ -0,0 +1,124 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.controls.Control;
+
+
+
+/**
+ * The base class of all Responses provides methods for querying and
+ * manipulating the set of Controls included with a Response.
+ * <p>
+ * TODO: added complete description including sub-types.
+ */
+public interface Response
+{
+
+ /**
+ * Adds the provided control to this response.
+ *
+ * @param control
+ * The control to be added.
+ * @return This response.
+ * @throws UnsupportedOperationException
+ * If this response does not permit controls to be added.
+ * @throws NullPointerException
+ * If {@code control} was {@code null}.
+ */
+ Response addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Removes all the controls included with this response.
+ *
+ * @return This response.
+ * @throws UnsupportedOperationException
+ * If this response does not permit controls to be removed.
+ */
+ Response clearControls() throws UnsupportedOperationException;
+
+
+
+ /**
+ * Returns the first control contained in this response having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be returned.
+ * @return The control, or {@code null} if the control is not included
+ * with this response.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control getControl(String oid) throws NullPointerException;
+
+
+
+ /**
+ * Returns an {@code Iterable} containing the controls included with
+ * this response. The returned {@code Iterable} may be used to remove
+ * controls if permitted by this response.
+ *
+ * @return An {@code Iterable} containing the controls.
+ */
+ Iterable<Control> getControls();
+
+
+
+ /**
+ * Indicates whether or not this response has any controls.
+ *
+ * @return {@code true} if this response has any controls, otherwise
+ * {@code false}.
+ */
+ boolean hasControls();
+
+
+
+ /**
+ * Removes the first control contained in this response having the
+ * specified OID.
+ *
+ * @param oid
+ * The OID of the control to be removed.
+ * @return The removed control, or {@code null} if the control is not
+ * included with this response.
+ * @throws UnsupportedOperationException
+ * If this response does not permit controls to be removed.
+ * @throws NullPointerException
+ * If {@code oid} was {@code null}.
+ */
+ Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/Responses.java b/sdk/src/org/opends/sdk/responses/Responses.java
new file mode 100644
index 0000000..0ba85e0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/Responses.java
@@ -0,0 +1,278 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.DN;
+import org.opends.sdk.Entry;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.SortedEntry;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class contains various methods for creating and manipulating
+ * responses.
+ * <p>
+ * TODO: search reference from LDAP URL.
+ * <p>
+ * TODO: referral from LDAP URL.
+ * <p>
+ * TODO: unmodifiable requests?
+ * <p>
+ * TODO: synchronized requests?
+ * <p>
+ * TODO: copy constructors.
+ */
+public final class Responses
+{
+
+ /**
+ * Creates a new bind result using the provided result code.
+ *
+ * @param resultCode
+ * The result code.
+ * @return The new bind result.
+ * @throws NullPointerException
+ * If {@code resultCode} was {@code null}.
+ */
+ public static BindResult newBindResult(ResultCode resultCode)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(resultCode);
+ return new BindResultImpl(resultCode);
+ }
+
+
+
+ /**
+ * Creates a new compare result using the provided result code.
+ *
+ * @param resultCode
+ * The result code.
+ * @return The new compare result.
+ * @throws NullPointerException
+ * If {@code resultCode} was {@code null}.
+ */
+ public static CompareResult newCompareResult(ResultCode resultCode)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(resultCode);
+ return new CompareResultImpl(resultCode);
+ }
+
+
+
+ /**
+ * Creates a new generic extended result using the provided result
+ * code.
+ *
+ * @param resultCode
+ * The result code.
+ * @return The new generic extended result.
+ * @throws NullPointerException
+ * If {@code resultCode} was {@code null}.
+ */
+ public static GenericExtendedResult newGenericExtendedResult(
+ ResultCode resultCode) throws NullPointerException
+ {
+ Validator.ensureNotNull(resultCode);
+ return new GenericExtendedResultImpl(resultCode);
+ }
+
+
+
+ /**
+ * Creates a new generic intermediate response with no name or value.
+ *
+ * @return The new generic intermediate response.
+ */
+ public static GenericIntermediateResponse newGenericIntermediateResponse()
+ {
+ return new GenericIntermediateResponseImpl(null, null);
+ }
+
+
+
+ /**
+ * Creates a new generic intermediate response using the provided
+ * response name and value.
+ *
+ * @param responseName
+ * The dotted-decimal representation of the unique OID
+ * corresponding to this intermediate response, which may be
+ * {@code null} indicating that none was provided.
+ * @param responseValue
+ * The response value associated with this generic
+ * intermediate response, which may be {@code null}
+ * indicating that none was provided.
+ * @return The new generic intermediate response.
+ */
+ public static GenericIntermediateResponse newGenericIntermediateResponse(
+ String responseName, ByteString responseValue)
+ {
+ return new GenericIntermediateResponseImpl(responseName,
+ responseValue);
+ }
+
+
+
+ /**
+ * Creates a new result using the provided result code.
+ *
+ * @param resultCode
+ * The result code.
+ * @return The new result.
+ * @throws NullPointerException
+ * If {@code resultCode} was {@code null}.
+ */
+ public static Result newResult(ResultCode resultCode)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(resultCode);
+ return new ResultImpl(resultCode);
+ }
+
+
+
+ /**
+ * Creates a new search result entry using the provided distinguished
+ * name.
+ *
+ * @param name
+ * The distinguished name of the entry.
+ * @return The new search result entry.
+ * @throws NullPointerException
+ * If {@code name} was {@code null}.
+ */
+ public static SearchResultEntry newSearchResultEntry(DN name)
+ throws NullPointerException
+ {
+ final Entry entry = new SortedEntry().setName(name);
+ return new SearchResultEntryImpl(entry);
+ }
+
+
+
+ /**
+ * Creates a new search result entry backed by the provided entry.
+ * Modifications made to {@code entry} will be reflected in the
+ * returned search result entry. The returned search result entry
+ * supports updates to its list of controls, as well as updates to the
+ * name and attributes if the underlying entry allows.
+ *
+ * @param entry
+ * The entry.
+ * @return The new search result entry.
+ * @throws NullPointerException
+ * If {@code entry} was {@code null} .
+ */
+ public static SearchResultEntry newSearchResultEntry(Entry entry)
+ throws NullPointerException
+ {
+ Validator.ensureNotNull(entry);
+ return new SearchResultEntryImpl(entry);
+ }
+
+
+
+ /**
+ * Creates a new search result entry using the provided distinguished
+ * name decoded using the default schema.
+ *
+ * @param name
+ * The distinguished name of the entry.
+ * @return The new search result entry.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code name} could not be decoded using the default
+ * schema.
+ * @throws NullPointerException
+ * If {@code name} was {@code null}.
+ */
+ public static SearchResultEntry newSearchResultEntry(String name)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ final Entry entry = new SortedEntry().setName(name);
+ return new SearchResultEntryImpl(entry);
+ }
+
+
+
+ /**
+ * Creates a new search result entry using the provided lines of LDIF
+ * decoded using the default schema.
+ *
+ * @param ldifLines
+ * Lines of LDIF containing an LDIF add change record or an
+ * LDIF entry record.
+ * @return The new search result entry.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code ldifLines} was empty, or contained invalid
+ * LDIF, or could not be decoded using the default schema.
+ * @throws NullPointerException
+ * If {@code ldifLines} was {@code null} .
+ */
+ public static SearchResultEntry newSearchResultEntry(
+ String... ldifLines) throws LocalizedIllegalArgumentException,
+ NullPointerException
+ {
+ return newSearchResultEntry(new SortedEntry(ldifLines));
+ }
+
+
+
+ /**
+ * Creates a new search result reference using the provided
+ * continuation reference URI.
+ *
+ * @param uri
+ * The first continuation reference URI to be added to this
+ * search result reference.
+ * @return The new search result reference.
+ * @throws NullPointerException
+ * If {@code uri} was {@code null}.
+ */
+ public static SearchResultReference newSearchResultReference(
+ String uri) throws NullPointerException
+ {
+ Validator.ensureNotNull(uri);
+ return new SearchResultReferenceImpl(uri);
+ }
+
+
+
+ // Private constructor.
+ private Responses()
+ {
+ // Prevent instantiation.
+ }
+}
diff --git a/sdk/src/org/opends/sdk/responses/Result.java b/sdk/src/org/opends/sdk/responses/Result.java
new file mode 100644
index 0000000..ad339e2
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/Result.java
@@ -0,0 +1,279 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.controls.Control;
+
+
+
+/**
+ * A Result is used to indicate the status of an operation performed by
+ * the server. A Result is comprised of several fields:
+ * <ul>
+ * <li>The <b>result code</b> can be retrieved using the method
+ * {@link #getResultCode}. This indicates the overall outcome of the
+ * operation. In particular, whether or not it succeeded which is
+ * indicated using a value of {@link ResultCode#SUCCESS}.
+ * <li>The optional <b>diagnostic message</b> can be retrieved using the
+ * method {@link #getDiagnosticMessage}. At the server's discretion, a
+ * diagnostic message may be included in a Result in order to supplement
+ * the result code with additional human-readable information.
+ * <li>The optional <b>matched DN</b> can be retrieved using the method
+ * {@link #getMatchedDN}. For certain result codes, this is used to
+ * indicate to the client the last entry used in finding the Request's
+ * target (or base) entry.
+ * <li>The optional <b>referrals</b> can be retrieved using the method
+ * {@link #getReferralURIs}. Referrals are present in a Result if the
+ * result code is set to {@link ResultCode#REFERRAL}, and it are absent
+ * with all other result codes.
+ * </ul>
+ */
+public interface Result extends Response
+{
+ /**
+ * {@inheritDoc}
+ */
+ Result addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Adds the provided referral URI to this result.
+ *
+ * @param uri
+ * The referral URI to be added.
+ * @return This result.
+ * @throws UnsupportedOperationException
+ * If this result does not permit referrals to be added.
+ * @throws NullPointerException
+ * If {@code uri} was {@code null}.
+ */
+ Result addReferralURI(String uri)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Result clearControls() throws UnsupportedOperationException;
+
+
+
+ /**
+ * Removes all the referral URIs included with this result.
+ *
+ * @return This result.
+ * @throws UnsupportedOperationException
+ * If this result does not permit referral URIs to be
+ * removed.
+ */
+ Result clearReferralURIs() throws UnsupportedOperationException;
+
+
+
+ /**
+ * Returns the throwable cause associated with this result if
+ * available. A cause may be provided in cases where a result
+ * indicates a failure due to a client-side error.
+ *
+ * @return The throwable cause, or {@code null} if none was provided.
+ */
+ Throwable getCause();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Control getControl(String oid) throws NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Iterable<Control> getControls();
+
+
+
+ /**
+ * Returns the diagnostic message associated with this result.
+ *
+ * @return The diagnostic message, which may be empty if none was
+ * provided (never {@code null}).
+ */
+ String getDiagnosticMessage();
+
+
+
+ /**
+ * Returns the matched DN associated with this result.
+ *
+ * @return The matched DN, which may be empty if none was provided
+ * (never {@code null}).
+ */
+ String getMatchedDN();
+
+
+
+ /**
+ * Returns an {@code Iterable} containing the referral URIs included
+ * with this result. The returned {@code Iterable} may be used to
+ * remove referral URIs if permitted by this result.
+ *
+ * @return An {@code Iterable} containing the referral URIs.
+ */
+ Iterable<String> getReferralURIs();
+
+
+
+ /**
+ * Returns the result code associated with this result.
+ *
+ * @return The result code.
+ */
+ ResultCode getResultCode();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ boolean hasControls();
+
+
+
+ /**
+ * Indicates whether or not this result has any referral URIs.
+ *
+ * @return {@code true} if this result has any referral URIs,
+ * otherwise {@code false}.
+ */
+ boolean hasReferralURIs();
+
+
+
+ /**
+ * Indicates whether or not a referral needs to be chased in order to
+ * complete the operation.
+ * <p>
+ * Specifically, this method returns {@code true} if the result code
+ * is equal to {@link ResultCode#REFERRAL}.
+ *
+ * @return {@code true} if a referral needs to be chased, otherwise
+ * {@code false}.
+ */
+ boolean isReferral();
+
+
+
+ /**
+ * Indicates whether or not the request succeeded or not. This method
+ * will return {code true} for all non-error responses.
+ *
+ * @return {@code true} if the request succeeded, otherwise {@code
+ * false}.
+ */
+ boolean isSuccess();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Sets the throwable cause associated with this result if available.
+ * A cause may be provided in cases where a result indicates a failure
+ * due to a client-side error.
+ *
+ * @param cause
+ * The throwable cause, which may be {@code null} indicating
+ * that none was provided.
+ * @return This result.
+ * @throws UnsupportedOperationException
+ * If this result does not permit the cause to be set.
+ */
+ Result setCause(Throwable cause) throws UnsupportedOperationException;
+
+
+
+ /**
+ * Sets the diagnostic message associated with this result.
+ *
+ * @param message
+ * The diagnostic message, which may be empty or {@code null}
+ * indicating that none was provided.
+ * @return This result.
+ * @throws UnsupportedOperationException
+ * If this result does not permit the diagnostic message to
+ * be set.
+ */
+ Result setDiagnosticMessage(String message)
+ throws UnsupportedOperationException;
+
+
+
+ /**
+ * Sets the matched DN associated with this result.
+ *
+ * @param dn
+ * The matched DN associated, which may be empty or {@code
+ * null} indicating that none was provided.
+ * @return This result.
+ * @throws UnsupportedOperationException
+ * If this result does not permit the matched DN to be set.
+ */
+ Result setMatchedDN(String dn) throws UnsupportedOperationException;
+
+
+
+ /**
+ * Sets the result code associated with this result.
+ *
+ * @param resultCode
+ * The result code.
+ * @return This result.
+ * @throws UnsupportedOperationException
+ * If this result does not permit the result code to be set.
+ * @throws NullPointerException
+ * If {@code resultCode} was {@code null}.
+ */
+ Result setResultCode(ResultCode resultCode)
+ throws UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/ResultImpl.java b/sdk/src/org/opends/sdk/responses/ResultImpl.java
new file mode 100644
index 0000000..c050541
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/ResultImpl.java
@@ -0,0 +1,86 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.ResultCode;
+
+
+
+/**
+ * A generic result indicates the final status of an operation.
+ */
+final class ResultImpl extends AbstractResultImpl<Result> implements
+ Result
+{
+
+ /**
+ * Creates a new generic result using the provided result code.
+ *
+ * @param resultCode
+ * The result code.
+ * @throws NullPointerException
+ * If {@code resultCode} was {@code null}.
+ */
+ ResultImpl(ResultCode resultCode) throws NullPointerException
+ {
+ super(resultCode);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Result(resultCode=");
+ builder.append(getResultCode());
+ builder.append(", matchedDN=");
+ builder.append(getMatchedDN());
+ builder.append(", diagnosticMessage=");
+ builder.append(getDiagnosticMessage());
+ builder.append(", referrals=");
+ builder.append(getReferralURIs());
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+
+
+
+ Result getThis()
+ {
+ return this;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/SearchResultEntry.java b/sdk/src/org/opends/sdk/responses/SearchResultEntry.java
new file mode 100644
index 0000000..fb5e755
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/SearchResultEntry.java
@@ -0,0 +1,230 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import java.util.Collection;
+
+import org.opends.sdk.Attribute;
+import org.opends.sdk.AttributeDescription;
+import org.opends.sdk.DN;
+import org.opends.sdk.Entry;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.schema.ObjectClass;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * A Search Result Entry represents an entry found during a Search
+ * operation.
+ * <p>
+ * Each entry returned in a Search Result Entry will contain all
+ * appropriate attributes as specified in the Search request, subject to
+ * access control and other administrative policy.
+ * <p>
+ * Note that a Search Result Entry may hold zero attributes. This may
+ * happen when none of the attributes of an entry were requested or
+ * could be returned.
+ * <p>
+ * Note also that each returned attribute may hold zero attribute
+ * values. This may happen when only attribute types are requested,
+ * access controls prevent the return of values, or other reasons.
+ */
+public interface SearchResultEntry extends Response, Entry
+{
+ boolean addAttribute(Attribute attribute)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ boolean addAttribute(Attribute attribute,
+ Collection<ByteString> duplicateValues)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ SearchResultEntry addAttribute(String attributeDescription,
+ Object... values) throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ SearchResultEntry addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ SearchResultEntry clearAttributes()
+ throws UnsupportedOperationException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ SearchResultEntry clearControls()
+ throws UnsupportedOperationException;
+
+
+
+ boolean containsAttribute(AttributeDescription attributeDescription)
+ throws NullPointerException;
+
+
+
+ boolean containsAttribute(String attributeDescription)
+ throws LocalizedIllegalArgumentException, NullPointerException;
+
+
+
+ boolean containsObjectClass(ObjectClass objectClass)
+ throws NullPointerException;
+
+
+
+ boolean containsObjectClass(String objectClass)
+ throws NullPointerException;
+
+
+
+ Iterable<Attribute> findAttributes(
+ AttributeDescription attributeDescription)
+ throws NullPointerException;
+
+
+
+ Iterable<Attribute> findAttributes(String attributeDescription)
+ throws LocalizedIllegalArgumentException, NullPointerException;
+
+
+
+ Attribute getAttribute(AttributeDescription attributeDescription)
+ throws NullPointerException;
+
+
+
+ Attribute getAttribute(String attributeDescription)
+ throws LocalizedIllegalArgumentException, NullPointerException;
+
+
+
+ int getAttributeCount();
+
+
+
+ Iterable<Attribute> getAttributes();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Control getControl(String oid) throws NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Iterable<Control> getControls();
+
+
+
+ DN getName();
+
+
+
+ Iterable<String> getObjectClasses();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ boolean hasControls();
+
+
+
+ boolean removeAttribute(Attribute attribute,
+ Collection<ByteString> missingValues)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ boolean removeAttribute(AttributeDescription attributeDescription)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ SearchResultEntry removeAttribute(String attributeDescription)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException;
+
+
+
+ SearchResultEntry removeAttribute(String attributeDescription,
+ Object... values) throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ boolean replaceAttribute(Attribute attribute)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ SearchResultEntry replaceAttribute(String attributeDescription,
+ Object... values) throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException;
+
+
+
+ SearchResultEntry setName(DN dn)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ SearchResultEntry setName(String dn)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/SearchResultEntryImpl.java b/sdk/src/org/opends/sdk/responses/SearchResultEntryImpl.java
new file mode 100644
index 0000000..6125343
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/SearchResultEntryImpl.java
@@ -0,0 +1,377 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import java.util.Collection;
+
+import org.opends.sdk.Attribute;
+import org.opends.sdk.AttributeDescription;
+import org.opends.sdk.DN;
+import org.opends.sdk.Entry;
+import org.opends.sdk.schema.ObjectClass;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * Search result entry implementation.
+ */
+final class SearchResultEntryImpl extends
+ AbstractResponseImpl<SearchResultEntry> implements
+ SearchResultEntry
+{
+
+ private final Entry entry;
+
+
+
+ /**
+ * Creates a new search result entry backed by the provided entry.
+ * Modifications made to {@code entry} will be reflected in the
+ * returned search result entry. The returned search result entry
+ * supports updates to its list of controls, as well as updates to the
+ * name and attributes if the underlying entry allows.
+ *
+ * @param entry
+ * The entry.
+ * @throws NullPointerException
+ * If {@code entry} was {@code null} .
+ */
+ public SearchResultEntryImpl(Entry entry) throws NullPointerException
+ {
+ this.entry = entry;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean addAttribute(Attribute attribute)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ return entry.addAttribute(attribute);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean addAttribute(Attribute attribute,
+ Collection<ByteString> duplicateValues)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ return entry.addAttribute(attribute, duplicateValues);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SearchResultEntry addAttribute(String attributeDescription,
+ Object... values) throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ entry.addAttribute(attributeDescription, values);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SearchResultEntry clearAttributes()
+ throws UnsupportedOperationException
+ {
+ entry.clearAttributes();
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean containsAttribute(
+ AttributeDescription attributeDescription)
+ throws NullPointerException
+ {
+ return entry.containsAttribute(attributeDescription);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean containsAttribute(String attributeDescription)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ return entry.containsAttribute(attributeDescription);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean containsObjectClass(ObjectClass objectClass)
+ throws NullPointerException
+ {
+ return entry.containsObjectClass(objectClass);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean containsObjectClass(String objectClass)
+ throws NullPointerException
+ {
+ return entry.containsObjectClass(objectClass);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterable<Attribute> findAttributes(
+ AttributeDescription attributeDescription)
+ throws NullPointerException
+ {
+ return entry.findAttributes(attributeDescription);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterable<Attribute> findAttributes(String attributeDescription)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ return entry.findAttributes(attributeDescription);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Attribute getAttribute(
+ AttributeDescription attributeDescription)
+ throws NullPointerException
+ {
+ return entry.getAttribute(attributeDescription);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Attribute getAttribute(String attributeDescription)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ return entry.getAttribute(attributeDescription);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int getAttributeCount()
+ {
+ return entry.getAttributeCount();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterable<Attribute> getAttributes()
+ {
+ return entry.getAttributes();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public DN getName()
+ {
+ return entry.getName();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterable<String> getObjectClasses()
+ {
+ return entry.getObjectClasses();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean removeAttribute(Attribute attribute,
+ Collection<ByteString> missingValues)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ return entry.removeAttribute(attribute, missingValues);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean removeAttribute(
+ AttributeDescription attributeDescription)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ return entry.removeAttribute(attributeDescription);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SearchResultEntry removeAttribute(String attributeDescription)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ entry.removeAttribute(attributeDescription);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SearchResultEntry removeAttribute(String attributeDescription,
+ Object... values) throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ entry.removeAttribute(attributeDescription, values);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean replaceAttribute(Attribute attribute)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ return entry.replaceAttribute(attribute);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SearchResultEntry replaceAttribute(
+ String attributeDescription, Object... values)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ entry.replaceAttribute(attributeDescription, values);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SearchResultEntry setName(DN dn)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ entry.setName(dn);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SearchResultEntry setName(String dn)
+ throws LocalizedIllegalArgumentException,
+ UnsupportedOperationException, NullPointerException
+ {
+ entry.setName(dn);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("SearchResultEntry(name=");
+ builder.append(getName());
+ builder.append(", attributes=");
+ builder.append(getAttributes());
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+
+
+
+ SearchResultEntry getThis()
+ {
+ return this;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/responses/SearchResultReference.java b/sdk/src/org/opends/sdk/responses/SearchResultReference.java
new file mode 100644
index 0000000..34cefbb
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/SearchResultReference.java
@@ -0,0 +1,151 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.controls.Control;
+
+
+
+/**
+ * A Search Result Reference represents an area not yet explored during
+ * a Search operation.
+ */
+public interface SearchResultReference extends Response
+{
+ /**
+ * {@inheritDoc}
+ */
+ SearchResultReference addControl(Control control)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * Adds the provided continuation reference URI to this search result
+ * reference.
+ *
+ * @param uri
+ * The continuation reference URI to be added.
+ * @return This search result reference.
+ * @throws UnsupportedOperationException
+ * If this search result reference does not permit
+ * continuation reference URI to be added.
+ * @throws NullPointerException
+ * If {@code uri} was {@code null}.
+ */
+ SearchResultReference addURI(String uri)
+ throws UnsupportedOperationException, NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ SearchResultReference clearControls()
+ throws UnsupportedOperationException;
+
+
+
+ /**
+ * Removes all the continuation reference URIs included with this
+ * search result reference.
+ *
+ * @return This search result reference.
+ * @throws UnsupportedOperationException
+ * If this search result reference does not permit
+ * continuation reference URIs to be removed.
+ */
+ SearchResultReference clearURIs()
+ throws UnsupportedOperationException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Control getControl(String oid) throws NullPointerException;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Iterable<Control> getControls();
+
+
+
+ /**
+ * Returns the number of continuation reference URIs in this search
+ * result reference.
+ *
+ * @return The number of continuation reference URIs.
+ */
+ int getURICount();
+
+
+
+ /**
+ * Returns an {@code Iterable} containing the continuation reference
+ * URIs included with this search result reference. The returned
+ * {@code Iterable} may be used to remove continuation reference URIs
+ * if permitted by this search result reference.
+ *
+ * @return An {@code Iterable} containing the continuation reference
+ * URIs.
+ */
+ Iterable<String> getURIs();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ boolean hasControls();
+
+
+
+ /**
+ * Indicates whether or not this search result reference has any
+ * continuation reference URIs.
+ *
+ * @return {@code true} if this search result reference has any
+ * continuation reference URIs, otherwise {@code false}.
+ */
+ boolean hasURIs();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Control removeControl(String oid)
+ throws UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/SearchResultReferenceImpl.java b/sdk/src/org/opends/sdk/responses/SearchResultReferenceImpl.java
new file mode 100644
index 0000000..fe0edc4
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/SearchResultReferenceImpl.java
@@ -0,0 +1,145 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Search result reference implementation.
+ */
+final class SearchResultReferenceImpl extends
+ AbstractResponseImpl<SearchResultReference> implements
+ SearchResultReference
+{
+
+ private final List<String> uris = new LinkedList<String>();
+
+
+
+ /**
+ * Creates a new search result reference using the provided
+ * continuation reference URI.
+ *
+ * @param uri
+ * The first continuation reference URI to be added to this
+ * search result reference.
+ * @throws NullPointerException
+ * If {@code uri} was {@code null}.
+ */
+ SearchResultReferenceImpl(String uri) throws NullPointerException
+ {
+ addURI(uri);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SearchResultReference addURI(String uri)
+ throws UnsupportedOperationException, NullPointerException
+ {
+ Validator.ensureNotNull(uri);
+ uris.add(uri);
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public SearchResultReference clearURIs()
+ throws UnsupportedOperationException
+ {
+ uris.clear();
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int getURICount()
+ {
+ return uris.size();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterable<String> getURIs()
+ {
+ return uris;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean hasURIs()
+ {
+ return !uris.isEmpty();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("SearchResultReference(uris=");
+ builder.append(getURIs());
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+
+
+
+ SearchResultReference getThis()
+ {
+ return this;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/sasl/AbstractSASLContext.java b/sdk/src/org/opends/sdk/sasl/AbstractSASLContext.java
new file mode 100644
index 0000000..4b97a18
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/AbstractSASLContext.java
@@ -0,0 +1,248 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+
+
+import static org.opends.messages.ExtensionMessages.INFO_SASL_UNSUPPORTED_CALLBACK;
+
+import java.io.IOException;
+
+import javax.security.auth.callback.*;
+import javax.security.sasl.AuthorizeCallback;
+import javax.security.sasl.RealmCallback;
+import javax.security.sasl.RealmChoiceCallback;
+import javax.security.sasl.SaslException;
+
+
+
+/**
+ * An abstract SASL context.
+ */
+public abstract class AbstractSASLContext implements SASLContext,
+ CallbackHandler
+{
+ /**
+ * The name of the default protocol used.
+ */
+ protected static final String SASL_DEFAULT_PROTOCOL = "ldap";
+
+
+
+ public void handle(Callback[] callbacks) throws IOException,
+ UnsupportedCallbackException
+ {
+ for (Callback callback : callbacks)
+ {
+ if (callback instanceof NameCallback)
+ {
+ handle((NameCallback) callback);
+ }
+ else if (callback instanceof PasswordCallback)
+ {
+ handle((PasswordCallback) callback);
+ }
+ else if (callback instanceof AuthorizeCallback)
+ {
+ handle((AuthorizeCallback) callback);
+ }
+ else if (callback instanceof RealmCallback)
+ {
+ handle((RealmCallback) callback);
+ }
+ else if (callback instanceof RealmChoiceCallback)
+ {
+ handle((RealmChoiceCallback) callback);
+ }
+ else if (callback instanceof ChoiceCallback)
+ {
+ handle((ChoiceCallback) callback);
+ }
+ else if (callback instanceof ConfirmationCallback)
+ {
+ handle((ConfirmationCallback) callback);
+ }
+ else if (callback instanceof LanguageCallback)
+ {
+ handle((LanguageCallback) callback);
+ }
+ else if (callback instanceof TextInputCallback)
+ {
+ handle((TextInputCallback) callback);
+ }
+ else if (callback instanceof TextOutputCallback)
+ {
+ handle((TextOutputCallback) callback);
+ }
+ else
+ {
+ org.opends.messages.Message message =
+ INFO_SASL_UNSUPPORTED_CALLBACK.get(getSASLBindRequest()
+ .getSASLMechanism(), String.valueOf(callback));
+ throw new UnsupportedCallbackException(callback, message
+ .toString());
+ }
+ }
+ }
+
+
+
+ /**
+ * Default implementation just returns the copy of the bytes.
+ */
+ public byte[] unwrap(byte[] incoming, int offset, int len)
+ throws SaslException
+ {
+ byte[] copy = new byte[len];
+ System.arraycopy(incoming, offset, copy, 0, len);
+ return copy;
+ }
+
+
+
+ /**
+ * Default implemenation just returns the copy of the bytes.
+ */
+ public byte[] wrap(byte[] outgoing, int offset, int len)
+ throws SaslException
+ {
+ byte[] copy = new byte[len];
+ System.arraycopy(outgoing, offset, copy, 0, len);
+ return copy;
+ }
+
+
+
+ protected void handle(AuthorizeCallback callback)
+ throws UnsupportedCallbackException
+ {
+ org.opends.messages.Message message =
+ INFO_SASL_UNSUPPORTED_CALLBACK.get(getSASLBindRequest()
+ .getSASLMechanism(), String.valueOf(callback));
+ throw new UnsupportedCallbackException(callback, message.toString());
+ }
+
+
+
+ protected void handle(ChoiceCallback callback)
+ throws UnsupportedCallbackException
+ {
+ org.opends.messages.Message message =
+ INFO_SASL_UNSUPPORTED_CALLBACK.get(getSASLBindRequest()
+ .getSASLMechanism(), String.valueOf(callback));
+ throw new UnsupportedCallbackException(callback, message.toString());
+ }
+
+
+
+ protected void handle(ConfirmationCallback callback)
+ throws UnsupportedCallbackException
+ {
+ org.opends.messages.Message message =
+ INFO_SASL_UNSUPPORTED_CALLBACK.get(getSASLBindRequest()
+ .getSASLMechanism(), String.valueOf(callback));
+ throw new UnsupportedCallbackException(callback, message.toString());
+ }
+
+
+
+ protected void handle(LanguageCallback callback)
+ throws UnsupportedCallbackException
+ {
+ org.opends.messages.Message message =
+ INFO_SASL_UNSUPPORTED_CALLBACK.get(getSASLBindRequest()
+ .getSASLMechanism(), String.valueOf(callback));
+ throw new UnsupportedCallbackException(callback, message.toString());
+ }
+
+
+
+ protected void handle(NameCallback callback)
+ throws UnsupportedCallbackException
+ {
+ org.opends.messages.Message message =
+ INFO_SASL_UNSUPPORTED_CALLBACK.get(getSASLBindRequest()
+ .getSASLMechanism(), String.valueOf(callback));
+ throw new UnsupportedCallbackException(callback, message.toString());
+ }
+
+
+
+ protected void handle(PasswordCallback callback)
+ throws UnsupportedCallbackException
+ {
+ org.opends.messages.Message message =
+ INFO_SASL_UNSUPPORTED_CALLBACK.get(getSASLBindRequest()
+ .getSASLMechanism(), String.valueOf(callback));
+ throw new UnsupportedCallbackException(callback, message.toString());
+ }
+
+
+
+ protected void handle(RealmCallback callback)
+ throws UnsupportedCallbackException
+ {
+ org.opends.messages.Message message =
+ INFO_SASL_UNSUPPORTED_CALLBACK.get(getSASLBindRequest()
+ .getSASLMechanism(), String.valueOf(callback));
+ throw new UnsupportedCallbackException(callback, message.toString());
+ }
+
+
+
+ protected void handle(RealmChoiceCallback callback)
+ throws UnsupportedCallbackException
+ {
+ org.opends.messages.Message message =
+ INFO_SASL_UNSUPPORTED_CALLBACK.get(getSASLBindRequest()
+ .getSASLMechanism(), String.valueOf(callback));
+ throw new UnsupportedCallbackException(callback, message.toString());
+ }
+
+
+
+ protected void handle(TextInputCallback callback)
+ throws UnsupportedCallbackException
+ {
+ org.opends.messages.Message message =
+ INFO_SASL_UNSUPPORTED_CALLBACK.get(getSASLBindRequest()
+ .getSASLMechanism(), String.valueOf(callback));
+ throw new UnsupportedCallbackException(callback, message.toString());
+ }
+
+
+
+ protected void handle(TextOutputCallback callback)
+ throws UnsupportedCallbackException
+ {
+ org.opends.messages.Message message =
+ INFO_SASL_UNSUPPORTED_CALLBACK.get(getSASLBindRequest()
+ .getSASLMechanism(), String.valueOf(callback));
+ throw new UnsupportedCallbackException(callback, message.toString());
+ }
+}
diff --git a/sdk/src/org/opends/sdk/sasl/AnonymousSASLBindRequest.java b/sdk/src/org/opends/sdk/sasl/AnonymousSASLBindRequest.java
new file mode 100644
index 0000000..2688391
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/AnonymousSASLBindRequest.java
@@ -0,0 +1,161 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+
+import javax.security.sasl.SaslException;
+
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Anonymous SASL bind request.
+ */
+public final class AnonymousSASLBindRequest extends
+ SASLBindRequest<AnonymousSASLBindRequest> implements SASLContext
+{
+ /**
+ * The name of the SASL mechanism that does not provide any authentication but
+ * rather uses anonymous access.
+ */
+ public static final String SASL_MECHANISM_ANONYMOUS = "ANONYMOUS";
+
+ private String traceString;
+
+ public void dispose() throws SaslException
+ {
+ // Nothing needed.
+ }
+
+ public boolean evaluateCredentials(ByteString incomingCredentials)
+ throws SaslException
+ {
+ // This is a single stage SASL bind.
+ return true;
+ }
+
+
+ public boolean isComplete()
+ {
+ return true;
+ }
+
+ public boolean isSecure()
+ {
+ return false;
+ }
+
+ public ByteString getSASLCredentials()
+ {
+ return ByteString.valueOf(traceString);
+ }
+
+
+ public byte[] unwrap(byte[] incoming, int offset, int len)
+ throws SaslException
+ {
+ byte[] copy = new byte[len];
+ System.arraycopy(incoming, offset, copy, 0, len);
+ return copy;
+ }
+
+
+ public byte[] wrap(byte[] outgoing, int offset, int len)
+ throws SaslException
+ {
+ byte[] copy = new byte[len];
+ System.arraycopy(outgoing, offset, copy, 0, len);
+ return copy;
+ }
+
+ public AnonymousSASLBindRequest()
+ {
+ this.traceString = "".intern();
+ }
+
+
+
+ public AnonymousSASLBindRequest(String traceString)
+ {
+ Validator.ensureNotNull(traceString);
+ this.traceString = traceString;
+ }
+
+
+
+ public String getSASLMechanism()
+ {
+ return SASL_MECHANISM_ANONYMOUS;
+ }
+
+
+
+ public String getTraceString()
+ {
+ return traceString;
+ }
+
+
+
+ public SASLContext getClientContext(String serverName) throws SaslException
+ {
+ return this;
+ }
+
+ public AnonymousSASLBindRequest getSASLBindRequest() {
+ return this;
+ }
+
+ public AnonymousSASLBindRequest setTraceString(String traceString)
+ {
+ Validator.ensureNotNull(traceString);
+ this.traceString = traceString;
+ return this;
+ }
+
+
+
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append("AnonymousSASLBindRequest(bindDN=");
+ builder.append(getName());
+ builder.append(", authentication=SASL");
+ builder.append(", saslMechanism=");
+ builder.append(getSASLMechanism());
+ builder.append(", traceString=");
+ builder.append(traceString);
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/sasl/CRAMMD5SASLBindRequest.java b/sdk/src/org/opends/sdk/sasl/CRAMMD5SASLBindRequest.java
new file mode 100644
index 0000000..15b3569
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/CRAMMD5SASLBindRequest.java
@@ -0,0 +1,275 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+
+import org.opends.sdk.DN;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * CRAM-MD5 SASL bind request.
+ */
+public final class CRAMMD5SASLBindRequest extends
+ SASLBindRequest<CRAMMD5SASLBindRequest>
+{
+ /**
+ * The name of the SASL mechanism based on CRAM-MD5 authentication.
+ */
+ public static final String SASL_MECHANISM_CRAM_MD5 = "CRAM-MD5";
+
+ private String authenticationID;
+ private ByteString password;
+
+ private NameCallbackHandler authIDHandler;
+ private PasswordCallbackHandler passHandler;
+
+ private class CRAMMD5SASLContext extends AbstractSASLContext
+ {
+ private SaslClient saslClient;
+ private ByteString outgoingCredentials = null;
+
+ private CRAMMD5SASLContext(String serverName) throws SaslException {
+ saslClient =
+ Sasl.createSaslClient(new String[] { SASL_MECHANISM_CRAM_MD5 },
+ null, SASL_DEFAULT_PROTOCOL, serverName, null, this);
+
+ if (saslClient.hasInitialResponse())
+ {
+ byte[] bytes = saslClient.evaluateChallenge(new byte[0]);
+ if (bytes != null)
+ {
+ this.outgoingCredentials = ByteString.wrap(bytes);
+ }
+ }
+ }
+
+ public void dispose() throws SaslException
+ {
+ saslClient.dispose();
+ }
+
+ public boolean evaluateCredentials(ByteString incomingCredentials)
+ throws SaslException
+ {
+ byte[] bytes =
+ saslClient.evaluateChallenge(incomingCredentials.toByteArray());
+ if (bytes != null)
+ {
+ this.outgoingCredentials = ByteString.wrap(bytes);
+ }
+ else
+ {
+ this.outgoingCredentials = null;
+ }
+
+ return isComplete();
+ }
+
+ public ByteString getSASLCredentials()
+ {
+ return outgoingCredentials;
+ }
+
+ public boolean isComplete()
+ {
+ return saslClient.isComplete();
+ }
+
+ public boolean isSecure()
+ {
+ return false;
+ }
+
+ @Override
+ protected void handle(NameCallback callback)
+ throws UnsupportedCallbackException
+ {
+ if (authIDHandler == null)
+ {
+ callback.setName(authenticationID);
+ }
+ else
+ {
+ if(authIDHandler.handle(callback))
+ {
+ authenticationID = callback.getName();
+ authIDHandler = null;
+ }
+ }
+ }
+
+ @Override
+ protected void handle(PasswordCallback callback)
+ throws UnsupportedCallbackException
+ {
+ if (passHandler == null)
+ {
+ callback.setPassword(password.toString().toCharArray());
+ }
+ else
+ {
+ if(passHandler.handle(callback))
+ {
+ password = ByteString.valueOf(callback.getPassword());
+ passHandler = null;
+ }
+ }
+ }
+
+ public CRAMMD5SASLBindRequest getSASLBindRequest() {
+ return CRAMMD5SASLBindRequest.this;
+ }
+ }
+
+ public CRAMMD5SASLBindRequest(DN authenticationDN, ByteString password)
+ {
+ Validator.ensureNotNull(authenticationDN, password);
+ this.authenticationID = "dn:" + authenticationDN.toString();
+ this.password = password;
+ }
+
+
+
+ public CRAMMD5SASLBindRequest(String authenticationID,
+ ByteString password)
+ {
+ Validator.ensureNotNull(authenticationID, password);
+ this.authenticationID = authenticationID;
+ this.password = password;
+ }
+
+
+
+ public String getAuthenticationID()
+ {
+ return authenticationID;
+ }
+
+
+
+ public NameCallbackHandler getAuthIDHandler()
+ {
+ return authIDHandler;
+ }
+
+
+
+ public PasswordCallbackHandler getPassHandler()
+ {
+ return passHandler;
+ }
+
+
+
+ public ByteString getPassword()
+ {
+ return password;
+ }
+
+
+
+
+ public String getSASLMechanism()
+ {
+ return SASL_MECHANISM_CRAM_MD5;
+ }
+
+
+
+ public SASLContext getClientContext(String serverName) throws SaslException
+ {
+ return new CRAMMD5SASLContext(serverName);
+ }
+
+
+
+ public CRAMMD5SASLBindRequest setAuthenticationID(
+ String authenticationID)
+ {
+ Validator.ensureNotNull(authenticationID);
+ this.authenticationID = authenticationID;
+ return this;
+ }
+
+
+
+ public CRAMMD5SASLBindRequest setAuthIDHandler(
+ NameCallbackHandler authIDHandler)
+ {
+ this.authIDHandler = authIDHandler;
+ return this;
+ }
+
+
+
+ public CRAMMD5SASLBindRequest setPassHandler(
+ PasswordCallbackHandler passHandler)
+ {
+ this.passHandler = passHandler;
+ return this;
+ }
+
+
+
+ public CRAMMD5SASLBindRequest setPassword(ByteString password)
+ {
+ Validator.ensureNotNull(password);
+ this.password = password;
+ return this;
+ }
+
+
+
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append("CRAMMD5SASLBindRequest(bindDN=");
+ builder.append(getName());
+ builder.append(", authentication=SASL");
+ builder.append(", saslMechanism=");
+ builder.append(getSASLMechanism());
+ builder.append(", authenticationID=");
+ builder.append(authenticationID);
+ builder.append(", password=");
+ builder.append(password);
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/sasl/DigestMD5SASLBindRequest.java b/sdk/src/org/opends/sdk/sasl/DigestMD5SASLBindRequest.java
new file mode 100644
index 0000000..a4afac7
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/DigestMD5SASLBindRequest.java
@@ -0,0 +1,436 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.sasl.RealmCallback;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+
+import org.opends.sdk.DN;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Digest-MD5 SASL bind request.
+ */
+public final class DigestMD5SASLBindRequest extends
+ SASLBindRequest<DigestMD5SASLBindRequest>
+{
+ /**
+ * The name of the SASL mechanism based on DIGEST-MD5 authentication.
+ */
+ public static final String SASL_MECHANISM_DIGEST_MD5 = "DIGEST-MD5";
+
+ private String authenticationID;
+ private String authorizationID;
+ private ByteString password;
+ private String realm;
+
+ private NameCallbackHandler authIDHandler;
+ private PasswordCallbackHandler passHandler;
+ private TextInputCallbackHandler realmHandler;
+
+ private class DigestMD5SASLContext extends AbstractSASLContext
+ {
+ private SaslClient saslClient;
+ private ByteString outgoingCredentials = null;
+
+ private DigestMD5SASLContext(String serverName) throws SaslException {
+ Map<String, String> props = new HashMap<String, String>();
+ props.put(Sasl.QOP, "auth-conf,auth-int,auth");
+ saslClient =
+ Sasl.createSaslClient(
+ new String[] { SASL_MECHANISM_DIGEST_MD5 },
+ authorizationID, SASL_DEFAULT_PROTOCOL, serverName, props,
+ this);
+
+ if (saslClient.hasInitialResponse())
+ {
+ byte[] bytes = saslClient.evaluateChallenge(new byte[0]);
+ if (bytes != null)
+ {
+ this.outgoingCredentials = ByteString.wrap(bytes);
+ }
+ }
+ }
+
+ public void dispose() throws SaslException
+ {
+ saslClient.dispose();
+ }
+
+ public boolean evaluateCredentials(ByteString incomingCredentials)
+ throws SaslException
+ {
+ byte[] bytes =
+ saslClient.evaluateChallenge(incomingCredentials.toByteArray());
+ if (bytes != null)
+ {
+ this.outgoingCredentials = ByteString.wrap(bytes);
+ }
+ else
+ {
+ this.outgoingCredentials = null;
+ }
+
+ return isComplete();
+ }
+
+ public ByteString getSASLCredentials()
+ {
+ return outgoingCredentials;
+ }
+
+
+ public boolean isComplete()
+ {
+ return saslClient.isComplete();
+ }
+
+
+
+ public boolean isSecure()
+ {
+ String qop = (String) saslClient.getNegotiatedProperty(Sasl.QOP);
+ return (qop.equalsIgnoreCase("auth-int") || qop
+ .equalsIgnoreCase("auth-conf"));
+ }
+
+ @Override
+ public byte[] unwrap(byte[] incoming, int offset, int len)
+ throws SaslException
+ {
+ return saslClient.unwrap(incoming, offset, len);
+ }
+
+
+
+ @Override
+ public byte[] wrap(byte[] outgoing, int offset, int len)
+ throws SaslException
+ {
+ return saslClient.wrap(outgoing, offset, len);
+ }
+
+
+
+ @Override
+ protected void handle(NameCallback callback)
+ throws UnsupportedCallbackException
+ {
+ if (authIDHandler == null)
+ {
+ callback.setName(authenticationID);
+ }
+ else
+ {
+ if(authIDHandler.handle(callback))
+ {
+ authenticationID = callback.getName();
+ authIDHandler = null;
+ }
+ }
+ }
+
+
+
+ @Override
+ protected void handle(PasswordCallback callback)
+ throws UnsupportedCallbackException
+ {
+ if (passHandler == null)
+ {
+ callback.setPassword(password.toString().toCharArray());
+ }
+ else
+ {
+ if(passHandler.handle(callback))
+ {
+ password = ByteString.valueOf(callback.getPassword());
+ passHandler = null;
+ }
+ }
+ }
+
+
+
+ @Override
+ protected void handle(RealmCallback callback)
+ throws UnsupportedCallbackException
+ {
+ if (realmHandler == null)
+ {
+ if (realm == null)
+ {
+ callback.setText(callback.getDefaultText());
+ }
+ else
+ {
+ callback.setText(realm);
+ }
+ }
+ else
+ {
+ if(realmHandler.handle(callback))
+ {
+ realm = callback.getText();
+ realmHandler = null;
+ }
+ }
+ }
+
+ public DigestMD5SASLBindRequest getSASLBindRequest() {
+ return DigestMD5SASLBindRequest.this;
+ }
+ }
+
+ public DigestMD5SASLBindRequest(DN authenticationDN,
+ ByteString password)
+ {
+ Validator.ensureNotNull(authenticationDN, password);
+ this.authenticationID = "dn:" + authenticationDN.toString();
+ this.password = password;
+ }
+
+
+
+ public DigestMD5SASLBindRequest(DN authenticationDN,
+ DN authorizationDN, ByteString password)
+ {
+ Validator
+ .ensureNotNull(authenticationDN, authorizationDN, password);
+ this.authenticationID = "dn:" + authenticationDN.toString();
+ this.authorizationID = "dn:" + authorizationDN.toString();
+ this.password = password;
+ }
+
+
+
+ public DigestMD5SASLBindRequest(DN authenticationDN,
+ DN authorizationDN, ByteString password, String realm)
+ {
+ Validator.ensureNotNull(authenticationDN, authorizationDN,
+ password);
+ this.authenticationID = "dn:" + authenticationDN.toString();
+ this.authorizationID = "dn:" + authorizationDN.toString();
+ this.password = password;
+ this.realm = realm;
+ }
+
+
+
+ public DigestMD5SASLBindRequest(String authenticationID,
+ ByteString password)
+ {
+ Validator.ensureNotNull(authenticationID, password);
+ this.authenticationID = authenticationID;
+ this.password = password;
+ }
+
+
+
+ public DigestMD5SASLBindRequest(String authenticationID,
+ String authorizationID, ByteString password)
+ {
+ Validator
+ .ensureNotNull(authenticationID, password);
+ this.authenticationID = authenticationID;
+ this.authorizationID = authorizationID;
+ this.password = password;
+ }
+
+
+
+ public DigestMD5SASLBindRequest(String authenticationID,
+ String authorizationID, ByteString password, String realm)
+ {
+ Validator.ensureNotNull(authenticationID, password);
+ this.authenticationID = authenticationID;
+ this.authorizationID = authorizationID;
+ this.password = password;
+ this.realm = realm;
+ }
+
+
+
+ public String getAuthenticationID()
+ {
+ return authenticationID;
+ }
+
+
+
+ public NameCallbackHandler getAuthIDHandler()
+ {
+ return authIDHandler;
+ }
+
+
+
+ public String getAuthorizationID()
+ {
+ return authorizationID;
+ }
+
+
+
+ public PasswordCallbackHandler getPassHandler()
+ {
+ return passHandler;
+ }
+
+
+
+ public ByteString getPassword()
+ {
+ return password;
+ }
+
+
+
+ public String getRealm()
+ {
+ return realm;
+ }
+
+
+
+ public TextInputCallbackHandler getRealmHandler()
+ {
+ return realmHandler;
+ }
+
+
+ @Override
+ public String getSASLMechanism()
+ {
+ return SASL_MECHANISM_DIGEST_MD5;
+ }
+
+
+
+ public SASLContext getClientContext(String serverName) throws SaslException
+ {
+ return new DigestMD5SASLContext(serverName);
+ }
+
+
+
+ public DigestMD5SASLBindRequest setAuthenticationID(
+ String authenticationID)
+ {
+ Validator.ensureNotNull(authenticationID);
+ this.authenticationID = authenticationID;
+ return this;
+ }
+
+
+
+ public DigestMD5SASLBindRequest setAuthIDHandler(
+ NameCallbackHandler authIDHandler)
+ {
+ this.authIDHandler = authIDHandler;
+ return this;
+ }
+
+
+
+ public DigestMD5SASLBindRequest setAuthorizationID(
+ String authorizationID)
+ {
+ this.authorizationID = authorizationID;
+ return this;
+ }
+
+
+
+ public DigestMD5SASLBindRequest setPassHandler(
+ PasswordCallbackHandler passHandler)
+ {
+ this.passHandler = passHandler;
+ return this;
+ }
+
+
+
+ public DigestMD5SASLBindRequest setPassword(ByteString password)
+ {
+ Validator.ensureNotNull(password);
+ this.password = password;
+ return this;
+ }
+
+
+
+ public DigestMD5SASLBindRequest setRealm(String realm)
+ {
+ this.realm = realm;
+ return this;
+ }
+
+
+
+ public void setRealmHandler(TextInputCallbackHandler realmHandler)
+ {
+ this.realmHandler = realmHandler;
+ }
+
+
+
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append("DigestMD5SASLBindRequest(bindDN=");
+ builder.append(getName());
+ builder.append(", authentication=SASL");
+ builder.append(", saslMechanism=");
+ builder.append(getSASLMechanism());
+ builder.append(", authenticationID=");
+ builder.append(authenticationID);
+ builder.append(", authorizationID=");
+ builder.append(authorizationID);
+ builder.append(", realm=");
+ builder.append(realm);
+ builder.append(", password=");
+ builder.append(password);
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/sasl/ExternalSASLBindRequest.java b/sdk/src/org/opends/sdk/sasl/ExternalSASLBindRequest.java
new file mode 100644
index 0000000..ed0aedd
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/ExternalSASLBindRequest.java
@@ -0,0 +1,190 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+
+import org.opends.sdk.DN;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * External SASL bind request.
+ */
+public final class ExternalSASLBindRequest extends
+ SASLBindRequest<ExternalSASLBindRequest>
+{
+ /**
+ * The name of the SASL mechanism based on external authentication.
+ */
+ public static final String SASL_MECHANISM_EXTERNAL = "EXTERNAL";
+
+ private String authorizationID;
+
+ private class ExternalSASLContext extends AbstractSASLContext
+ {
+ private SaslClient saslClient;
+ private ByteString outgoingCredentials = null;
+
+ private ExternalSASLContext(String serverName) throws SaslException {
+ saslClient =
+ Sasl.createSaslClient(new String[] { SASL_MECHANISM_EXTERNAL },
+ authorizationID, SASL_DEFAULT_PROTOCOL, serverName, null,
+ this);
+
+ if (saslClient.hasInitialResponse())
+ {
+ byte[] bytes = saslClient.evaluateChallenge(new byte[0]);
+ if (bytes != null)
+ {
+ this.outgoingCredentials = ByteString.wrap(bytes);
+ }
+ }
+ }
+
+ public void dispose() throws SaslException
+
+ {
+ saslClient.dispose();
+ }
+
+
+
+ public boolean evaluateCredentials(ByteString incomingCredentials)
+ throws SaslException
+ {
+ byte[] bytes =
+ saslClient.evaluateChallenge(incomingCredentials.toByteArray());
+ if (bytes != null)
+ {
+ this.outgoingCredentials = ByteString.wrap(bytes);
+ }
+ else
+ {
+ this.outgoingCredentials = null;
+ }
+
+ return isComplete();
+ }
+
+ public ByteString getSASLCredentials()
+ {
+ return outgoingCredentials;
+ }
+
+
+
+ public boolean isComplete()
+ {
+ return saslClient.isComplete();
+ }
+
+
+
+ public boolean isSecure()
+ {
+ return false;
+ }
+
+ public ExternalSASLBindRequest getSASLBindRequest() {
+ return ExternalSASLBindRequest.this;
+ }
+ }
+
+ public ExternalSASLBindRequest()
+ {
+ }
+
+
+
+ public ExternalSASLBindRequest(DN authorizationDN)
+ {
+ Validator.ensureNotNull(authorizationDN);
+ this.authorizationID = "dn:" + authorizationDN.toString();
+ }
+
+
+
+ public ExternalSASLBindRequest(String authorizationID)
+ {
+ this.authorizationID = authorizationID;
+ }
+
+
+
+ public String getAuthorizationID()
+ {
+ return authorizationID;
+ }
+
+
+
+ public String getSASLMechanism()
+ {
+ return SASL_MECHANISM_EXTERNAL;
+ }
+
+
+
+ public SASLContext getClientContext(String serverName) throws SaslException
+ {
+ return new ExternalSASLContext(serverName);
+ }
+
+
+
+ public ExternalSASLBindRequest setAuthorizationID(
+ String authorizationID)
+ {
+ this.authorizationID = authorizationID;
+ return this;
+ }
+
+
+
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append("ExternalSASLBindRequest(bindDN=");
+ builder.append(getName());
+ builder.append(", authentication=SASL");
+ builder.append(", saslMechanism=");
+ builder.append(getSASLMechanism());
+ builder.append(", authorizationID=");
+ builder.append(authorizationID);
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/sasl/GSSAPISASLBindRequest.java b/sdk/src/org/opends/sdk/sasl/GSSAPISASLBindRequest.java
new file mode 100644
index 0000000..7eac5d5
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/GSSAPISASLBindRequest.java
@@ -0,0 +1,304 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+
+
+import static org.opends.messages.ExtensionMessages.ERR_SASL_CONTEXT_CREATE_ERROR;
+import static org.opends.messages.ExtensionMessages.ERR_SASL_PROTOCOL_ERROR;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginException;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DN;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+import com.sun.security.auth.callback.TextCallbackHandler;
+import com.sun.security.auth.module.Krb5LoginModule;
+
+
+/**
+ * GSSAPI SASL bind request.
+ */
+public final class GSSAPISASLBindRequest extends
+ SASLBindRequest<GSSAPISASLBindRequest>
+{
+ /**
+ * The name of the SASL mechanism based on GSS-API authentication.
+ */
+ public static final String SASL_MECHANISM_GSSAPI = "GSSAPI";
+
+ private final Subject subject;
+ private String authorizationID;
+
+ private class GSSAPISASLContext extends AbstractSASLContext
+ {
+ private String serverName;
+ private SaslClient saslClient;
+ private ByteString outgoingCredentials = null;
+
+ private ByteString incomingCredentials;
+
+ PrivilegedExceptionAction<Boolean> evaluateAction =
+ new PrivilegedExceptionAction<Boolean>()
+ {
+ public Boolean run() throws Exception
+ {
+ byte[] bytes =
+ saslClient.evaluateChallenge(incomingCredentials
+ .toByteArray());
+ if (bytes != null)
+ {
+ outgoingCredentials = ByteString.wrap(bytes);
+ }
+ else
+ {
+ outgoingCredentials = null;
+ }
+
+ return isComplete();
+ }
+ };
+
+ PrivilegedExceptionAction<Object> invokeAction =
+ new PrivilegedExceptionAction<Object>()
+ {
+ public Object run() throws Exception
+ {
+ Map<String, String> props = new HashMap<String, String>();
+ props.put(Sasl.QOP, "auth-conf,auth-int,auth");
+ saslClient =
+ Sasl.createSaslClient(
+ new String[] { SASL_MECHANISM_GSSAPI },
+ authorizationID, SASL_DEFAULT_PROTOCOL, serverName,
+ props, GSSAPISASLContext.this);
+
+ if (saslClient.hasInitialResponse())
+ {
+ byte[] bytes = saslClient.evaluateChallenge(new byte[0]);
+ if (bytes != null)
+ {
+ outgoingCredentials = ByteString.wrap(bytes);
+ }
+ }
+ return null;
+ }
+ };
+
+ private GSSAPISASLContext(String serverName) throws SaslException {
+ this.serverName = serverName;
+
+ try
+ {
+ Subject.doAs(subject, invokeAction);
+ }
+ catch (PrivilegedActionException e)
+ {
+ if (e.getCause() instanceof SaslException)
+ {
+ throw (SaslException) e.getCause();
+ }
+
+ // This should not happen. Must be a bug.
+ Message msg =
+ ERR_SASL_CONTEXT_CREATE_ERROR.get(SASL_MECHANISM_GSSAPI,
+ getExceptionMessage(e));
+ throw new SaslException(msg.toString(), e.getCause());
+ }
+ }
+
+ public void dispose() throws SaslException
+ {
+ saslClient.dispose();
+ }
+
+
+
+ public boolean evaluateCredentials(ByteString incomingCredentials)
+ throws SaslException
+ {
+ this.incomingCredentials = incomingCredentials;
+ try
+ {
+ return Subject.doAs(subject, evaluateAction);
+ }
+ catch (PrivilegedActionException e)
+ {
+ if (e.getCause() instanceof SaslException)
+ {
+ throw (SaslException) e.getCause();
+ }
+
+ // This should not happen. Must be a bug.
+ Message msg =
+ ERR_SASL_PROTOCOL_ERROR.get(SASL_MECHANISM_GSSAPI,
+ getExceptionMessage(e));
+ throw new SaslException(msg.toString(), e.getCause());
+ }
+ }
+
+ public ByteString getSASLCredentials()
+ {
+ return outgoingCredentials;
+ }
+
+
+
+ public boolean isComplete()
+ {
+ return saslClient.isComplete();
+ }
+
+
+
+ public boolean isSecure()
+ {
+ String qop = (String) saslClient.getNegotiatedProperty(Sasl.QOP);
+ return (qop.equalsIgnoreCase("auth-int") || qop
+ .equalsIgnoreCase("auth-conf"));
+ }
+
+ public GSSAPISASLBindRequest getSASLBindRequest() {
+ return GSSAPISASLBindRequest.this;
+ }
+ }
+
+
+ public GSSAPISASLBindRequest(Subject subject)
+ {
+ Validator.ensureNotNull(subject);
+ this.subject = subject;
+ }
+
+
+
+ public GSSAPISASLBindRequest(Subject subject, DN authorizationDN)
+ {
+ Validator.ensureNotNull(subject, authorizationDN);
+ this.subject = subject;
+ this.authorizationID = "dn:" + authorizationDN.toString();
+ }
+
+
+
+ public GSSAPISASLBindRequest(Subject subject, String authorizationID)
+ {
+ Validator.ensureNotNull(subject);
+ this.subject = subject;
+ this.authorizationID = authorizationID;
+ }
+
+
+
+ public String getAuthorizationID()
+ {
+ return authorizationID;
+ }
+
+
+
+ public String getSASLMechanism()
+ {
+ return SASL_MECHANISM_GSSAPI;
+ }
+
+
+
+ public SASLContext getClientContext(String serverName) throws SaslException
+ {
+ return new GSSAPISASLContext(serverName);
+ }
+
+
+
+ public GSSAPISASLBindRequest setAuthorizationID(String authorizationID)
+ {
+ this.authorizationID = authorizationID;
+ return this;
+ }
+
+
+
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append("GSSAPISASLBindRequest(bindDN=");
+ builder.append(getName());
+ builder.append(", authentication=SASL");
+ builder.append(", saslMechanism=");
+ builder.append(getSASLMechanism());
+ builder.append(", subject=");
+ builder.append(subject);
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+
+ public static Subject Kerberos5Login(String authenticationID,
+ ByteString password,
+ String realm, String kdc)
+ throws LoginException {
+ Map<String, Object> state = new HashMap<String, Object>();
+ state.put("javax.security.auth.login.name", authenticationID);
+ state.put("javax.security.auth.login.password",
+ password.toString().toCharArray());
+ state.put("javax.security.auth.useSubjectCredsOnly", "true");
+ state.put("java.security.krb5.realm", realm);
+ state.put("java.security.krb5.kdc", kdc);
+
+ Map<String, Object> options = new HashMap<String, Object>();
+ options.put("debug", "true");
+ options.put("tryFirstPass", "true");
+ options.put("useTicketCache", "true");
+ options.put("doNotPrompt", "true");
+ options.put("storePass", "false");
+ options.put("forwardable", "true");
+
+ Subject subject = new Subject();
+ Krb5LoginModule login = new Krb5LoginModule();
+ login.initialize(subject, new TextCallbackHandler(), state,
+ options);
+ if(login.login()){
+ login.commit();
+ }
+ return subject;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/sasl/GenericSASLBindRequest.java b/sdk/src/org/opends/sdk/sasl/GenericSASLBindRequest.java
new file mode 100644
index 0000000..f151dd2
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/GenericSASLBindRequest.java
@@ -0,0 +1,175 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+
+
+import javax.security.sasl.SaslException;
+
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+/**
+ * Generic SASL bind request.
+ */
+public class GenericSASLBindRequest extends
+ SASLBindRequest<GenericSASLBindRequest> implements SASLContext
+{
+ // The SASL credentials.
+ private ByteString saslCredentials;
+
+ // The SASL mechanism.
+ private String saslMechanism;
+
+
+
+ public GenericSASLBindRequest(String saslMechanism,
+ ByteString saslCredentials)
+ {
+ Validator.ensureNotNull(saslMechanism);
+ this.saslCredentials = saslCredentials;
+ this.saslMechanism = saslMechanism;
+ }
+
+ public void dispose() throws SaslException
+ {
+ // Nothing needed.
+ }
+
+ public boolean evaluateCredentials(ByteString incomingCredentials)
+ throws SaslException
+ {
+ // This is a single stage SASL bind.
+ return true;
+ }
+
+
+ public boolean isComplete()
+ {
+ return true;
+ }
+
+ public boolean isSecure()
+ {
+ return false;
+ }
+
+
+ public byte[] unwrap(byte[] incoming, int offset, int len)
+ throws SaslException
+ {
+ byte[] copy = new byte[len];
+ System.arraycopy(incoming, offset, copy, 0, len);
+ return copy;
+ }
+
+
+ public byte[] wrap(byte[] outgoing, int offset, int len)
+ throws SaslException
+ {
+ byte[] copy = new byte[len];
+ System.arraycopy(outgoing, offset, copy, 0, len);
+ return copy;
+ }
+
+ public ByteString getSASLCredentials()
+ {
+ return saslCredentials;
+ }
+
+ public String getSASLMechanism()
+ {
+ return saslMechanism;
+ }
+
+ public SASLContext getClientContext(String serverName) throws SaslException
+ {
+ return this;
+ }
+
+ public GenericSASLBindRequest getSASLBindRequest() {
+ return this;
+ }
+
+ /**
+ * Sets the SASL credentials for this bind request.
+ *
+ * @param saslCredentials
+ * The SASL credentials for this bind request, or {@code
+ * null} if there are none or if the bind does not use SASL
+ * authentication.
+ * @return This raw bind request.
+ */
+ public GenericSASLBindRequest setSASLCredentials(
+ ByteString saslCredentials)
+ {
+ this.saslCredentials = saslCredentials;
+ return this;
+ }
+
+
+
+ /**
+ * Sets The SASL mechanism for this bind request.
+ *
+ * @param saslMechanism
+ * The SASL mechanism for this bind request, or {@code null}
+ * if there are none or if the bind does not use SASL
+ * authentication.
+ * @return This raw bind request.
+ */
+ public GenericSASLBindRequest setSASLMechanism(String saslMechanism)
+ {
+ Validator.ensureNotNull(saslMechanism);
+ this.saslMechanism = saslMechanism;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append("SASLBindRequest(bindDN=");
+ builder.append(getName());
+ builder.append(", authentication=SASL");
+ builder.append(", saslMechanism=");
+ builder.append(saslMechanism);
+ builder.append(", saslCredentials=");
+ builder.append(saslCredentials);
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/sasl/NameCallbackHandler.java b/sdk/src/org/opends/sdk/sasl/NameCallbackHandler.java
new file mode 100644
index 0000000..913e3c0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/NameCallbackHandler.java
@@ -0,0 +1,42 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+
+
+import javax.security.auth.callback.NameCallback;
+
+
+
+/**
+ * Name call-back.
+ */
+public interface NameCallbackHandler
+{
+ public boolean handle(NameCallback callback);
+}
diff --git a/sdk/src/org/opends/sdk/sasl/PasswordCallbackHandler.java b/sdk/src/org/opends/sdk/sasl/PasswordCallbackHandler.java
new file mode 100644
index 0000000..62b3ae3
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/PasswordCallbackHandler.java
@@ -0,0 +1,42 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+
+
+import javax.security.auth.callback.PasswordCallback;
+
+
+
+/**
+ * Password call-back.
+ */
+public interface PasswordCallbackHandler
+{
+ public boolean handle(PasswordCallback callback);
+}
diff --git a/sdk/src/org/opends/sdk/sasl/PlainSASLBindRequest.java b/sdk/src/org/opends/sdk/sasl/PlainSASLBindRequest.java
new file mode 100644
index 0000000..3e50421
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/PlainSASLBindRequest.java
@@ -0,0 +1,320 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+
+import org.opends.sdk.DN;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Plain SASL bind request.
+ */
+public final class PlainSASLBindRequest extends
+ SASLBindRequest<PlainSASLBindRequest>
+{
+ /**
+ * The name of the SASL mechanism based on PLAIN authentication.
+ */
+ public static final String SASL_MECHANISM_PLAIN = "PLAIN";
+
+ private String authenticationID;
+ private String authorizationID;
+ private ByteString password;
+
+ private NameCallbackHandler authIDHandler;
+ private PasswordCallbackHandler passHandler;
+
+ private class PlainSASLContext extends AbstractSASLContext
+ {
+ private SaslClient saslClient;
+ private ByteString outgoingCredentials = null;
+
+ private PlainSASLContext(String serverName) throws SaslException {
+ saslClient =
+ Sasl.createSaslClient(new String[] { SASL_MECHANISM_PLAIN },
+ authorizationID, SASL_DEFAULT_PROTOCOL, serverName, null,
+ this);
+
+ if (saslClient.hasInitialResponse())
+ {
+ byte[] bytes = saslClient.evaluateChallenge(new byte[0]);
+ if (bytes != null)
+ {
+ this.outgoingCredentials = ByteString.wrap(bytes);
+ }
+ }
+ }
+
+ public void dispose() throws SaslException
+ {
+ saslClient.dispose();
+ }
+
+
+
+ public boolean evaluateCredentials(ByteString incomingCredentials)
+ throws SaslException
+ {
+ byte[] bytes =
+ saslClient.evaluateChallenge(incomingCredentials.toByteArray());
+ if (bytes != null)
+ {
+ this.outgoingCredentials = ByteString.wrap(bytes);
+ }
+ else
+ {
+ this.outgoingCredentials = null;
+ }
+
+ return isComplete();
+ }
+
+
+ public ByteString getSASLCredentials()
+ {
+ return outgoingCredentials;
+ }
+
+
+
+ public boolean isComplete()
+ {
+ return saslClient.isComplete();
+ }
+
+
+
+ public boolean isSecure()
+ {
+ return false;
+ }
+
+
+
+ @Override
+ protected void handle(NameCallback callback)
+ throws UnsupportedCallbackException
+ {
+ if (authIDHandler == null)
+ {
+ callback.setName(authenticationID);
+ }
+ else
+ {
+ if(authIDHandler.handle(callback))
+ {
+ authenticationID = callback.getName();
+ authIDHandler = null;
+ }
+ }
+ }
+
+
+
+ @Override
+ protected void handle(PasswordCallback callback)
+ throws UnsupportedCallbackException
+ {
+ if (passHandler == null)
+ {
+ callback.setPassword(password.toString().toCharArray());
+ }
+ else
+ {
+ if(passHandler.handle(callback))
+ {
+ password = ByteString.valueOf(callback.getPassword());
+ passHandler = null;
+ }
+ }
+ }
+
+ public PlainSASLBindRequest getSASLBindRequest() {
+ return PlainSASLBindRequest.this;
+ }
+ }
+
+ public PlainSASLBindRequest(DN authenticationDN, ByteString password)
+ {
+ Validator.ensureNotNull(authenticationDN, password);
+ this.authenticationID = "dn:" + authenticationDN.toString();
+ this.password = password;
+ }
+
+
+
+ public PlainSASLBindRequest(DN authenticationDN, DN authorizationDN,
+ ByteString password)
+ {
+ Validator
+ .ensureNotNull(authenticationDN, authorizationDN, password);
+ this.authenticationID = "dn:" + authenticationDN.toString();
+ this.authorizationID = "dn:" + authorizationDN.toString();
+ this.password = password;
+ }
+
+
+
+ public PlainSASLBindRequest(String authenticationID,
+ ByteString password)
+ {
+ Validator.ensureNotNull(authenticationID, password);
+ this.authenticationID = authenticationID;
+ this.password = password;
+ }
+
+
+
+ public PlainSASLBindRequest(String authenticationID,
+ String authorizationID, ByteString password)
+ {
+ Validator
+ .ensureNotNull(authenticationID, password);
+ this.authenticationID = authenticationID;
+ this.authorizationID = authorizationID;
+ this.password = password;
+ }
+
+
+
+ public String getAuthenticationID()
+ {
+ return authenticationID;
+ }
+
+
+
+ public NameCallbackHandler getAuthIDHandler()
+ {
+ return authIDHandler;
+ }
+
+
+
+ public String getAuthorizationID()
+ {
+ return authorizationID;
+ }
+
+
+
+ public PasswordCallbackHandler getPassHandler()
+ {
+ return passHandler;
+ }
+
+
+
+ public ByteString getPassword()
+ {
+ return password;
+ }
+
+
+
+ public String getSASLMechanism()
+ {
+ return SASL_MECHANISM_PLAIN;
+ }
+
+
+
+ public SASLContext getClientContext(String serverName) throws SaslException
+ {
+ return new PlainSASLContext(serverName);
+ }
+
+
+
+ public PlainSASLBindRequest setAuthenticationID(
+ String authenticationID)
+ {
+ Validator.ensureNotNull(authenticationID);
+ this.authenticationID = authenticationID;
+ return this;
+ }
+
+
+
+ public PlainSASLBindRequest setAuthIDHandler(
+ NameCallbackHandler authIDHandler)
+ {
+ this.authIDHandler = authIDHandler;
+ return this;
+ }
+
+
+
+ public PlainSASLBindRequest setPassHandler(
+ PasswordCallbackHandler passHandler)
+ {
+ this.passHandler = passHandler;
+ return this;
+ }
+
+
+
+ public PlainSASLBindRequest setPassword(ByteString password)
+ {
+ Validator.ensureNotNull(password);
+ this.password = password;
+ return this;
+ }
+
+
+
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append("PlainSASLBindRequest(bindDN=");
+ builder.append(getName());
+ builder.append(", authentication=SASL");
+ builder.append(", saslMechanism=");
+ builder.append(getSASLMechanism());
+ builder.append(", authenticationID=");
+ builder.append(authenticationID);
+ builder.append(", authorizationID=");
+ builder.append(authorizationID);
+ builder.append(", password=");
+ builder.append(password);
+ builder.append(", controls=");
+ builder.append(getControls());
+ builder.append(")");
+ return builder.toString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/sasl/SASLBindRequest.java b/sdk/src/org/opends/sdk/sasl/SASLBindRequest.java
new file mode 100644
index 0000000..0401e38
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/SASLBindRequest.java
@@ -0,0 +1,66 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+
+
+import javax.security.sasl.SaslException;
+
+import org.opends.sdk.DN;
+import org.opends.sdk.requests.AbstractBindRequest;
+
+
+
+/**
+ * SASL bind request.
+ */
+public abstract class SASLBindRequest<R extends SASLBindRequest<R>>
+ extends AbstractBindRequest<R>
+{
+ /**
+ * Returns the SASL mechanism for this bind request.
+ *
+ * @return The SASL mechanism for this bind request, or {@code null}
+ * if there are none or if the bind does not use SASL
+ * authentication.
+ */
+ public abstract String getSASLMechanism();
+
+
+
+ public abstract SASLContext getClientContext(String serverName)
+ throws SaslException;
+
+
+
+ public DN getName()
+ {
+ return DN.rootDN();
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/sasl/SASLContext.java b/sdk/src/org/opends/sdk/sasl/SASLContext.java
new file mode 100644
index 0000000..d870bfd
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/SASLContext.java
@@ -0,0 +1,77 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+
+
+import javax.security.sasl.SaslException;
+
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * SASL context that is used for the lifetime of the SASL bound connection.
+ */
+public interface SASLContext
+{
+ public void dispose() throws SaslException;
+
+ /**
+ * Returns the SASL credentials for this bind request.
+ *
+ * @return The SASL credentials for this bind request, or {@code null}
+ * if there are none or if the bind does not use SASL
+ * authentication.
+ */
+ public ByteString getSASLCredentials();
+
+
+ public boolean evaluateCredentials(ByteString saslCredentials)
+ throws SaslException;
+
+
+
+ public boolean isComplete();
+
+
+
+ public boolean isSecure();
+
+
+
+ public byte[] unwrap(byte[] incoming, int offset, int len)
+ throws SaslException;
+
+
+
+ public byte[] wrap(byte[] outgoing, int offset, int len)
+ throws SaslException;
+
+ public SASLBindRequest<?> getSASLBindRequest();
+}
diff --git a/sdk/src/org/opends/sdk/sasl/TextInputCallbackHandler.java b/sdk/src/org/opends/sdk/sasl/TextInputCallbackHandler.java
new file mode 100644
index 0000000..6712689
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/TextInputCallbackHandler.java
@@ -0,0 +1,42 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+
+
+import javax.security.auth.callback.TextInputCallback;
+
+
+
+/**
+ * Text input call-back.
+ */
+public interface TextInputCallbackHandler
+{
+ public boolean handle(TextInputCallback callback);
+}
diff --git a/sdk/src/org/opends/sdk/schema/AbstractMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/AbstractMatchingRuleImpl.java
new file mode 100644
index 0000000..d5fb099
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/AbstractMatchingRuleImpl.java
@@ -0,0 +1,132 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import java.util.Comparator;
+import java.util.List;
+
+import org.opends.sdk.Assertion;
+import org.opends.sdk.ConditionResult;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements a default equality or approximate matching rule
+ * that matches normalized values in byte order.
+ */
+abstract class AbstractMatchingRuleImpl implements MatchingRuleImpl
+{
+ static class DefaultEqualityAssertion implements Assertion
+ {
+ ByteSequence normalizedAssertionValue;
+
+
+
+ protected DefaultEqualityAssertion(
+ ByteSequence normalizedAssertionValue)
+ {
+ this.normalizedAssertionValue = normalizedAssertionValue;
+ }
+
+
+
+ public ConditionResult matches(ByteSequence attributeValue)
+ {
+ return normalizedAssertionValue.equals(attributeValue) ? ConditionResult.TRUE
+ : ConditionResult.FALSE;
+ }
+ }
+
+ private static final Assertion UNDEFINED_ASSERTION = new Assertion()
+ {
+ public ConditionResult matches(ByteSequence attributeValue)
+ {
+ return ConditionResult.UNDEFINED;
+ }
+ };
+
+ private static final Comparator<ByteSequence> DEFAULT_COMPARATOR =
+ new Comparator<ByteSequence>()
+ {
+ public int compare(ByteSequence o1, ByteSequence o2)
+ {
+ return o1.compareTo(o2);
+ }
+ };
+
+
+
+ AbstractMatchingRuleImpl()
+ {
+ // Nothing to do.
+ }
+
+
+
+ public Comparator<ByteSequence> comparator(Schema schema)
+ {
+ return DEFAULT_COMPARATOR;
+ }
+
+
+
+ public Assertion getAssertion(Schema schema, ByteSequence value)
+ throws DecodeException
+ {
+ return new DefaultEqualityAssertion(normalizeAttributeValue(schema,
+ value));
+ }
+
+
+
+ public Assertion getAssertion(Schema schema, ByteSequence subInitial,
+ List<ByteSequence> subAnyElements, ByteSequence subFinal)
+ throws DecodeException
+ {
+ return UNDEFINED_ASSERTION;
+ }
+
+
+
+ public Assertion getGreaterOrEqualAssertion(Schema schema,
+ ByteSequence value) throws DecodeException
+ {
+ return UNDEFINED_ASSERTION;
+ }
+
+
+
+ public Assertion getLessOrEqualAssertion(Schema schema,
+ ByteSequence value) throws DecodeException
+ {
+ return UNDEFINED_ASSERTION;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/AbstractOrderingMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/AbstractOrderingMatchingRuleImpl.java
new file mode 100644
index 0000000..7ebcbd7
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/AbstractOrderingMatchingRuleImpl.java
@@ -0,0 +1,104 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.Assertion;
+import org.opends.sdk.ConditionResult;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements a default ordering matching rule that matches
+ * normalized values in byte order.
+ */
+abstract class AbstractOrderingMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+ AbstractOrderingMatchingRuleImpl()
+ {
+ // Nothing to do.
+ }
+
+
+
+ @Override
+ public Assertion getAssertion(Schema schema, ByteSequence value)
+ throws DecodeException
+ {
+ final ByteString normAssertion =
+ normalizeAttributeValue(schema, value);
+ return new Assertion()
+ {
+ public ConditionResult matches(ByteSequence attributeValue)
+ {
+ return attributeValue.compareTo(normAssertion) < 0 ? ConditionResult.TRUE
+ : ConditionResult.FALSE;
+ }
+ };
+ }
+
+
+
+ @Override
+ public Assertion getGreaterOrEqualAssertion(Schema schema,
+ ByteSequence value) throws DecodeException
+ {
+ final ByteString normAssertion =
+ normalizeAttributeValue(schema, value);
+ return new Assertion()
+ {
+ public ConditionResult matches(ByteSequence attributeValue)
+ {
+ return attributeValue.compareTo(normAssertion) >= 0 ? ConditionResult.TRUE
+ : ConditionResult.FALSE;
+ }
+ };
+ }
+
+
+
+ @Override
+ public Assertion getLessOrEqualAssertion(Schema schema,
+ ByteSequence value) throws DecodeException
+ {
+ final ByteString normAssertion =
+ normalizeAttributeValue(schema, value);
+ return new Assertion()
+ {
+ public ConditionResult matches(ByteSequence attributeValue)
+ {
+ return attributeValue.compareTo(normAssertion) <= 0 ? ConditionResult.TRUE
+ : ConditionResult.FALSE;
+ }
+ };
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/AbstractSubstringMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/AbstractSubstringMatchingRuleImpl.java
new file mode 100644
index 0000000..7c0c1b0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/AbstractSubstringMatchingRuleImpl.java
@@ -0,0 +1,270 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opends.messages.SchemaMessages;
+import org.opends.sdk.Assertion;
+import org.opends.sdk.ConditionResult;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class implements a default substring matching rule that matches
+ * normalized substring assertion values in byte order.
+ */
+abstract class AbstractSubstringMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+ static class DefaultSubstringAssertion implements Assertion
+ {
+ private final ByteString normInitial;
+ private final ByteString[] normAnys;
+ private final ByteString normFinal;
+
+
+
+ protected DefaultSubstringAssertion(ByteString normInitial,
+ ByteString[] normAnys, ByteString normFinal)
+ {
+ this.normInitial = normInitial;
+ this.normAnys = normAnys;
+ this.normFinal = normFinal;
+ }
+
+
+
+ public ConditionResult matches(ByteSequence attributeValue)
+ {
+ final int valueLength = attributeValue.length();
+
+ int pos = 0;
+ if (normInitial != null)
+ {
+ final int initialLength = normInitial.length();
+ if (initialLength > valueLength)
+ {
+ return ConditionResult.FALSE;
+ }
+
+ for (; pos < initialLength; pos++)
+ {
+ if (normInitial.byteAt(pos) != attributeValue.byteAt(pos))
+ {
+ return ConditionResult.FALSE;
+ }
+ }
+ }
+
+ if (normAnys != null && normAnys.length != 0)
+ {
+ for (final ByteSequence element : normAnys)
+ {
+ final int anyLength = element.length();
+ if (anyLength == 0)
+ {
+ continue;
+ }
+ final int end = valueLength - anyLength;
+ boolean match = false;
+ for (; pos <= end; pos++)
+ {
+ if (element.byteAt(0) == attributeValue.byteAt(pos))
+ {
+ boolean subMatch = true;
+ for (int i = 1; i < anyLength; i++)
+ {
+ if (element.byteAt(i) != attributeValue.byteAt(pos + i))
+ {
+ subMatch = false;
+ break;
+ }
+ }
+
+ if (subMatch)
+ {
+ match = subMatch;
+ break;
+ }
+ }
+ }
+
+ if (match)
+ {
+ pos += anyLength;
+ }
+ else
+ {
+ return ConditionResult.FALSE;
+ }
+ }
+ }
+
+ if (normFinal != null)
+ {
+ final int finalLength = normFinal.length();
+
+ if (valueLength - finalLength < pos)
+ {
+ return ConditionResult.FALSE;
+ }
+
+ pos = valueLength - finalLength;
+ for (int i = 0; i < finalLength; i++, pos++)
+ {
+ if (normFinal.byteAt(i) != attributeValue.byteAt(pos))
+ {
+ return ConditionResult.FALSE;
+ }
+ }
+ }
+
+ return ConditionResult.TRUE;
+ }
+ }
+
+
+
+ AbstractSubstringMatchingRuleImpl()
+ {
+ // Nothing to do.
+ }
+
+
+
+ @Override
+ public Assertion getAssertion(Schema schema, ByteSequence value)
+ throws DecodeException
+ {
+ if (value.length() == 0)
+ {
+ throw DecodeException.error(
+ SchemaMessages.WARN_ATTR_SYNTAX_SUBSTRING_EMPTY.get());
+ }
+
+ ByteSequence initialString = null;
+ ByteSequence finalString = null;
+ List<ByteSequence> anyStrings = null;
+
+ final String valueString = value.toString();
+
+ if (valueString.length() == 1 && valueString.charAt(0) == '*')
+ {
+ return getAssertion(schema, initialString, anyStrings,
+ finalString);
+ }
+
+ final char[] escapeChars = new char[] { '*' };
+ final SubstringReader reader = new SubstringReader(valueString);
+
+ ByteString bytes =
+ StaticUtils.evaluateEscapes(reader, escapeChars, false);
+ if (bytes.length() > 0)
+ {
+ initialString = normalizeSubString(schema, bytes);
+ }
+ if (reader.remaining() == 0)
+ {
+ throw DecodeException.error(
+ SchemaMessages.WARN_ATTR_SYNTAX_SUBSTRING_NO_WILDCARDS
+ .get(value.toString()));
+ }
+ while (true)
+ {
+ reader.read();
+ bytes = StaticUtils.evaluateEscapes(reader, escapeChars, false);
+ if (reader.remaining() > 0)
+ {
+ if (bytes.length() == 0)
+ {
+ throw DecodeException.error(
+ SchemaMessages.WARN_ATTR_SYNTAX_SUBSTRING_CONSECUTIVE_WILDCARDS
+ .get(value.toString(), reader.pos()));
+ }
+ if (anyStrings == null)
+ {
+ anyStrings = new LinkedList<ByteSequence>();
+ }
+ anyStrings.add(normalizeSubString(schema, bytes));
+ }
+ else
+ {
+ if (bytes.length() > 0)
+ {
+ finalString = normalizeSubString(schema, bytes);
+ }
+ break;
+ }
+ }
+
+ return getAssertion(schema, initialString, anyStrings, finalString);
+ }
+
+
+
+ @Override
+ public Assertion getAssertion(Schema schema, ByteSequence subInitial,
+ List<ByteSequence> subAnyElements, ByteSequence subFinal)
+ throws DecodeException
+ {
+ final ByteString normInitial =
+ subInitial == null ? null : normalizeSubString(schema,
+ subInitial);
+
+ ByteString[] normAnys = null;
+ if (subAnyElements != null && !subAnyElements.isEmpty())
+ {
+ normAnys = new ByteString[subAnyElements.size()];
+ for (int i = 0; i < subAnyElements.size(); i++)
+ {
+ normAnys[i] = normalizeSubString(schema, subAnyElements.get(i));
+ }
+ }
+ final ByteString normFinal =
+ subFinal == null ? null : normalizeSubString(schema, subFinal);
+
+ return new DefaultSubstringAssertion(normInitial, normAnys,
+ normFinal);
+ }
+
+
+
+ ByteString normalizeSubString(Schema schema, ByteSequence value)
+ throws DecodeException
+ {
+ return normalizeAttributeValue(schema, value);
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/AbstractSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/AbstractSyntaxImpl.java
new file mode 100644
index 0000000..22c1717
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/AbstractSyntaxImpl.java
@@ -0,0 +1,77 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+/**
+ * This class defines the set of methods and structures that must be
+ * implemented to define a new attribute syntax.
+ */
+abstract class AbstractSyntaxImpl implements SyntaxImpl
+{
+ AbstractSyntaxImpl()
+ {
+ // Nothing to do.
+ }
+
+
+
+ public String getApproximateMatchingRule()
+ {
+ return null;
+ }
+
+
+
+ public String getEqualityMatchingRule()
+ {
+ return null;
+ }
+
+
+
+ public String getOrderingMatchingRule()
+ {
+ return null;
+ }
+
+
+
+ public String getSubstringMatchingRule()
+ {
+ return null;
+ }
+
+
+
+ public boolean isBEREncodingRequired()
+ {
+ return false;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/AttributeType.java b/sdk/src/org/opends/sdk/schema/AttributeType.java
new file mode 100644
index 0000000..f338af2
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/AttributeType.java
@@ -0,0 +1,908 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.schema.SchemaConstants.SCHEMA_PROPERTY_APPROX_RULE;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class defines a data structure for storing and interacting with
+ * an attribute type, which contains information about the format of an
+ * attribute and the syntax and matching rules that should be used when
+ * interacting with it.
+ * <p>
+ * Where ordered sets of names, or extra properties are provided, the
+ * ordering will be preserved when the associated fields are accessed
+ * via their getters or via the {@link #toString()} methods.
+ */
+public final class AttributeType extends SchemaElement implements
+ Comparable<AttributeType>
+{
+
+ // The approximate matching rule for this attribute type.
+ private final String approximateMatchingRuleOID;
+
+ // The attribute usage for this attribute type.
+ private final AttributeUsage attributeUsage;
+
+ // The definition string used to create this objectclass.
+ private final String definition;
+
+ // The equality matching rule for this attribute type.
+ private final String equalityMatchingRuleOID;
+
+ // Indicates whether this attribute type is declared "collective".
+ private final boolean isCollective;
+
+ // Indicates whether this attribute type is declared
+ // "no-user-modification".
+ private final boolean isNoUserModification;
+
+ // Indicates whether this definition is declared "obsolete".
+ private final boolean isObsolete;
+
+ // Indicates whether this attribute type is declared "single-value".
+ private final boolean isSingleValue;
+
+ // The set of user defined names for this definition.
+ private final List<String> names;
+
+ // The OID that may be used to reference this definition.
+ private final String oid;
+
+ // The ordering matching rule for this attribute type.
+ private final String orderingMatchingRuleOID;
+
+ // The substring matching rule for this attribute type.
+ private final String substringMatchingRuleOID;
+
+ // The superior attribute type from which this attribute type
+ // inherits.
+ private final String superiorTypeOID;
+
+ // The syntax for this attribute type.
+ private final String syntaxOID;
+
+ // True if this type has OID 2.5.4.0.
+ private final boolean isObjectClassType;
+
+ // The normalized name of this attribute type.
+ private final String normalizedName;
+
+ // The superior attribute type from which this attribute type
+ // inherits.
+ private AttributeType superiorType;
+
+ // The equality matching rule for this attribute type.
+ private MatchingRule equalityMatchingRule;
+
+ // The ordering matching rule for this attribute type.
+ private MatchingRule orderingMatchingRule;
+
+ // The substring matching rule for this attribute type.
+ private MatchingRule substringMatchingRule;
+
+ // The approximate matching rule for this attribute type.
+ private MatchingRule approximateMatchingRule;
+
+ // The syntax for this attribute type.
+ private Syntax syntax;
+
+
+
+ AttributeType(String oid, List<String> names, String description,
+ boolean obsolete, String superiorType,
+ String equalityMatchingRule, String orderingMatchingRule,
+ String substringMatchingRule, String approximateMatchingRule,
+ String syntax, boolean singleValue, boolean collective,
+ boolean noUserModification, AttributeUsage attributeUsage,
+ Map<String, List<String>> extraProperties, String definition)
+ {
+ super(description, extraProperties);
+
+ Validator.ensureNotNull(oid, names, description, attributeUsage);
+ Validator.ensureTrue(superiorType != null || syntax != null,
+ "superiorType and/or syntax must not be null");
+ Validator.ensureNotNull(extraProperties);
+
+ this.oid = oid;
+ this.names = names;
+ this.isObsolete = obsolete;
+ this.superiorTypeOID = superiorType;
+ this.equalityMatchingRuleOID = equalityMatchingRule;
+ this.orderingMatchingRuleOID = orderingMatchingRule;
+ this.substringMatchingRuleOID = substringMatchingRule;
+ this.approximateMatchingRuleOID = approximateMatchingRule;
+ this.syntaxOID = syntax;
+ this.isSingleValue = singleValue;
+ this.isCollective = collective;
+ this.isNoUserModification = noUserModification;
+ this.attributeUsage = attributeUsage;
+
+ if (definition != null)
+ {
+ this.definition = definition;
+ }
+ else
+ {
+ this.definition = buildDefinition();
+ }
+
+ this.isObjectClassType = oid.equals("2.5.4.0");
+ this.normalizedName = StaticUtils.toLowerCase(getNameOrOID());
+ }
+
+
+
+ AttributeType(String oid, List<String> names, String description,
+ MatchingRule equalityMatchingRule, Syntax syntax)
+ {
+ super(description, Collections.<String, List<String>> emptyMap());
+
+ Validator.ensureNotNull(oid, names, description);
+
+ this.oid = oid;
+ this.names = names;
+ this.isObsolete = false;
+ this.superiorTypeOID = null;
+ this.superiorType = null;
+ this.equalityMatchingRuleOID = equalityMatchingRule.getOID();
+ this.equalityMatchingRule = equalityMatchingRule;
+ this.orderingMatchingRuleOID = null;
+ this.substringMatchingRuleOID = null;
+ this.approximateMatchingRuleOID = null;
+ this.syntaxOID = syntax.getOID();
+ this.syntax = syntax;
+ this.isSingleValue = false;
+ this.isCollective = false;
+ this.isNoUserModification = false;
+ this.attributeUsage = AttributeUsage.USER_APPLICATIONS;
+ this.definition = buildDefinition();
+
+ this.isObjectClassType = oid.equals("2.5.4.0");
+ this.normalizedName = StaticUtils.toLowerCase(getNameOrOID());
+ }
+
+
+
+ /**
+ * Compares this attribute type to the provided attribute type. The
+ * sort-order is defined as follows:
+ * <ul>
+ * <li>The {@code objectClass} attribute is less than all other
+ * attribute types.
+ * <li>User attributes are less than operational attributes.
+ * <li>Lexicographic comparison of the primary name or OID.
+ * </ul>
+ *
+ * @param type
+ * The attribute type to be compared.
+ * @return A negative integer, zero, or a positive integer as this
+ * attribute type is less than, equal to, or greater than the
+ * specified attribute type.
+ * @throws NullPointerException
+ * If {@code name} was {@code null}.
+ */
+ public int compareTo(AttributeType type) throws NullPointerException
+ {
+ if (isObjectClassType)
+ {
+ return type.isObjectClassType ? 0 : -1;
+ }
+ else if (type.isObjectClassType)
+ {
+ return 1;
+ }
+ else
+ {
+ final boolean isOperational = getUsage().isOperational();
+ final boolean typeIsOperational = type.getUsage().isOperational();
+
+ if (isOperational == typeIsOperational)
+ {
+ return normalizedName.compareTo(type.normalizedName);
+ }
+ else
+ {
+ return isOperational ? 1 : -1;
+ }
+ }
+ }
+
+
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ {
+ return true;
+ }
+
+ if (o instanceof AttributeType)
+ {
+ final AttributeType other = (AttributeType) o;
+ return oid.equals(other.oid);
+ }
+
+ return false;
+ }
+
+
+
+ /**
+ * Retrieves the matching rule that should be used for approximate
+ * matching with this attribute type.
+ *
+ * @return The matching rule that should be used for approximate
+ * matching with this attribute type.
+ */
+ public MatchingRule getApproximateMatchingRule()
+ {
+ return approximateMatchingRule;
+ }
+
+
+
+ /**
+ * Retrieves the matching rule that should be used for equality
+ * matching with this attribute type.
+ *
+ * @return The matching rule that should be used for equality matching
+ * with this attribute type.
+ */
+ public MatchingRule getEqualityMatchingRule()
+ {
+ return equalityMatchingRule;
+ }
+
+
+
+ /**
+ * Retrieves the name or OID for this schema definition. If it has one
+ * or more names, then the primary name will be returned. If it does
+ * not have any names, then the OID will be returned.
+ *
+ * @return The name or OID for this schema definition.
+ */
+ public String getNameOrOID()
+ {
+ if (names.isEmpty())
+ {
+ return oid;
+ }
+ return names.get(0);
+ }
+
+
+
+ /**
+ * Retrieves an iterable over the set of user-defined names that may
+ * be used to reference this schema definition.
+ *
+ * @return Returns an iterable over the set of user-defined names that
+ * may be used to reference this schema definition.
+ */
+ public Iterable<String> getNames()
+ {
+ return names;
+ }
+
+
+
+ /**
+ * Retrieves the OID for this schema definition.
+ *
+ * @return The OID for this schema definition.
+ */
+ public String getOID()
+ {
+
+ return oid;
+ }
+
+
+
+ /**
+ * Retrieves the matching rule that should be used for ordering with
+ * this attribute type.
+ *
+ * @return The matching rule that should be used for ordering with
+ * this attribute type.
+ */
+ public MatchingRule getOrderingMatchingRule()
+ {
+ return orderingMatchingRule;
+ }
+
+
+
+ /**
+ * Retrieves the matching rule that should be used for substring
+ * matching with this attribute type.
+ *
+ * @return The matching rule that should be used for substring
+ * matching with this attribute type.
+ */
+ public MatchingRule getSubstringMatchingRule()
+ {
+ return substringMatchingRule;
+ }
+
+
+
+ /**
+ * Retrieves the superior type for this attribute type.
+ *
+ * @return The superior type for this attribute type, or
+ * <CODE>null</CODE> if it does not have one.
+ */
+ public AttributeType getSuperiorType()
+ {
+ return superiorType;
+ }
+
+
+
+ /**
+ * Retrieves the syntax for this attribute type.
+ *
+ * @return The syntax for this attribute type.
+ */
+ public Syntax getSyntax()
+ {
+ return syntax;
+ }
+
+
+
+ /**
+ * Retrieves the usage indicator for this attribute type.
+ *
+ * @return The usage indicator for this attribute type.
+ */
+ public AttributeUsage getUsage()
+ {
+ return attributeUsage;
+ }
+
+
+
+ @Override
+ public int hashCode()
+ {
+ return oid.hashCode();
+ }
+
+
+
+ /**
+ * Indicates whether this schema definition has the specified name.
+ *
+ * @param name
+ * The name for which to make the determination.
+ * @return {@code true} if the specified name is assigned to this
+ * schema definition, or {@code false} if not.
+ */
+ public boolean hasName(String name)
+ {
+ for (final String n : names)
+ {
+ if (n.equalsIgnoreCase(name))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether this schema definition has the specified name or
+ * OID.
+ *
+ * @param value
+ * The value for which to make the determination.
+ * @return {@code true} if the provided value matches the OID or one
+ * of the names assigned to this schema definition, or {@code
+ * false} if not.
+ */
+ public boolean hasNameOrOID(String value)
+ {
+ return hasName(value) || getOID().equals(value);
+ }
+
+
+
+ /**
+ * Indicates whether this attribute type is declared "collective".
+ *
+ * @return {@code true} if this attribute type is declared
+ * "collective", or {@code false} if not.
+ */
+ public boolean isCollective()
+ {
+ return isCollective;
+ }
+
+
+
+ /**
+ * Indicates whether this attribute type is declared
+ * "no-user-modification".
+ *
+ * @return {@code true} if this attribute type is declared
+ * "no-user-modification", or {@code false} if not.
+ */
+ public boolean isNoUserModification()
+ {
+ return isNoUserModification;
+ }
+
+
+
+ /**
+ * Indicates whether or not this attribute type is the {@code
+ * objectClass} attribute type having the OID 2.5.4.0.
+ *
+ * @return {@code true} if this attribute type is the {@code
+ * objectClass} attribute type, or {@code false} if not.
+ */
+ public boolean isObjectClass()
+ {
+ return isObjectClassType;
+ }
+
+
+
+ /**
+ * Indicates whether this schema definition is declared "obsolete".
+ *
+ * @return {@code true} if this schema definition is declared
+ * "obsolete", or {@code false} if not.
+ */
+ public boolean isObsolete()
+ {
+ return isObsolete;
+ }
+
+
+
+ /**
+ * Indicates whether this is an operational attribute. An operational
+ * attribute is one with a usage of "directoryOperation",
+ * "distributedOperation", or "dSAOperation" (i.e., only
+ * userApplications is not operational).
+ *
+ * @return {@code true} if this is an operational attribute, or
+ * {@code false} if not.
+ */
+ public boolean isOperational()
+ {
+ return attributeUsage.isOperational();
+ }
+
+
+
+ /**
+ * Indicates whether this attribute type is declared "single-value".
+ *
+ * @return {@code true} if this attribute type is declared
+ * "single-value", or {@code false} if not.
+ */
+ public boolean isSingleValue()
+ {
+ return isSingleValue;
+ }
+
+
+
+ /**
+ * Indicates whether or not this attribute type is a sub-type of the
+ * provided attribute type.
+ *
+ * @param type
+ * The attribute type for which to make the determination.
+ * @return {@code true} if this attribute type is a sub-type of the
+ * provided attribute type, or {@code false} if not.
+ * @throws NullPointerException
+ * If {@code type} was {@code null}.
+ */
+ public boolean isSubTypeOf(AttributeType type)
+ {
+ AttributeType tmp = this;
+ do
+ {
+ if (tmp.equals(type))
+ {
+ return true;
+ }
+ tmp = tmp.getSuperiorType();
+ } while (tmp != null);
+ return false;
+ }
+
+
+
+ /**
+ * Retrieves the string representation of this schema definition in
+ * the form specified in RFC 2252.
+ *
+ * @return The string representation of this schema definition in the
+ * form specified in RFC 2252.
+ */
+ @Override
+ public String toString()
+ {
+ return definition;
+ }
+
+
+
+ AttributeType duplicate()
+ {
+ return new AttributeType(oid, names, description, isObsolete,
+ superiorTypeOID, equalityMatchingRuleOID,
+ orderingMatchingRuleOID, substringMatchingRuleOID,
+ approximateMatchingRuleOID, syntaxOID, isSingleValue,
+ isCollective, isNoUserModification, attributeUsage,
+ extraProperties, definition);
+ }
+
+
+
+ @Override
+ void toStringContent(StringBuilder buffer)
+ {
+ buffer.append(oid);
+
+ if (!names.isEmpty())
+ {
+ final Iterator<String> iterator = names.iterator();
+
+ final String firstName = iterator.next();
+ if (iterator.hasNext())
+ {
+ buffer.append(" NAME ( '");
+ buffer.append(firstName);
+
+ while (iterator.hasNext())
+ {
+ buffer.append("' '");
+ buffer.append(iterator.next());
+ }
+
+ buffer.append("' )");
+ }
+ else
+ {
+ buffer.append(" NAME '");
+ buffer.append(firstName);
+ buffer.append("'");
+ }
+ }
+
+ if (description != null && description.length() > 0)
+ {
+ buffer.append(" DESC '");
+ buffer.append(description);
+ buffer.append("'");
+ }
+
+ if (isObsolete)
+ {
+ buffer.append(" OBSOLETE");
+ }
+
+ if (superiorTypeOID != null)
+ {
+ buffer.append(" SUP ");
+ buffer.append(superiorTypeOID);
+ }
+
+ if (equalityMatchingRuleOID != null)
+ {
+ buffer.append(" EQUALITY ");
+ buffer.append(equalityMatchingRuleOID);
+ }
+
+ if (orderingMatchingRuleOID != null)
+ {
+ buffer.append(" ORDERING ");
+ buffer.append(orderingMatchingRuleOID);
+ }
+
+ if (substringMatchingRuleOID != null)
+ {
+ buffer.append(" SUBSTR ");
+ buffer.append(substringMatchingRuleOID);
+ }
+
+ if (syntaxOID != null)
+ {
+ buffer.append(" SYNTAX ");
+ buffer.append(syntaxOID);
+ }
+
+ if (isSingleValue())
+ {
+ buffer.append(" SINGLE-VALUE");
+ }
+
+ if (isCollective())
+ {
+ buffer.append(" COLLECTIVE");
+ }
+
+ if (isNoUserModification())
+ {
+ buffer.append(" NO-USER-MODIFICATION");
+ }
+
+ if (attributeUsage != null)
+ {
+ buffer.append(" USAGE ");
+ buffer.append(attributeUsage.toString());
+ }
+
+ if (approximateMatchingRuleOID != null)
+ {
+ buffer.append(" ");
+ buffer.append(SCHEMA_PROPERTY_APPROX_RULE);
+ buffer.append(" '");
+ buffer.append(approximateMatchingRuleOID);
+ buffer.append("'");
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ void validate(List<Message> warnings, Schema schema)
+ throws SchemaException
+ {
+ if (superiorTypeOID != null)
+ {
+ try
+ {
+ superiorType = schema.getAttributeType(superiorTypeOID);
+ }
+ catch (UnknownSchemaElementException e)
+ {
+ final Message message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_SUPERIOR_TYPE
+ .get(getNameOrOID(), superiorTypeOID);
+ throw new SchemaException(message);
+ }
+
+ // If there is a superior type, then it must have the same usage
+ // as the subordinate type. Also, if the superior type is
+ // collective, then so must the subordinate type be collective.
+ if (superiorType.getUsage() != getUsage())
+ {
+ final Message message = WARN_ATTR_SYNTAX_ATTRTYPE_INVALID_SUPERIOR_USAGE
+ .get(getNameOrOID(), getUsage().toString(), superiorType
+ .getNameOrOID());
+ throw new SchemaException(message);
+ }
+
+ if (superiorType.isCollective() != isCollective())
+ {
+ Message message;
+ if (isCollective())
+ {
+ message = WARN_ATTR_SYNTAX_ATTRTYPE_COLLECTIVE_FROM_NONCOLLECTIVE
+ .get(getNameOrOID(), superiorType.getNameOrOID());
+ }
+ else
+ {
+ message = WARN_ATTR_SYNTAX_ATTRTYPE_NONCOLLECTIVE_FROM_COLLECTIVE
+ .get(getNameOrOID(), superiorType.getNameOrOID());
+ }
+ throw new SchemaException(message);
+ }
+ }
+
+ if (syntaxOID != null)
+ {
+ if (!schema.hasSyntax(syntaxOID))
+ {
+ // Try substituting a syntax from the core schema. This will
+ // never fail since the core schema is non-strict and will
+ // substitute the syntax if required.
+ syntax = Schema.getCoreSchema().getSyntax(syntaxOID);
+ final Message message = WARN_ATTR_TYPE_NOT_DEFINED.get(
+ getNameOrOID(), syntaxOID, syntax.toString());
+ warnings.add(message);
+ }
+ else
+ {
+ syntax = schema.getSyntax(syntaxOID);
+ }
+ }
+ else if (getSuperiorType() != null
+ && getSuperiorType().getSyntax() != null)
+ {
+ // Try to inherit the syntax from the superior type if possible
+ syntax = getSuperiorType().getSyntax();
+ }
+
+ if (equalityMatchingRuleOID != null)
+ {
+ // Use explicitly defined matching rule first.
+ try
+ {
+ equalityMatchingRule = schema
+ .getMatchingRule(equalityMatchingRuleOID);
+ }
+ catch (UnknownSchemaElementException e)
+ {
+ final Message message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_EQUALITY_MR
+ .get(getNameOrOID(), equalityMatchingRuleOID);
+ throw new SchemaException(message);
+ }
+ }
+ else if (getSuperiorType() != null
+ && getSuperiorType().getEqualityMatchingRule() != null)
+ {
+ // Inherit matching rule from superior type if possible
+ equalityMatchingRule = getSuperiorType()
+ .getEqualityMatchingRule();
+ }
+ else if (getSyntax() != null
+ && getSyntax().getEqualityMatchingRule() != null)
+ {
+ // Use default for syntax
+ equalityMatchingRule = getSyntax().getEqualityMatchingRule();
+ }
+
+ if (orderingMatchingRuleOID != null)
+ {
+ // Use explicitly defined matching rule first.
+ try
+ {
+ orderingMatchingRule = schema
+ .getMatchingRule(orderingMatchingRuleOID);
+ }
+ catch (UnknownSchemaElementException e)
+ {
+ final Message message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_ORDERING_MR
+ .get(getNameOrOID(), orderingMatchingRuleOID);
+ throw new SchemaException(message);
+ }
+ }
+ else if (getSuperiorType() != null
+ && getSuperiorType().getOrderingMatchingRule() != null)
+ {
+ // Inherit matching rule from superior type if possible
+ orderingMatchingRule = getSuperiorType()
+ .getOrderingMatchingRule();
+ }
+ else if (getSyntax() != null
+ && getSyntax().getOrderingMatchingRule() != null)
+ {
+ // Use default for syntax
+ orderingMatchingRule = getSyntax().getOrderingMatchingRule();
+ }
+
+ if (substringMatchingRuleOID != null)
+ {
+ // Use explicitly defined matching rule first.
+ try
+ {
+ substringMatchingRule = schema
+ .getMatchingRule(substringMatchingRuleOID);
+ }
+ catch (UnknownSchemaElementException e)
+ {
+ final Message message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_SUBSTRING_MR
+ .get(getNameOrOID(), substringMatchingRuleOID);
+ throw new SchemaException(message);
+ }
+ }
+ else if (getSuperiorType() != null
+ && getSuperiorType().getSubstringMatchingRule() != null)
+ {
+ // Inherit matching rule from superior type if possible
+ substringMatchingRule = getSuperiorType()
+ .getSubstringMatchingRule();
+ }
+ else if (getSyntax() != null
+ && getSyntax().getSubstringMatchingRule() != null)
+ {
+ // Use default for syntax
+ substringMatchingRule = getSyntax().getSubstringMatchingRule();
+ }
+
+ if (approximateMatchingRuleOID != null)
+ {
+ // Use explicitly defined matching rule first.
+ try
+ {
+ approximateMatchingRule = schema
+ .getMatchingRule(approximateMatchingRuleOID);
+ }
+ catch (UnknownSchemaElementException e)
+ {
+ final Message message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_APPROXIMATE_MR
+ .get(getNameOrOID(), approximateMatchingRuleOID);
+ throw new SchemaException(message);
+ }
+ }
+ else if (getSuperiorType() != null
+ && getSuperiorType().getApproximateMatchingRule() != null)
+ {
+ // Inherit matching rule from superior type if possible
+ approximateMatchingRule = getSuperiorType()
+ .getApproximateMatchingRule();
+ }
+ else if (getSyntax() != null
+ && getSyntax().getApproximateMatchingRule() != null)
+ {
+ // Use default for syntax
+ approximateMatchingRule = getSyntax()
+ .getApproximateMatchingRule();
+ }
+
+ // If the attribute type is COLLECTIVE, then it must have a usage of
+ // userApplications.
+ if (isCollective()
+ && getUsage() != AttributeUsage.USER_APPLICATIONS)
+ {
+ final Message message = WARN_ATTR_SYNTAX_ATTRTYPE_COLLECTIVE_IS_OPERATIONAL
+ .get(getNameOrOID());
+ warnings.add(message);
+ }
+
+ // If the attribute type is NO-USER-MODIFICATION, then it must not
+ // have a usage of userApplications.
+ if (isNoUserModification()
+ && getUsage() == AttributeUsage.USER_APPLICATIONS)
+ {
+ final Message message = WARN_ATTR_SYNTAX_ATTRTYPE_NO_USER_MOD_NOT_OPERATIONAL
+ .get(getNameOrOID());
+ warnings.add(message);
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/AttributeTypeSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/AttributeTypeSyntaxImpl.java
new file mode 100644
index 0000000..66967d8
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/AttributeTypeSyntaxImpl.java
@@ -0,0 +1,277 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_ATTRTYPE_EMPTY_VALUE;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_ATTRTYPE_EXPECTED_OPEN_PARENTHESIS;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_ILLEGAL_TOKEN;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_ATTRTYPE_INVALID_ATTRIBUTE_USAGE;
+import static org.opends.sdk.schema.SchemaConstants.EMR_OID_FIRST_COMPONENT_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_ATTRIBUTE_TYPE_NAME;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class defines the attribute type description syntax, which is
+ * used to hold attribute type definitions in the server schema. The
+ * format of this syntax is defined in RFC 2252.
+ */
+final class AttributeTypeSyntaxImpl extends AbstractSyntaxImpl
+{
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_OID_FIRST_COMPONENT_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_ATTRIBUTE_TYPE_NAME;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ try
+ {
+ final String definition = value.toString();
+ final SubstringReader reader = new SubstringReader(definition);
+
+ // We'll do this a character at a time. First, skip over any
+ // leading whitespace.
+ reader.skipWhitespaces();
+
+ if (reader.remaining() <= 0)
+ {
+ // This means that the definition was empty or contained only
+ // whitespace. That is illegal.
+ final Message message =
+ ERR_ATTR_SYNTAX_ATTRTYPE_EMPTY_VALUE.get();
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("AttributeTypeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ // The next character must be an open parenthesis. If it is not,
+ // then that is an error.
+ final char c = reader.read();
+ if (c != '(')
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_ATTRTYPE_EXPECTED_OPEN_PARENTHESIS.get(
+ definition, (reader.pos() - 1), String.valueOf(c));
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("AttributeTypeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ // Skip over any spaces immediately following the opening
+ // parenthesis.
+ reader.skipWhitespaces();
+
+ // The next set of characters must be the OID.
+ final String oid = SchemaUtils.readOID(reader);
+
+ // At this point, we should have a pretty specific syntax that
+ // describes what may come next, but some of the components are
+ // optional and it would be pretty easy to put something in the
+ // wrong order, so we will be very flexible about what we can
+ // accept. Just look at the next token, figure out what it is and
+ // how to treat what comes after it, then repeat until we get to
+ // the end of the definition. But before we start, set default
+ // values for everything else we might need to know.
+ while (true)
+ {
+ final String tokenName = SchemaUtils.readTokenName(reader);
+
+ if (tokenName == null)
+ {
+ // No more tokens.
+ break;
+ }
+ else if (tokenName.equalsIgnoreCase("name"))
+ {
+ SchemaUtils.readNameDescriptors(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("desc"))
+ {
+ // This specifies the description for the attribute type. It
+ // is an arbitrary string of characters enclosed in single
+ // quotes.
+ SchemaUtils.readQuotedString(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("obsolete"))
+ {
+ // This indicates whether the attribute type should be
+ // considered obsolete. We do not need to do any more parsing
+ // for this token.
+ }
+ else if (tokenName.equalsIgnoreCase("sup"))
+ {
+ // This specifies the name or OID of the superior attribute
+ // type from which this attribute type should inherit its
+ // properties.
+ SchemaUtils.readOID(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("equality"))
+ {
+ // This specifies the name or OID of the equality matching
+ // rule to use for this attribute type.
+ SchemaUtils.readOID(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("ordering"))
+ {
+ // This specifies the name or OID of the ordering matching
+ // rule to use for this attribute type.
+ SchemaUtils.readOID(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("substr"))
+ {
+ // This specifies the name or OID of the substring matching
+ // rule to use for this attribute type.
+ SchemaUtils.readOID(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("syntax"))
+ {
+ // This specifies the numeric OID of the syntax for this
+ // matching rule. It may optionally be immediately followed by
+ // an open curly brace, an integer definition, and a close
+ // curly brace to suggest the minimum number of characters
+ // that should be allowed in values of that type. This
+ // implementation will ignore any such length because it does
+ // not impose any practical limit on the length of attribute
+ // values.
+ SchemaUtils.readOIDLen(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("single-definition"))
+ {
+ // This indicates that attributes of this type are allowed to
+ // have at most one definition. We do not need any more
+ // parsing for this token.
+ }
+ else if (tokenName.equalsIgnoreCase("single-value"))
+ {
+ // This indicates that attributes of this type are allowed to
+ // have at most one value. We do not need any more parsing for
+ // this token.
+ }
+ else if (tokenName.equalsIgnoreCase("collective"))
+ {
+ // This indicates that attributes of this type are collective
+ // (i.e., have their values generated dynamically in some
+ // way). We do not need any more parsing for this token.
+ }
+ else if (tokenName.equalsIgnoreCase("no-user-modification"))
+ {
+ // This indicates that the values of attributes of this type
+ // are not to be modified by end users. We do not need any
+ // more parsing for this token.
+ }
+ else if (tokenName.equalsIgnoreCase("usage"))
+ {
+ // This specifies the usage string for this attribute type. It
+ // should be followed by one of the strings
+ // "userApplications", "directoryOperation",
+ // "distributedOperation", or "dSAOperation".
+ int length = 0;
+
+ reader.skipWhitespaces();
+ reader.mark();
+
+ while (reader.read() != ' ')
+ {
+ length++;
+ }
+
+ reader.reset();
+ final String usageStr = reader.read(length);
+ if (!usageStr.equalsIgnoreCase("userapplications")
+ && !usageStr.equalsIgnoreCase("directoryoperation")
+ && !usageStr.equalsIgnoreCase("distributedoperation")
+ && !usageStr.equalsIgnoreCase("dsaoperation"))
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_ATTRTYPE_INVALID_ATTRIBUTE_USAGE.get(
+ String.valueOf(oid), usageStr);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("AttributeTypeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+ }
+ else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+ {
+ // This must be a non-standard property and it must be
+ // followed by either a single definition in single quotes or
+ // an open parenthesis followed by one or more values in
+ // single quotes separated by spaces followed by a close
+ // parenthesis.
+ SchemaUtils.readExtensions(reader);
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("AttributeTypeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+ }
+ return true;
+ }
+ catch (final DecodeException de)
+ {
+ invalidReason.append(de.getMessageObject());
+ return false;
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/AttributeUsage.java b/sdk/src/org/opends/sdk/schema/AttributeUsage.java
new file mode 100644
index 0000000..ac6e3e6
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/AttributeUsage.java
@@ -0,0 +1,112 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+/**
+ * This enumeration defines the set of possible attribute usage values
+ * that may apply to an attribute type, as defined in RFC 2252.
+ */
+public enum AttributeUsage
+{
+ /**
+ * The attribute usage intended for user-defined attribute types.
+ */
+ USER_APPLICATIONS("userApplications", false),
+
+ /**
+ * The attribute usage intended for standard operational attributes.
+ */
+ DIRECTORY_OPERATION("directoryOperation", true),
+
+ /**
+ * The attribute usage intended for non-standard operational
+ * attributes shared among multiple DSAs.
+ */
+ DISTRIBUTED_OPERATION("distributedOperation", true),
+
+ /**
+ * The attribute usage intended for non-standard operational
+ * attributes used by a single DSA.
+ */
+ DSA_OPERATION("dSAOperation", true);
+
+ // The string representation of this attribute usage.
+ private final String usageString;
+
+ // Flag indicating whether or not the usage should be categorized as
+ // operational.
+ private final boolean isOperational;
+
+
+
+ /**
+ * Creates a new attribute usage with the provided string
+ * representation.
+ *
+ * @param usageString
+ * The string representation of this attribute usage.
+ * @param isOperational
+ * <code>true</code> if attributes having this attribute
+ * usage are operational, or <code>false</code> otherwise.
+ */
+ private AttributeUsage(String usageString, boolean isOperational)
+ {
+ this.usageString = usageString;
+ this.isOperational = isOperational;
+ }
+
+
+
+ /**
+ * Determine whether or not attributes having this attribute usage are
+ * operational.
+ *
+ * @return Returns <code>true</code> if attributes having this
+ * attribute usage are operational, or <code>false</code>
+ * otherwise.
+ */
+ public boolean isOperational()
+ {
+ return isOperational;
+ }
+
+
+
+ /**
+ * Retrieves a string representation of this attribute usage.
+ *
+ * @return A string representation of this attribute usage.
+ */
+ @Override
+ public String toString()
+ {
+ return usageString;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/AuthPasswordExactEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/AuthPasswordExactEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..621b334
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/AuthPasswordExactEqualityMatchingRuleImpl.java
@@ -0,0 +1,62 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the authPasswordMatch matching rule defined in
+ * RFC 3112.
+ */
+final class AuthPasswordExactEqualityMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value) throws DecodeException
+ {
+ final StringBuilder[] authPWComponents =
+ AuthPasswordSyntaxImpl.decodeAuthPassword(value.toString());
+
+ final StringBuilder normalizedValue =
+ new StringBuilder(2 + authPWComponents[0].length()
+ + authPWComponents[1].length()
+ + authPWComponents[2].length());
+ normalizedValue.append(authPWComponents[0]);
+ normalizedValue.append('$');
+ normalizedValue.append(authPWComponents[1]);
+ normalizedValue.append('$');
+ normalizedValue.append(authPWComponents[2]);
+
+ return ByteString.valueOf(normalizedValue.toString());
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/AuthPasswordSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/AuthPasswordSyntaxImpl.java
new file mode 100644
index 0000000..6c98bfa
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/AuthPasswordSyntaxImpl.java
@@ -0,0 +1,340 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.schema.SchemaConstants.EMR_AUTH_PASSWORD_EXACT_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_AUTH_PASSWORD_NAME;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class defines the auth password attribute syntax, which is
+ * defined in RFC 3112 and is used to hold authentication information.
+ * Only equality matching will be allowed by default.
+ */
+final class AuthPasswordSyntaxImpl extends AbstractSyntaxImpl
+{
+ /**
+ * Decodes the provided authentication password value into its
+ * component parts.
+ *
+ * @param authPasswordValue
+ * The authentication password value to be decoded.
+ * @return A three-element array, containing the scheme, authInfo, and
+ * authValue components of the given string, in that order.
+ * @throws DecodeException
+ * If a problem is encountered while attempting to decode
+ * the value.
+ */
+ static StringBuilder[] decodeAuthPassword(String authPasswordValue)
+ throws DecodeException
+ {
+ // Create placeholders for the values to return.
+ final StringBuilder scheme = new StringBuilder();
+ final StringBuilder authInfo = new StringBuilder();
+ final StringBuilder authValue = new StringBuilder();
+
+ // First, ignore any leading whitespace.
+ final int length = authPasswordValue.length();
+ int pos = 0;
+ while (pos < length && authPasswordValue.charAt(pos) == ' ')
+ {
+ pos++;
+ }
+
+ // The next set of characters will be the scheme, which must consist
+ // only of digits, uppercase alphabetic characters, dash, period,
+ // slash, and underscore characters. It must be immediately followed
+ // by one or more spaces or a dollar sign.
+ readScheme: while (pos < length)
+ {
+ final char c = authPasswordValue.charAt(pos);
+
+ switch (c)
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ case 'G':
+ case 'H':
+ case 'I':
+ case 'J':
+ case 'K':
+ case 'L':
+ case 'M':
+ case 'N':
+ case 'O':
+ case 'P':
+ case 'Q':
+ case 'R':
+ case 'S':
+ case 'T':
+ case 'U':
+ case 'V':
+ case 'W':
+ case 'X':
+ case 'Y':
+ case 'Z':
+ case '-':
+ case '.':
+ case '/':
+ case '_':
+ scheme.append(c);
+ pos++;
+ break;
+ case ' ':
+ case '$':
+ break readScheme;
+ default:
+ final Message message =
+ ERR_ATTR_SYNTAX_AUTHPW_INVALID_SCHEME_CHAR.get(pos);
+ throw DecodeException.error(message);
+ }
+ }
+
+ // The scheme must consist of at least one character.
+ if (scheme.length() == 0)
+ {
+ final Message message = ERR_ATTR_SYNTAX_AUTHPW_NO_SCHEME.get();
+ throw DecodeException.error(message);
+ }
+
+ // Ignore any spaces before the dollar sign separator. Then read the
+ // dollar sign and ignore any trailing spaces.
+ while (pos < length && authPasswordValue.charAt(pos) == ' ')
+ {
+ pos++;
+ }
+
+ if (pos < length && authPasswordValue.charAt(pos) == '$')
+ {
+ pos++;
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_AUTHPW_NO_SCHEME_SEPARATOR.get();
+ throw DecodeException.error(message);
+ }
+
+ while (pos < length && authPasswordValue.charAt(pos) == ' ')
+ {
+ pos++;
+ }
+
+ // The next component must be the authInfo element, containing only
+ // printable characters other than the dollar sign and space
+ // character.
+ readAuthInfo: while (pos < length)
+ {
+ final char c = authPasswordValue.charAt(pos);
+ if (c == ' ' || c == '$')
+ {
+ break readAuthInfo;
+ }
+ else if (PrintableStringSyntaxImpl.isPrintableCharacter(c))
+ {
+ authInfo.append(c);
+ pos++;
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_AUTHPW_INVALID_AUTH_INFO_CHAR.get(pos);
+ throw DecodeException.error(message);
+ }
+ }
+
+ // The authInfo element must consist of at least one character.
+ if (scheme.length() == 0)
+ {
+ final Message message = ERR_ATTR_SYNTAX_AUTHPW_NO_AUTH_INFO.get();
+ throw DecodeException.error(message);
+ }
+
+ // Ignore any spaces before the dollar sign separator. Then read the
+ // dollar sign and ignore any trailing spaces.
+ while (pos < length && authPasswordValue.charAt(pos) == ' ')
+ {
+ pos++;
+ }
+
+ if (pos < length && authPasswordValue.charAt(pos) == '$')
+ {
+ pos++;
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_AUTHPW_NO_AUTH_INFO_SEPARATOR.get();
+ throw DecodeException.error(message);
+ }
+
+ while (pos < length && authPasswordValue.charAt(pos) == ' ')
+ {
+ pos++;
+ }
+
+ // The final component must be the authValue element, containing
+ // only printable characters other than the dollar sign and space
+ // character.
+ while (pos < length)
+ {
+ final char c = authPasswordValue.charAt(pos);
+ if (c == ' ' || c == '$')
+ {
+ break;
+ }
+ else if (PrintableStringSyntaxImpl.isPrintableCharacter(c))
+ {
+ authValue.append(c);
+ pos++;
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_AUTHPW_INVALID_AUTH_VALUE_CHAR.get(pos);
+ throw DecodeException.error(message);
+ }
+ }
+
+ // The authValue element must consist of at least one character.
+ if (scheme.length() == 0)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_AUTHPW_NO_AUTH_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ // The only characters remaining must be whitespace.
+ while (pos < length)
+ {
+ final char c = authPasswordValue.charAt(pos);
+ if (c == ' ')
+ {
+ pos++;
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_AUTHPW_INVALID_TRAILING_CHAR.get(pos);
+ throw DecodeException.error(message);
+ }
+ }
+
+ // If we've gotten here, then everything must be OK.
+ return new StringBuilder[] { scheme, authInfo, authValue };
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is encoded using the auth
+ * password syntax.
+ *
+ * @param value
+ * The value for which to make the determination.
+ * @return <CODE>true</CODE> if the value appears to be encoded using
+ * the auth password syntax, or <CODE>false</CODE> if not.
+ */
+ static boolean isEncoded(ByteSequence value)
+ {
+ // FIXME -- Make this more efficient, and don't use exceptions for
+ // flow control.
+
+ try
+ {
+ decodeAuthPassword(value.toString());
+ return true;
+ }
+ catch (final Exception e)
+ {
+ return false;
+ }
+ }
+
+
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_AUTH_PASSWORD_EXACT_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_AUTH_PASSWORD_NAME;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ try
+ {
+ decodeAuthPassword(value.toString());
+ return true;
+ }
+ catch (final DecodeException de)
+ {
+ invalidReason.append(de.getMessageObject());
+ return false;
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/BinarySyntaxImpl.java b/sdk/src/org/opends/sdk/schema/BinarySyntaxImpl.java
new file mode 100644
index 0000000..db1e9d3
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/BinarySyntaxImpl.java
@@ -0,0 +1,98 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.EMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_BINARY_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class defines the binary attribute syntax, which is essentially
+ * a byte array using very strict matching. Equality, ordering, and
+ * substring matching will be allowed by default.
+ */
+final class BinarySyntaxImpl extends AbstractSyntaxImpl
+{
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_OCTET_STRING_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_BINARY_NAME;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_OCTET_STRING_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // All values will be acceptable for the binary syntax.
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/BitStringEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/BitStringEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..36ce4b1
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/BitStringEqualityMatchingRuleImpl.java
@@ -0,0 +1,89 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_BIT_STRING_INVALID_BIT;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_BIT_STRING_NOT_QUOTED;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_BIT_STRING_TOO_SHORT;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the bitStringMatch matching rule defined in X.520
+ * and referenced in RFC 2252.
+ */
+final class BitStringEqualityMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value) throws DecodeException
+ {
+ final String valueString = value.toString().toUpperCase();
+
+ final int length = valueString.length();
+ if (length < 3)
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_BIT_STRING_TOO_SHORT.get(value.toString());
+ throw DecodeException.error(message);
+ }
+
+ if (valueString.charAt(0) != '\''
+ || valueString.charAt(length - 2) != '\''
+ || valueString.charAt(length - 1) != 'B')
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_BIT_STRING_NOT_QUOTED.get(value.toString());
+ throw DecodeException.error(message);
+ }
+
+ for (int i = 1; i < length - 2; i++)
+ {
+ switch (valueString.charAt(i))
+ {
+ case '0':
+ case '1':
+ // These characters are fine.
+ break;
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_BIT_STRING_INVALID_BIT.get(value
+ .toString(), String.valueOf(valueString.charAt(i)));
+ throw DecodeException.error(message);
+ }
+ }
+
+ return ByteString.valueOf(valueString);
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/BitStringSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/BitStringSyntaxImpl.java
new file mode 100644
index 0000000..92ff2e7
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/BitStringSyntaxImpl.java
@@ -0,0 +1,127 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_BIT_STRING_INVALID_BIT;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_BIT_STRING_NOT_QUOTED;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_BIT_STRING_TOO_SHORT;
+import static org.opends.sdk.schema.SchemaConstants.EMR_BIT_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_BIT_STRING_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class defines the bit string attribute syntax, which is
+ * comprised of a string of binary digits surrounded by single quotes
+ * and followed by a capital letter "B" (e.g., '101001'B).
+ */
+final class BitStringSyntaxImpl extends AbstractSyntaxImpl
+{
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_BIT_STRING_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_BIT_STRING_NAME;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ final String valueString = value.toString().toUpperCase();
+
+ final int length = valueString.length();
+ if (length < 3)
+ {
+ invalidReason.append(WARN_ATTR_SYNTAX_BIT_STRING_TOO_SHORT
+ .get(value.toString()));
+ return false;
+ }
+
+ if (valueString.charAt(0) != '\''
+ || valueString.charAt(length - 2) != '\''
+ || valueString.charAt(length - 1) != 'B')
+ {
+ invalidReason.append(WARN_ATTR_SYNTAX_BIT_STRING_NOT_QUOTED
+ .get(value.toString()));
+ return false;
+ }
+
+ for (int i = 1; i < length - 2; i++)
+ {
+ switch (valueString.charAt(i))
+ {
+ case '0':
+ case '1':
+ // These characters are fine.
+ break;
+ default:
+ invalidReason.append(WARN_ATTR_SYNTAX_BIT_STRING_INVALID_BIT
+ .get(value.toString(), String
+ .valueOf(valueString.charAt(i))));
+ return false;
+ }
+ }
+
+ // If we've gotten here, then everything is fine.
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/BooleanEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/BooleanEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..0c52bcd
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/BooleanEqualityMatchingRuleImpl.java
@@ -0,0 +1,64 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_ILLEGAL_BOOLEAN;
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the booleanMatch matching rule defined in X.520
+ * and referenced in RFC 4519.
+ */
+final class BooleanEqualityMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value) throws DecodeException
+ {
+ final String valueString = value.toString().toUpperCase();
+ if (valueString.equals("TRUE") || valueString.equals("YES")
+ || valueString.equals("ON") || valueString.equals("1"))
+ {
+ return SchemaConstants.TRUE_VALUE;
+ }
+ else if (valueString.equals("FALSE") || valueString.equals("NO")
+ || valueString.equals("OFF") || valueString.equals("0"))
+ {
+ return SchemaConstants.FALSE_VALUE;
+ }
+
+ throw DecodeException.error(WARN_ATTR_SYNTAX_ILLEGAL_BOOLEAN
+ .get(value.toString()));
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/BooleanSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/BooleanSyntaxImpl.java
new file mode 100644
index 0000000..5d9a3cc
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/BooleanSyntaxImpl.java
@@ -0,0 +1,105 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_ILLEGAL_BOOLEAN;
+import static org.opends.sdk.schema.SchemaConstants.EMR_BOOLEAN_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_BOOLEAN_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class defines the Boolean attribute syntax, which only allows
+ * values of "TRUE" or "FALSE" (although this implementation is more
+ * flexible and will also allow "YES", "ON", or "1" instead of "TRUE",
+ * or "NO", "OFF", or "0" instead of "FALSE"). Only equality matching is
+ * allowed by default for this syntax.
+ */
+final class BooleanSyntaxImpl extends AbstractSyntaxImpl
+{
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_BOOLEAN_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_BOOLEAN_NAME;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ final String valueString = value.toString().toUpperCase();
+
+ final boolean returnValue =
+ valueString.equals("TRUE") || valueString.equals("YES")
+ || valueString.equals("ON") || valueString.equals("1")
+ || valueString.equals("FALSE") || valueString.equals("NO")
+ || valueString.equals("OFF") || valueString.equals("0");
+
+ if (!returnValue)
+ {
+ invalidReason.append(WARN_ATTR_SYNTAX_ILLEGAL_BOOLEAN.get(value
+ .toString()));
+ }
+
+ return returnValue;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CaseExactEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/CaseExactEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..bda2301
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CaseExactEqualityMatchingRuleImpl.java
@@ -0,0 +1,83 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.NO_CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the caseExactMatch matching rule defined in X.520
+ * and referenced in RFC 4519.
+ */
+final class CaseExactEqualityMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value)
+ {
+ final StringBuilder buffer = new StringBuilder();
+ prepareUnicode(buffer, value, TRIM, NO_CASE_FOLD);
+
+ final int bufferLength = buffer.length();
+ if (bufferLength == 0)
+ {
+ if (value.length() > 0)
+ {
+ // This should only happen if the value is composed entirely of
+ // spaces. In that case, the normalized value is a single space.
+ return SchemaConstants.SINGLE_SPACE_VALUE;
+ }
+ else
+ {
+ // The value is empty, so it is already normalized.
+ return ByteString.empty();
+ }
+ }
+
+ // Replace any consecutive spaces with a single space.
+ for (int pos = bufferLength - 1; pos > 0; pos--)
+ {
+ if (buffer.charAt(pos) == ' ')
+ {
+ if (buffer.charAt(pos - 1) == ' ')
+ {
+ buffer.delete(pos, pos + 1);
+ }
+ }
+ }
+
+ return ByteString.valueOf(buffer.toString());
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CaseExactIA5EqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/CaseExactIA5EqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..0df9ee9
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CaseExactIA5EqualityMatchingRuleImpl.java
@@ -0,0 +1,98 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_IA5_ILLEGAL_CHARACTER;
+import static org.opends.sdk.util.StringPrepProfile.NO_CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the caseExactIA5Match matching rule defined in
+ * RFC 2252.
+ */
+final class CaseExactIA5EqualityMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value) throws DecodeException
+ {
+ final StringBuilder buffer = new StringBuilder();
+ prepareUnicode(buffer, value, TRIM, NO_CASE_FOLD);
+
+ final int bufferLength = buffer.length();
+ if (bufferLength == 0)
+ {
+ if (value.length() > 0)
+ {
+ // This should only happen if the value is composed entirely of
+ // spaces. In that case, the normalized value is a single space.
+ return SchemaConstants.SINGLE_SPACE_VALUE;
+ }
+ else
+ {
+ // The value is empty, so it is already normalized.
+ return ByteString.empty();
+ }
+ }
+
+ // Replace any consecutive spaces with a single space and watch out
+ // for non-ASCII characters.
+ for (int pos = bufferLength - 1; pos > 0; pos--)
+ {
+ final char c = buffer.charAt(pos);
+ if (c == ' ')
+ {
+ if (buffer.charAt(pos - 1) == ' ')
+ {
+ buffer.delete(pos, pos + 1);
+ }
+ }
+ else if ((c & 0x7F) != c)
+ {
+ // This is not a valid character for an IA5 string. If strict
+ // syntax enforcement is enabled, then we'll throw an exception.
+ // Otherwise, we'll get rid of the character.
+ final Message message =
+ WARN_ATTR_SYNTAX_IA5_ILLEGAL_CHARACTER.get(
+ value.toString(), String.valueOf(c));
+ throw DecodeException.error(message);
+ }
+ }
+
+ return ByteString.valueOf(buffer.toString());
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CaseExactIA5SubstringMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/CaseExactIA5SubstringMatchingRuleImpl.java
new file mode 100644
index 0000000..a70abe7
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CaseExactIA5SubstringMatchingRuleImpl.java
@@ -0,0 +1,117 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_IA5_ILLEGAL_CHARACTER;
+import static org.opends.sdk.util.StringPrepProfile.NO_CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the caseExactIA5SubstringsMatch matching rule.
+ * This matching rule actually isn't defined in any official
+ * specification, but some directory vendors do provide an
+ * implementation using an OID from their own private namespace.
+ */
+final class CaseExactIA5SubstringMatchingRuleImpl extends
+ AbstractSubstringMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value) throws DecodeException
+ {
+ return normalize(TRIM, value);
+ }
+
+
+
+ @Override
+ ByteString normalizeSubString(Schema schema, ByteSequence value)
+ throws DecodeException
+ {
+ return normalize(false, value);
+ }
+
+
+
+ private ByteString normalize(boolean trim, ByteSequence value)
+ throws DecodeException
+ {
+ final StringBuilder buffer = new StringBuilder();
+ prepareUnicode(buffer, value, trim, NO_CASE_FOLD);
+
+ final int bufferLength = buffer.length();
+ if (bufferLength == 0)
+ {
+ if (value.length() > 0)
+ {
+ // This should only happen if the value is composed entirely of
+ // spaces. In that case, the normalized value is a single space.
+ return SchemaConstants.SINGLE_SPACE_VALUE;
+ }
+ else
+ {
+ // The value is empty, so it is already normalized.
+ return ByteString.empty();
+ }
+ }
+
+ // Replace any consecutive spaces with a single space and watch out
+ // for non-ASCII characters.
+ for (int pos = bufferLength - 1; pos > 0; pos--)
+ {
+ final char c = buffer.charAt(pos);
+ if (c == ' ')
+ {
+ if (buffer.charAt(pos - 1) == ' ')
+ {
+ buffer.delete(pos, pos + 1);
+ }
+ }
+ else if ((c & 0x7F) != c)
+ {
+ // This is not a valid character for an IA5 string. If strict
+ // syntax enforcement is enabled, then we'll throw an exception.
+ // Otherwise, we'll get rid of the character.
+ final Message message =
+ WARN_ATTR_SYNTAX_IA5_ILLEGAL_CHARACTER.get(
+ value.toString(), String.valueOf(c));
+ throw DecodeException.error(message);
+ }
+ }
+
+ return ByteString.valueOf(buffer.toString());
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CaseExactOrderingMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/CaseExactOrderingMatchingRuleImpl.java
new file mode 100644
index 0000000..f22d047
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CaseExactOrderingMatchingRuleImpl.java
@@ -0,0 +1,83 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.NO_CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the caseExactOrderingMatch matching rule defined
+ * in X.520 and referenced in RFC 4519.
+ */
+final class CaseExactOrderingMatchingRuleImpl extends
+ AbstractOrderingMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value)
+ {
+ final StringBuilder buffer = new StringBuilder();
+ prepareUnicode(buffer, value, TRIM, NO_CASE_FOLD);
+
+ final int bufferLength = buffer.length();
+ if (bufferLength == 0)
+ {
+ if (value.length() > 0)
+ {
+ // This should only happen if the value is composed entirely of
+ // spaces. In that case, the normalized value is a single space.
+ return SchemaConstants.SINGLE_SPACE_VALUE;
+ }
+ else
+ {
+ // The value is empty, so it is already normalized.
+ return ByteString.empty();
+ }
+ }
+
+ // Replace any consecutive spaces with a single space.
+ for (int pos = bufferLength - 1; pos > 0; pos--)
+ {
+ if (buffer.charAt(pos) == ' ')
+ {
+ if (buffer.charAt(pos - 1) == ' ')
+ {
+ buffer.delete(pos, pos + 1);
+ }
+ }
+ }
+
+ return ByteString.valueOf(buffer.toString());
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CaseExactSubstringMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/CaseExactSubstringMatchingRuleImpl.java
new file mode 100644
index 0000000..33d53c8
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CaseExactSubstringMatchingRuleImpl.java
@@ -0,0 +1,100 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.NO_CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the caseExactSubstringsMatch matching rule defined
+ * in X.520 and referenced in RFC 2252.
+ */
+final class CaseExactSubstringMatchingRuleImpl extends
+ AbstractSubstringMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value)
+ {
+ return normalize(TRIM, value);
+ }
+
+
+
+ @Override
+ ByteString normalizeSubString(Schema schema, ByteSequence value)
+ throws DecodeException
+ {
+ return normalize(false, value);
+ }
+
+
+
+ private ByteString normalize(boolean trim, ByteSequence value)
+ {
+ final StringBuilder buffer = new StringBuilder();
+ prepareUnicode(buffer, value, trim, NO_CASE_FOLD);
+
+ final int bufferLength = buffer.length();
+ if (bufferLength == 0)
+ {
+ if (value.length() > 0)
+ {
+ // This should only happen if the value is composed entirely of
+ // spaces. In that case, the normalized value is a single space.
+ return SchemaConstants.SINGLE_SPACE_VALUE;
+ }
+ else
+ {
+ // The value is empty, so it is already normalized.
+ return ByteString.empty();
+ }
+ }
+
+ // Replace any consecutive spaces with a single space.
+ for (int pos = bufferLength - 1; pos > 0; pos--)
+ {
+ if (buffer.charAt(pos) == ' ')
+ {
+ if (buffer.charAt(pos - 1) == ' ')
+ {
+ buffer.delete(pos, pos + 1);
+ }
+ }
+ }
+
+ return ByteString.valueOf(buffer.toString());
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CaseIgnoreEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/CaseIgnoreEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..b5b9bc8
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CaseIgnoreEqualityMatchingRuleImpl.java
@@ -0,0 +1,83 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the caseIgnoreMatch matching rule defined in X.520
+ * and referenced in RFC 2252.
+ */
+final class CaseIgnoreEqualityMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value)
+ {
+ final StringBuilder buffer = new StringBuilder();
+ prepareUnicode(buffer, value, TRIM, CASE_FOLD);
+
+ final int bufferLength = buffer.length();
+ if (bufferLength == 0)
+ {
+ if (value.length() > 0)
+ {
+ // This should only happen if the value is composed entirely of
+ // spaces. In that case, the normalized value is a single space.
+ return SchemaConstants.SINGLE_SPACE_VALUE;
+ }
+ else
+ {
+ // The value is empty, so it is already normalized.
+ return ByteString.empty();
+ }
+ }
+
+ // Replace any consecutive spaces with a single space.
+ for (int pos = bufferLength - 1; pos > 0; pos--)
+ {
+ if (buffer.charAt(pos) == ' ')
+ {
+ if (buffer.charAt(pos - 1) == ' ')
+ {
+ buffer.delete(pos, pos + 1);
+ }
+ }
+ }
+
+ return ByteString.valueOf(buffer.toString());
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CaseIgnoreIA5EqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/CaseIgnoreIA5EqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..c27caf9
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CaseIgnoreIA5EqualityMatchingRuleImpl.java
@@ -0,0 +1,98 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_IA5_ILLEGAL_CHARACTER;
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the caseIgnoreIA5Match matching rule defined in
+ * RFC 2252.
+ */
+final class CaseIgnoreIA5EqualityMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value) throws DecodeException
+ {
+ final StringBuilder buffer = new StringBuilder();
+ prepareUnicode(buffer, value, TRIM, CASE_FOLD);
+
+ final int bufferLength = buffer.length();
+ if (bufferLength == 0)
+ {
+ if (value.length() > 0)
+ {
+ // This should only happen if the value is composed entirely of
+ // spaces. In that case, the normalized value is a single space.
+ return SchemaConstants.SINGLE_SPACE_VALUE;
+ }
+ else
+ {
+ // The value is empty, so it is already normalized.
+ return ByteString.empty();
+ }
+ }
+
+ // Replace any consecutive spaces with a single space and watch out
+ // for non-ASCII characters.
+ for (int pos = bufferLength - 1; pos > 0; pos--)
+ {
+ final char c = buffer.charAt(pos);
+ if (c == ' ')
+ {
+ if (buffer.charAt(pos - 1) == ' ')
+ {
+ buffer.delete(pos, pos + 1);
+ }
+ }
+ else if ((c & 0x7F) != c)
+ {
+ // This is not a valid character for an IA5 string. If strict
+ // syntax enforcement is enabled, then we'll throw an exception.
+ // Otherwise, we'll get rid of the character.
+ final Message message =
+ WARN_ATTR_SYNTAX_IA5_ILLEGAL_CHARACTER.get(
+ value.toString(), String.valueOf(c));
+ throw DecodeException.error(message);
+ }
+ }
+
+ return ByteString.valueOf(buffer.toString());
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CaseIgnoreIA5SubstringMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/CaseIgnoreIA5SubstringMatchingRuleImpl.java
new file mode 100644
index 0000000..b205035
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CaseIgnoreIA5SubstringMatchingRuleImpl.java
@@ -0,0 +1,115 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_IA5_ILLEGAL_CHARACTER;
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the caseIgnoreIA5SubstringsMatch matching rule
+ * defined in RFC 2252.
+ */
+final class CaseIgnoreIA5SubstringMatchingRuleImpl extends
+ AbstractSubstringMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value) throws DecodeException
+ {
+ return normalize(TRIM, value);
+ }
+
+
+
+ @Override
+ ByteString normalizeSubString(Schema schema, ByteSequence value)
+ throws DecodeException
+ {
+ return normalize(false, value);
+ }
+
+
+
+ private ByteString normalize(boolean trim, ByteSequence value)
+ throws DecodeException
+ {
+ final StringBuilder buffer = new StringBuilder();
+ prepareUnicode(buffer, value, trim, CASE_FOLD);
+
+ final int bufferLength = buffer.length();
+ if (bufferLength == 0)
+ {
+ if (value.length() > 0)
+ {
+ // This should only happen if the value is composed entirely of
+ // spaces. In that case, the normalized value is a single space.
+ return SchemaConstants.SINGLE_SPACE_VALUE;
+ }
+ else
+ {
+ // The value is empty, so it is already normalized.
+ return ByteString.empty();
+ }
+ }
+
+ // Replace any consecutive spaces with a single space and watch out
+ // for non-ASCII characters.
+ for (int pos = bufferLength - 1; pos > 0; pos--)
+ {
+ final char c = buffer.charAt(pos);
+ if (c == ' ')
+ {
+ if (buffer.charAt(pos - 1) == ' ')
+ {
+ buffer.delete(pos, pos + 1);
+ }
+ }
+ else if ((c & 0x7F) != c)
+ {
+ // This is not a valid character for an IA5 string. If strict
+ // syntax enforcement is enabled, then we'll throw an exception.
+ // Otherwise, we'll get rid of the character.
+ final Message message =
+ WARN_ATTR_SYNTAX_IA5_ILLEGAL_CHARACTER.get(
+ value.toString(), String.valueOf(c));
+ throw DecodeException.error(message);
+ }
+ }
+
+ return ByteString.valueOf(buffer.toString());
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CaseIgnoreListEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/CaseIgnoreListEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..e0018bd
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CaseIgnoreListEqualityMatchingRuleImpl.java
@@ -0,0 +1,96 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the caseIgnoreListMatch matching rule defined
+ * in X.520 and referenced in RFC 2252.
+ */
+final class CaseIgnoreListEqualityMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value)
+ {
+ final StringBuilder buffer = new StringBuilder();
+ prepareUnicode(buffer, value, TRIM, CASE_FOLD);
+
+ final int bufferLength = buffer.length();
+ if (bufferLength == 0)
+ {
+ if (value.length() > 0)
+ {
+ // This should only happen if the value is composed entirely of
+ // spaces. In that case, the normalized value is a single space.
+ return SchemaConstants.SINGLE_SPACE_VALUE;
+ }
+ else
+ {
+ // The value is empty, so it is already normalized.
+ return ByteString.empty();
+ }
+ }
+
+ // Replace any consecutive spaces with a single space. Any spaces
+ // around a dollar sign will also be removed.
+ for (int pos = bufferLength - 1; pos > 0; pos--)
+ {
+ if (buffer.charAt(pos) == ' ')
+ {
+ final char c = buffer.charAt(pos - 1);
+ if (c == ' ')
+ {
+ buffer.delete(pos, pos + 1);
+ }
+ else if (c == '$')
+ {
+ if (pos <= 1 || buffer.charAt(pos - 2) != '\\')
+ {
+ buffer.delete(pos, pos + 1);
+ }
+ }
+ else if (buffer.charAt(pos + 1) == '$')
+ {
+ buffer.delete(pos, pos + 1);
+ }
+ }
+ }
+
+ return ByteString.valueOf(buffer.toString());
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CaseIgnoreListSubstringMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/CaseIgnoreListSubstringMatchingRuleImpl.java
new file mode 100644
index 0000000..f95f1d4
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CaseIgnoreListSubstringMatchingRuleImpl.java
@@ -0,0 +1,140 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StringPrepProfile;
+
+
+
+/**
+ * This class implements the caseIgnoreListSubstringsMatch matching rule
+ * defined in X.520 and referenced in RFC 2252.
+ */
+final class CaseIgnoreListSubstringMatchingRuleImpl extends
+ AbstractSubstringMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value)
+ {
+ final StringBuilder buffer = new StringBuilder();
+ prepareUnicode(buffer, value, StringPrepProfile.TRIM, CASE_FOLD);
+
+ final int bufferLength = buffer.length();
+ if (bufferLength == 0)
+ {
+ if (value.length() > 0)
+ {
+ // This should only happen if the value is composed entirely of
+ // spaces. In that case, the normalized value is a single space.
+ return SchemaConstants.SINGLE_SPACE_VALUE;
+ }
+ else
+ {
+ // The value is empty, so it is already normalized.
+ return ByteString.empty();
+ }
+ }
+
+ // Replace any consecutive spaces with a single space. Any spaces
+ // around a dollar sign will also be removed.
+ for (int pos = bufferLength - 1; pos > 0; pos--)
+ {
+ if (buffer.charAt(pos) == ' ')
+ {
+ final char c = buffer.charAt(pos - 1);
+ if (c == ' ')
+ {
+ buffer.delete(pos, pos + 1);
+ }
+ else if (c == '$')
+ {
+ if (pos <= 1 || buffer.charAt(pos - 2) != '\\')
+ {
+ buffer.delete(pos, pos + 1);
+ }
+ }
+ else if (buffer.charAt(pos + 1) == '$')
+ {
+ buffer.delete(pos, pos + 1);
+ }
+ }
+ }
+
+ return ByteString.valueOf(buffer.toString());
+ }
+
+
+
+ @Override
+ ByteString normalizeSubString(Schema schema, ByteSequence value)
+ throws DecodeException
+ {
+ // In this case, the process for normalizing a substring is the same
+ // as normalizing a full value with the exception that it may
+ // include an opening or trailing space.
+ final StringBuilder buffer = new StringBuilder();
+ prepareUnicode(buffer, value, false, CASE_FOLD);
+
+ final int bufferLength = buffer.length();
+ if (bufferLength == 0)
+ {
+ if (value.length() > 0)
+ {
+ // This should only happen if the value is composed entirely of
+ // spaces. In that case, the normalized value is a single space.
+ return SchemaConstants.SINGLE_SPACE_VALUE;
+ }
+ else
+ {
+ // The value is empty, so it is already normalized.
+ return value.toByteString();
+ }
+ }
+
+ // Replace any consecutive spaces with a single space.
+ for (int pos = bufferLength - 1; pos > 0; pos--)
+ {
+ if (buffer.charAt(pos) == ' ')
+ {
+ if (buffer.charAt(pos - 1) == ' ')
+ {
+ buffer.delete(pos, pos + 1);
+ }
+ }
+ }
+
+ return ByteString.valueOf(buffer.toString());
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CaseIgnoreOrderingMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/CaseIgnoreOrderingMatchingRuleImpl.java
new file mode 100644
index 0000000..292e17c
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CaseIgnoreOrderingMatchingRuleImpl.java
@@ -0,0 +1,83 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the caseIgnoreOrderingMatch matching rule defined
+ * in X.520 and referenced in RFC 2252.
+ */
+final class CaseIgnoreOrderingMatchingRuleImpl extends
+ AbstractOrderingMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value)
+ {
+ final StringBuilder buffer = new StringBuilder();
+ prepareUnicode(buffer, value, TRIM, CASE_FOLD);
+
+ final int bufferLength = buffer.length();
+ if (bufferLength == 0)
+ {
+ if (value.length() > 0)
+ {
+ // This should only happen if the value is composed entirely of
+ // spaces. In that case, the normalized value is a single space.
+ return SchemaConstants.SINGLE_SPACE_VALUE;
+ }
+ else
+ {
+ // The value is empty, so it is already normalized.
+ return ByteString.empty();
+ }
+ }
+
+ // Replace any consecutive spaces with a single space.
+ for (int pos = bufferLength - 1; pos > 0; pos--)
+ {
+ if (buffer.charAt(pos) == ' ')
+ {
+ if (buffer.charAt(pos - 1) == ' ')
+ {
+ buffer.delete(pos, pos + 1);
+ }
+ }
+ }
+
+ return ByteString.valueOf(buffer.toString());
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CaseIgnoreSubstringMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/CaseIgnoreSubstringMatchingRuleImpl.java
new file mode 100644
index 0000000..0ed9ce0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CaseIgnoreSubstringMatchingRuleImpl.java
@@ -0,0 +1,101 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StringPrepProfile;
+
+
+
+/**
+ * This class defines the caseIgnoreSubstringsMatch matching rule
+ * defined in X.520 and referenced in RFC 2252.
+ */
+final class CaseIgnoreSubstringMatchingRuleImpl extends
+ AbstractSubstringMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value)
+ {
+ return normalize(TRIM, value);
+ }
+
+
+
+ @Override
+ ByteString normalizeSubString(Schema schema, ByteSequence value)
+ throws DecodeException
+ {
+ return normalize(false, value);
+ }
+
+
+
+ private ByteString normalize(boolean trim, ByteSequence value)
+ {
+
+ final StringBuilder buffer = new StringBuilder();
+ StringPrepProfile.prepareUnicode(buffer, value, trim, CASE_FOLD);
+
+ final int bufferLength = buffer.length();
+ if (bufferLength == 0)
+ {
+ if (value.length() > 0)
+ {
+ // This should only happen if the value is composed entirely of
+ // spaces. In that case, the normalized value is a single space.
+ return SchemaConstants.SINGLE_SPACE_VALUE;
+ }
+ else
+ {
+ // The value is empty, so it is already normalized.
+ return value.toByteString();
+ }
+ }
+
+ // Replace any consecutive spaces with a single space.
+ for (int pos = bufferLength - 1; pos > 0; pos--)
+ {
+ if (buffer.charAt(pos) == ' ')
+ {
+ if (buffer.charAt(pos - 1) == ' ')
+ {
+ buffer.delete(pos, pos + 1);
+ }
+ }
+ }
+
+ return ByteString.valueOf(buffer.toString());
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CertificateListSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/CertificateListSyntaxImpl.java
new file mode 100644
index 0000000..6041dad
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CertificateListSyntaxImpl.java
@@ -0,0 +1,108 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.EMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_CERTLIST_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the certificate list attribute syntax. This
+ * should be restricted to holding only X.509 certificate lists, but we
+ * will accept any set of bytes. It will be treated much like the octet
+ * string attribute syntax.
+ */
+final class CertificateListSyntaxImpl extends AbstractSyntaxImpl
+{
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_OCTET_STRING_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_CERTLIST_NAME;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_OCTET_STRING_OID;
+ }
+
+
+
+ @Override
+ public boolean isBEREncodingRequired()
+ {
+ return true;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // All values will be acceptable for the certificate list syntax.
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CertificatePairSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/CertificatePairSyntaxImpl.java
new file mode 100644
index 0000000..6c4ebdd
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CertificatePairSyntaxImpl.java
@@ -0,0 +1,107 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.EMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_CERTPAIR_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the certificate pair attribute syntax. This
+ * should be restricted to holding only X.509 certificate pairs, but we
+ * will accept any set of bytes. It will be treated much like the octet
+ * string attribute syntax.
+ */
+final class CertificatePairSyntaxImpl extends AbstractSyntaxImpl
+{
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_OCTET_STRING_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_CERTPAIR_NAME;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_OCTET_STRING_OID;
+ }
+
+
+
+ @Override
+ public boolean isBEREncodingRequired()
+ {
+ return true;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // All values will be acceptable for the certificate pair syntax.
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CertificateSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/CertificateSyntaxImpl.java
new file mode 100644
index 0000000..3d34b64
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CertificateSyntaxImpl.java
@@ -0,0 +1,107 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.EMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_CERTIFICATE_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the certificate attribute syntax. This should
+ * be restricted to holding only X.509 certificates, but we will accept
+ * any set of bytes. It will be treated much like the octet string
+ * attribute syntax.
+ */
+final class CertificateSyntaxImpl extends AbstractSyntaxImpl
+{
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_OCTET_STRING_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_CERTIFICATE_NAME;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_OCTET_STRING_OID;
+ }
+
+
+
+ @Override
+ public boolean isBEREncodingRequired()
+ {
+ return true;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // All values will be acceptable for the certificate syntax.
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/ConflictingSchemaElementException.java b/sdk/src/org/opends/sdk/schema/ConflictingSchemaElementException.java
new file mode 100644
index 0000000..857cfda
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/ConflictingSchemaElementException.java
@@ -0,0 +1,58 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * Thrown when addition of a schema element to a schema builder fails
+ * because the OID of the schema element conflicts with an existing
+ * schema element and the caller explicitly requested not to override
+ * existing schema elements.
+ */
+@SuppressWarnings("serial")
+public class ConflictingSchemaElementException extends
+ LocalizedIllegalArgumentException
+{
+ /**
+ * Creates a new conflicting schema element exception with the
+ * provided message.
+ *
+ * @param message
+ * The message that explains the problem that occurred.
+ */
+ public ConflictingSchemaElementException(Message message)
+ {
+ super(message);
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CoreSchema.java b/sdk/src/org/opends/sdk/schema/CoreSchema.java
new file mode 100644
index 0000000..0117a13
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CoreSchema.java
@@ -0,0 +1,2666 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+/**
+ * The OpenDS SDK core schema contains standard LDAP RFC schema elements. These include:
+ * <ul>
+ * <li><a href="http://tools.ietf.org/html/rfc4512">RFC 4512 -
+ * Lightweight Directory Access Protocol (LDAP): Directory Information
+ * Models </a>
+ * <li><a href="http://tools.ietf.org/html/rfc4517">RFC 4517 -
+ * Lightweight Directory Access Protocol (LDAP): Syntaxes and Matching
+ * Rules </a>
+ * <li><a href="http://tools.ietf.org/html/rfc4519">RFC 4519 -
+ * Lightweight Directory Access Protocol (LDAP): Schema for User
+ * Applications </a>
+ * <li><a href="http://tools.ietf.org/html/rfc4530">RFC 4530 -
+ * Lightweight Directory Access Protocol (LDAP): entryUUID Operational
+ * Attribute </a>
+ * <li><a href="http://tools.ietf.org/html/rfc3045">RFC 3045 - Storing
+ * Vendor Information in the LDAP Root DSE </a>
+ * <li><a href="http://tools.ietf.org/html/rfc3112">RFC 3112 - LDAP
+ * Authentication Password Schema </a>
+ * </ul>
+ * <p>
+ * The core schema is non-strict: attempts to retrieve
+ * non-existent Attribute Types will return a temporary
+ * Attribute Type having the Octet String syntax.
+ */
+public final class CoreSchema
+{
+ // Core Syntaxes
+ private static final Syntax ATTRIBUTE_TYPE_DESCRIPTION_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.3");
+ private static final Syntax AUTHENTICATION_PASSWORD_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.4203.1.1.2");
+ private static final Syntax BINARY_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.5");
+ private static final Syntax BIT_STRING_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.6");
+ private static final Syntax BOOLEAN_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.7");
+ private static final Syntax CERTIFICATE_LIST_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.9");
+ private static final Syntax CERTIFICATE_PAIR_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.10");
+ private static final Syntax CERTIFICATE_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.8");
+ private static final Syntax COUNTRY_STRING_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.11");
+ private static final Syntax DELIVERY_METHOD_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.14");
+ private static final Syntax DIRECTORY_STRING_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.15");
+ private static final Syntax DIT_CONTENT_RULE_DESCRIPTION_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.16");
+ private static final Syntax DIT_STRUCTURE_RULE_DESCRIPTION_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.17");
+ private static final Syntax DN_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.12");
+ private static final Syntax ENHANCED_GUIDE_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.21");
+ private static final Syntax FACSIMILE_TELEPHONE_NUMBER_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.22");
+ private static final Syntax FAX_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.23");
+ private static final Syntax GENERALIZED_TIME_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.24");
+ private static final Syntax GUIDE_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.25");
+ private static final Syntax IA5_STRING_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.26");
+ private static final Syntax INTEGER_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.27");
+ private static final Syntax JPEG_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.28");
+ private static final Syntax LDAP_SYNTAX_DESCRIPTION_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.54");
+ private static final Syntax MATCHING_RULE_DESCRIPTION_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.30");
+ private static final Syntax MATCHING_RULE_USE_DESCRIPTION_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.31");
+ private static final Syntax NAME_AND_OPTIONAL_UID_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.34");
+ private static final Syntax NAME_FORM_DESCRIPTION_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.35");
+ private static final Syntax NUMERIC_STRING_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.36");
+ private static final Syntax OBJECT_CLASS_DESCRIPTION_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.37");
+ private static final Syntax OCTET_STRING_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.40");
+ private static final Syntax OID_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.38");
+ private static final Syntax OTHER_MAILBOX_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.39");
+ private static final Syntax POSTAL_ADDRESS_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.41");
+ private static final Syntax PRESENTATION_ADDRESS_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.43");
+ private static final Syntax PRINTABLE_STRING_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.44");
+ private static final Syntax PROTOCOL_INFORMATION_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.42");
+ private static final Syntax SUBSTRING_ASSERTION_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.58");
+ private static final Syntax SUPPORTED_ALGORITHM_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.49");
+ private static final Syntax TELEPHONE_NUMBER_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.50");
+ private static final Syntax TELETEX_TERMINAL_IDENTIFIER_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.51");
+ private static final Syntax TELEX_NUMBER_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.52");
+ private static final Syntax UTC_TIME_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.53");
+ private static final Syntax UUID_SYNTAX =
+ CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.1.16.1");
+
+ // Core Matching Rules
+ private static final MatchingRule AUTH_PASSWORD_EXACT_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("1.3.6.1.4.1.4203.1.2.2");
+ private static final MatchingRule BIT_STRING_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.16");
+ private static final MatchingRule BOOLEAN_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.13");
+ private static final MatchingRule CASE_EXACT_IA5_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("1.3.6.1.4.1.1466.109.114.1");
+ private static final MatchingRule CASE_EXACT_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.5");
+ private static final MatchingRule CASE_EXACT_ORDERING_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.6");
+ private static final MatchingRule CASE_EXACT_SUBSTRINGS_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.7");
+ private static final MatchingRule CASE_IGNORE_IA5_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("1.3.6.1.4.1.1466.109.114.2");
+ private static final MatchingRule CASE_IGNORE_IA5_SUBSTRINGS_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("1.3.6.1.4.1.1466.109.114.3");
+ private static final MatchingRule CASE_IGNORE_LIST_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.11");
+ private static final MatchingRule CASE_IGNORE_LIST_SUBSTRINGS_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.12");
+ private static final MatchingRule CASE_IGNORE_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.2");
+ private static final MatchingRule CASE_IGNORE_ORDERING_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.3");
+ private static final MatchingRule CASE_IGNORE_SUBSTRINGS_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.4");
+ private static final MatchingRule DIRECTORY_STRING_FIRST_COMPONENT_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.31");
+ private static final MatchingRule DISTINGUISHED_NAME_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.1");
+ private static final MatchingRule GENERALIZED_TIME_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.27");
+ private static final MatchingRule GENERALIZED_TIME_ORDERING_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.28");
+ private static final MatchingRule INTEGER_FIRST_COMPONENT_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.29");
+ private static final MatchingRule INTEGER_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.14");
+ private static final MatchingRule INTEGER_ORDERING_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.15");
+ private static final MatchingRule KEYWORD_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.33");
+ private static final MatchingRule NUMERIC_STRING_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.8");
+ private static final MatchingRule NUMERIC_STRING_ORDERING_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.9");
+ private static final MatchingRule NUMERIC_STRING_SUBSTRINGS_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.10");
+ private static final MatchingRule OBJECT_IDENTIFIER_FIRST_COMPONENT_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.30");
+ private static final MatchingRule OBJECT_IDENTIFIER_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.0");
+ private static final MatchingRule OCTET_STRING_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.17");
+ private static final MatchingRule OCTET_STRING_ORDERING_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.18");
+ private static final MatchingRule OCTET_STRING_SUBSTRINGS_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.19");
+ private static final MatchingRule PRESENTATION_ADDRESS_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.22");
+ private static final MatchingRule PROTOCOL_INFORMATION_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.24");
+ private static final MatchingRule TELEPHONE_NUMBER_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.20");
+ private static final MatchingRule TELEPHONE_NUMBER_SUBSTRINGS_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.21");
+ private static final MatchingRule UNIQUE_MEMBER_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.23");
+ private static final MatchingRule UUID_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("1.3.6.1.1.16.2");
+ private static final MatchingRule UUID_ORDERING_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("1.3.6.1.1.16.3");
+ private static final MatchingRule WORD_MATCHING_RULE =
+ CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.32");
+
+ // Core Attribute Types
+ private static final AttributeType ALIASED_OBJECT_NAME_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.1");
+ private static final AttributeType ALT_SERVER_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.4.1.1466.101.120.6");
+ private static final AttributeType ATTRIBUTE_TYPES_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.21.5");
+ private static final AttributeType AUTH_PASSWORD_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.4.1.4203.1.3.4");
+ private static final AttributeType BUSINESS_CATEGORY_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.15");
+ private static final AttributeType CN_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.3");
+ private static final AttributeType CREATE_TIMESTAMP_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.18.1");
+ private static final AttributeType CREATORS_NAME_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.18.3");
+ private static final AttributeType C_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.6");
+ private static final AttributeType DC_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("0.9.2342.19200300.100.1.25");
+ private static final AttributeType DESCRIPTION_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.13");
+ private static final AttributeType DESTINATION_INDICATOR_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.27");
+ private static final AttributeType DISTINGUISHED_NAME_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.49");
+ private static final AttributeType DIT_CONTENT_RULES_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.21.2");
+ private static final AttributeType DIT_STRUCTURE_RULES_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.21.1");
+ private static final AttributeType DN_QUALIFIER_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.46");
+ private static final AttributeType ENHANCED_SEARCH_GUIDE_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.47");
+ private static final AttributeType ENTRY_UUID_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.1.16.4");
+ private static final AttributeType FACSIMILE_TELEPHONE_NUMBER_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.23");
+ private static final AttributeType GENERATION_QUALIFIER_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.44");
+ private static final AttributeType GIVEN_NAME_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.42");
+ private static final AttributeType GOVERNING_STRUCTURE_RULE_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.21.10");
+ private static final AttributeType HOUSE_IDENTIFIER_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.51");
+ private static final AttributeType INITIALS_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.43");
+ private static final AttributeType INTERNATIONAL_ISDN_NUMBER_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.25");
+ private static final AttributeType LDAP_SYNTAXES_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.4.1.1466.101.120.16");
+ private static final AttributeType L_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.7");
+ private static final AttributeType MATCHING_RULES_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.21.4");
+ private static final AttributeType MATCHING_RULE_USE_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.21.8");
+ private static final AttributeType MEMBER_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.31");
+ private static final AttributeType MODIFIERS_NAME_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.18.4");
+ private static final AttributeType MODIFY_TIMESTAMP_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.18.2");
+ private static final AttributeType NAME_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.41");
+ private static final AttributeType NAME_FORMS_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.21.7");
+ private static final AttributeType NAMING_CONTEXTS_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.4.1.1466.101.120.5");
+ private static final AttributeType OBJECT_CLASSES_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.21.6");
+ private static final AttributeType OBJECT_CLASS_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.0");
+ private static final AttributeType OU_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.11");
+ private static final AttributeType OWNER_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.32");
+ private static final AttributeType O_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.10");
+ private static final AttributeType PHYSICAL_DELIVERY_OFFICE_NAME_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.19");
+ private static final AttributeType POSTAL_ADDRESS_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.16");
+ private static final AttributeType POSTAL_CODE_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.17");
+ private static final AttributeType POST_OFFICE_BOX_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.18");
+ private static final AttributeType PREFERRED_DELIVERY_METHOD_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.28");
+ private static final AttributeType REGISTERED_ADDRESS_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.26");
+ private static final AttributeType ROLE_OCCUPANT_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.33");
+ private static final AttributeType SEARCH_GUIDE_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.14");
+ private static final AttributeType SEE_ALSO_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.34");
+ private static final AttributeType SERIAL_NUMBER_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.5");
+ private static final AttributeType SN_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.4");
+ private static final AttributeType STREET_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.9");
+ private static final AttributeType STRUCTURAL_OBJECT_CLASS_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.21.9");
+ private static final AttributeType ST_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.8");
+ private static final AttributeType SUBSCHEMA_SUBENTRY_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.18.10");
+ private static final AttributeType SUPPORTED_AUTH_PASSWORD_SCHEMES_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.4.1.4203.1.3.3");
+ private static final AttributeType SUPPORTED_CONTROL_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.4.1.1466.101.120.13");
+ private static final AttributeType SUPPORTED_EXTENSION_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.4.1.1466.101.120.7");
+ private static final AttributeType SUPPORTED_FEATURES_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.4.1.4203.1.3.5");
+ private static final AttributeType SUPPORTED_LDAP_VERSION_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.4.1.1466.101.120.15");
+ private static final AttributeType SUPPORTED_SASL_MECHANISMS_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.4.1.1466.101.120.14");
+ private static final AttributeType TELEPHONE_NUMBER_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.20");
+ private static final AttributeType TELETEX_TERMINAL_IDENTIFIER_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.22");
+ private static final AttributeType TELEX_NUMBER_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.21");
+ private static final AttributeType TITLE_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.12");
+ private static final AttributeType UID_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("0.9.2342.19200300.100.1.1");
+ private static final AttributeType UNIQUE_MEMBER_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.50");
+ private static final AttributeType USER_PASSWORD_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.35");
+ private static final AttributeType VENDOR_NAME_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.1.4");
+ private static final AttributeType VENDOR_VERSION_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.1.5");
+ private static final AttributeType X121_ADDRESS_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.24");
+ private static final AttributeType X500_UNIQUE_IDENTIFIER_ATTRIBUTE_TYPE =
+ CoreSchemaImpl.getInstance().getAttributeType("2.5.4.45");
+
+ // Core Object Classes
+ private static final ObjectClass ALIAS_OBJECT_CLASS =
+ CoreSchemaImpl.getInstance().getObjectClass("2.5.6.1");
+ private static final ObjectClass APPLICATION_PROCESS_OBJECT_CLASS =
+ CoreSchemaImpl.getInstance().getObjectClass("2.5.6.11");
+ private static final ObjectClass AUTH_PASSWORD_OBJECT_OBJECT_CLASS =
+ CoreSchemaImpl.getInstance().getObjectClass("1.3.6.1.4.1.4203.1.4.7");
+ private static final ObjectClass COUNTRY_OBJECT_CLASS =
+ CoreSchemaImpl.getInstance().getObjectClass("2.5.6.2");
+ private static final ObjectClass DC_OBJECT_OBJECT_CLASS =
+ CoreSchemaImpl.getInstance().getObjectClass("1.3.6.1.4.1.1466.344");
+ private static final ObjectClass DEVICE_OBJECT_CLASS =
+ CoreSchemaImpl.getInstance().getObjectClass("2.5.6.14");
+ private static final ObjectClass EXTENSIBLE_OBJECT_OBJECT_CLASS =
+ CoreSchemaImpl.getInstance().getObjectClass("1.3.6.1.4.1.1466.101.120.111");
+ private static final ObjectClass GROUP_OF_NAMES_OBJECT_CLASS =
+ CoreSchemaImpl.getInstance().getObjectClass("2.5.6.9");
+ private static final ObjectClass GROUP_OF_UNIQUE_NAMES_OBJECT_CLASS =
+ CoreSchemaImpl.getInstance().getObjectClass("2.5.6.17");
+ private static final ObjectClass LOCALITY_OBJECT_CLASS =
+ CoreSchemaImpl.getInstance().getObjectClass("2.5.6.3");
+ private static final ObjectClass ORGANIZATIONAL_PERSON_OBJECT_CLASS =
+ CoreSchemaImpl.getInstance().getObjectClass("2.5.6.7");
+ private static final ObjectClass ORGANIZATIONAL_ROLE_OBJECT_CLASS =
+ CoreSchemaImpl.getInstance().getObjectClass("2.5.6.8");
+ private static final ObjectClass ORGANIZATIONAL_UNIT_OBJECT_CLASS =
+ CoreSchemaImpl.getInstance().getObjectClass("2.5.6.5");
+ private static final ObjectClass ORGANIZATION_OBJECT_CLASS =
+ CoreSchemaImpl.getInstance().getObjectClass("2.5.6.4");
+ private static final ObjectClass PERSON_OBJECT_CLASS =
+ CoreSchemaImpl.getInstance().getObjectClass("2.5.6.6");
+ private static final ObjectClass RESIDENTIAL_PERSON_OBJECT_CLASS =
+ CoreSchemaImpl.getInstance().getObjectClass("2.5.6.10");
+ private static final ObjectClass SUBSCHEMA_OBJECT_CLASS =
+ CoreSchemaImpl.getInstance().getObjectClass("2.5.20.1");
+ private static final ObjectClass TOP_OBJECT_CLASS =
+ CoreSchemaImpl.getInstance().getObjectClass("2.5.6.0");
+ private static final ObjectClass UID_OBJECT_OBJECT_CLASS =
+ CoreSchemaImpl.getInstance().getObjectClass("1.3.6.1.1.3.1");
+
+
+
+ // Prevent instantiation
+ private CoreSchema()
+ {
+ // Nothing to do.
+ }
+
+
+
+ /**
+ * Returns a reference to the singleton core schema.
+ *
+ * @return The core schema.
+ */
+ public static Schema getInstance()
+ {
+ return CoreSchemaImpl.getInstance();
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Attribute Type Description Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.3}.
+ *
+ * @return A reference to the {@code Attribute Type Description Syntax}.
+ */
+ public static Syntax getAttributeTypeDescriptionSyntax()
+ {
+ return ATTRIBUTE_TYPE_DESCRIPTION_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Authentication Password Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.4203.1.1.2}.
+ *
+ * @return A reference to the {@code Authentication Password Syntax}.
+ */
+ public static Syntax getAuthenticationPasswordSyntax()
+ {
+ return AUTHENTICATION_PASSWORD_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Binary Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.5}.
+ *
+ * @return A reference to the {@code Binary Syntax}.
+ */
+ public static Syntax getBinarySyntax()
+ {
+ return BINARY_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Bit String Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.6}.
+ *
+ * @return A reference to the {@code Bit String Syntax}.
+ */
+ public static Syntax getBitStringSyntax()
+ {
+ return BIT_STRING_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Boolean Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.7}.
+ *
+ * @return A reference to the {@code Boolean Syntax}.
+ */
+ public static Syntax getBooleanSyntax()
+ {
+ return BOOLEAN_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Certificate List Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.9}.
+ *
+ * @return A reference to the {@code Certificate List Syntax}.
+ */
+ public static Syntax getCertificateListSyntax()
+ {
+ return CERTIFICATE_LIST_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Certificate Pair Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.10}.
+ *
+ * @return A reference to the {@code Certificate Pair Syntax}.
+ */
+ public static Syntax getCertificatePairSyntax()
+ {
+ return CERTIFICATE_PAIR_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Certificate Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.8}.
+ *
+ * @return A reference to the {@code Certificate Syntax}.
+ */
+ public static Syntax getCertificateSyntax()
+ {
+ return CERTIFICATE_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Country String Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.11}.
+ *
+ * @return A reference to the {@code Country String Syntax}.
+ */
+ public static Syntax getCountryStringSyntax()
+ {
+ return COUNTRY_STRING_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Delivery Method Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.14}.
+ *
+ * @return A reference to the {@code Delivery Method Syntax}.
+ */
+ public static Syntax getDeliveryMethodSyntax()
+ {
+ return DELIVERY_METHOD_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Directory String Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.15}.
+ *
+ * @return A reference to the {@code Directory String Syntax}.
+ */
+ public static Syntax getDirectoryStringSyntax()
+ {
+ return DIRECTORY_STRING_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code DIT Content Rule Description Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.16}.
+ *
+ * @return A reference to the {@code DIT Content Rule Description Syntax}.
+ */
+ public static Syntax getDITContentRuleDescriptionSyntax()
+ {
+ return DIT_CONTENT_RULE_DESCRIPTION_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code DIT Structure Rule Description Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.17}.
+ *
+ * @return A reference to the {@code DIT Structure Rule Description Syntax}.
+ */
+ public static Syntax getDITStructureRuleDescriptionSyntax()
+ {
+ return DIT_STRUCTURE_RULE_DESCRIPTION_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code DN Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.12}.
+ *
+ * @return A reference to the {@code DN Syntax}.
+ */
+ public static Syntax getDNSyntax()
+ {
+ return DN_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Enhanced Guide Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.21}.
+ *
+ * @return A reference to the {@code Enhanced Guide Syntax}.
+ */
+ public static Syntax getEnhancedGuideSyntax()
+ {
+ return ENHANCED_GUIDE_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Facsimile Telephone Number Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.22}.
+ *
+ * @return A reference to the {@code Facsimile Telephone Number Syntax}.
+ */
+ public static Syntax getFacsimileTelephoneNumberSyntax()
+ {
+ return FACSIMILE_TELEPHONE_NUMBER_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Fax Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.23}.
+ *
+ * @return A reference to the {@code Fax Syntax}.
+ */
+ public static Syntax getFaxSyntax()
+ {
+ return FAX_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Generalized Time Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.24}.
+ *
+ * @return A reference to the {@code Generalized Time Syntax}.
+ */
+ public static Syntax getGeneralizedTimeSyntax()
+ {
+ return GENERALIZED_TIME_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Guide Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.25}.
+ *
+ * @return A reference to the {@code Guide Syntax}.
+ */
+ public static Syntax getGuideSyntax()
+ {
+ return GUIDE_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code IA5 String Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.26}.
+ *
+ * @return A reference to the {@code IA5 String Syntax}.
+ */
+ public static Syntax getIA5StringSyntax()
+ {
+ return IA5_STRING_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Integer Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.27}.
+ *
+ * @return A reference to the {@code Integer Syntax}.
+ */
+ public static Syntax getIntegerSyntax()
+ {
+ return INTEGER_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code JPEG Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.28}.
+ *
+ * @return A reference to the {@code JPEG Syntax}.
+ */
+ public static Syntax getJPEGSyntax()
+ {
+ return JPEG_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code LDAP Syntax Description Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.54}.
+ *
+ * @return A reference to the {@code LDAP Syntax Description Syntax}.
+ */
+ public static Syntax getLDAPSyntaxDescriptionSyntax()
+ {
+ return LDAP_SYNTAX_DESCRIPTION_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Matching Rule Description Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.30}.
+ *
+ * @return A reference to the {@code Matching Rule Description Syntax}.
+ */
+ public static Syntax getMatchingRuleDescriptionSyntax()
+ {
+ return MATCHING_RULE_DESCRIPTION_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Matching Rule Use Description Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.31}.
+ *
+ * @return A reference to the {@code Matching Rule Use Description Syntax}.
+ */
+ public static Syntax getMatchingRuleUseDescriptionSyntax()
+ {
+ return MATCHING_RULE_USE_DESCRIPTION_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Name and Optional UID Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.34}.
+ *
+ * @return A reference to the {@code Name and Optional UID Syntax}.
+ */
+ public static Syntax getNameAndOptionalUIDSyntax()
+ {
+ return NAME_AND_OPTIONAL_UID_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Name Form Description Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.35}.
+ *
+ * @return A reference to the {@code Name Form Description Syntax}.
+ */
+ public static Syntax getNameFormDescriptionSyntax()
+ {
+ return NAME_FORM_DESCRIPTION_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Numeric String Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.36}.
+ *
+ * @return A reference to the {@code Numeric String Syntax}.
+ */
+ public static Syntax getNumericStringSyntax()
+ {
+ return NUMERIC_STRING_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Object Class Description Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.37}.
+ *
+ * @return A reference to the {@code Object Class Description Syntax}.
+ */
+ public static Syntax getObjectClassDescriptionSyntax()
+ {
+ return OBJECT_CLASS_DESCRIPTION_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Octet String Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.40}.
+ *
+ * @return A reference to the {@code Octet String Syntax}.
+ */
+ public static Syntax getOctetStringSyntax()
+ {
+ return OCTET_STRING_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code OID Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.38}.
+ *
+ * @return A reference to the {@code OID Syntax}.
+ */
+ public static Syntax getOIDSyntax()
+ {
+ return OID_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Other Mailbox Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.39}.
+ *
+ * @return A reference to the {@code Other Mailbox Syntax}.
+ */
+ public static Syntax getOtherMailboxSyntax()
+ {
+ return OTHER_MAILBOX_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Postal Address Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.41}.
+ *
+ * @return A reference to the {@code Postal Address Syntax}.
+ */
+ public static Syntax getPostalAddressSyntax()
+ {
+ return POSTAL_ADDRESS_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Presentation Address Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.43}.
+ *
+ * @return A reference to the {@code Presentation Address Syntax}.
+ */
+ public static Syntax getPresentationAddressSyntax()
+ {
+ return PRESENTATION_ADDRESS_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Printable String Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.44}.
+ *
+ * @return A reference to the {@code Printable String Syntax}.
+ */
+ public static Syntax getPrintableStringSyntax()
+ {
+ return PRINTABLE_STRING_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Protocol Information Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.42}.
+ *
+ * @return A reference to the {@code Protocol Information Syntax}.
+ */
+ public static Syntax getProtocolInformationSyntax()
+ {
+ return PROTOCOL_INFORMATION_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Substring Assertion Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.58}.
+ *
+ * @return A reference to the {@code Substring Assertion Syntax}.
+ */
+ public static Syntax getSubstringAssertionSyntax()
+ {
+ return SUBSTRING_ASSERTION_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Supported Algorithm Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.49}.
+ *
+ * @return A reference to the {@code Supported Algorithm Syntax}.
+ */
+ public static Syntax getSupportedAlgorithmSyntax()
+ {
+ return SUPPORTED_ALGORITHM_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Telephone Number Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.50}.
+ *
+ * @return A reference to the {@code Telephone Number Syntax}.
+ */
+ public static Syntax getTelephoneNumberSyntax()
+ {
+ return TELEPHONE_NUMBER_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Teletex Terminal Identifier Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.51}.
+ *
+ * @return A reference to the {@code Teletex Terminal Identifier Syntax}.
+ */
+ public static Syntax getTeletexTerminalIdentifierSyntax()
+ {
+ return TELETEX_TERMINAL_IDENTIFIER_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code Telex Number Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.52}.
+ *
+ * @return A reference to the {@code Telex Number Syntax}.
+ */
+ public static Syntax getTelexNumberSyntax()
+ {
+ return TELEX_NUMBER_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code UTC Time Syntax}
+ * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.53}.
+ *
+ * @return A reference to the {@code UTC Time Syntax}.
+ */
+ public static Syntax getUTCTimeSyntax()
+ {
+ return UTC_TIME_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code UUID Syntax}
+ * which has the OID {@code 1.3.6.1.1.16.1}.
+ *
+ * @return A reference to the {@code UUID Syntax}.
+ */
+ public static Syntax getUUIDSyntax()
+ {
+ return UUID_SYNTAX;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code authPasswordExactMatch} Matching Rule
+ * which has the OID {@code 1.3.6.1.4.1.4203.1.2.2}.
+ *
+ * @return A reference to the {@code authPasswordExactMatch} Matching Rule.
+ */
+ public static MatchingRule getAuthPasswordExactMatchingRule()
+ {
+ return AUTH_PASSWORD_EXACT_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code bitStringMatch} Matching Rule
+ * which has the OID {@code 2.5.13.16}.
+ *
+ * @return A reference to the {@code bitStringMatch} Matching Rule.
+ */
+ public static MatchingRule getBitStringMatchingRule()
+ {
+ return BIT_STRING_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code booleanMatch} Matching Rule
+ * which has the OID {@code 2.5.13.13}.
+ *
+ * @return A reference to the {@code booleanMatch} Matching Rule.
+ */
+ public static MatchingRule getBooleanMatchingRule()
+ {
+ return BOOLEAN_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code caseExactIA5Match} Matching Rule
+ * which has the OID {@code 1.3.6.1.4.1.1466.109.114.1}.
+ *
+ * @return A reference to the {@code caseExactIA5Match} Matching Rule.
+ */
+ public static MatchingRule getCaseExactIA5MatchingRule()
+ {
+ return CASE_EXACT_IA5_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code caseExactMatch} Matching Rule
+ * which has the OID {@code 2.5.13.5}.
+ *
+ * @return A reference to the {@code caseExactMatch} Matching Rule.
+ */
+ public static MatchingRule getCaseExactMatchingRule()
+ {
+ return CASE_EXACT_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code caseExactOrderingMatch} Matching Rule
+ * which has the OID {@code 2.5.13.6}.
+ *
+ * @return A reference to the {@code caseExactOrderingMatch} Matching Rule.
+ */
+ public static MatchingRule getCaseExactOrderingMatchingRule()
+ {
+ return CASE_EXACT_ORDERING_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code caseExactSubstringsMatch} Matching Rule
+ * which has the OID {@code 2.5.13.7}.
+ *
+ * @return A reference to the {@code caseExactSubstringsMatch} Matching Rule.
+ */
+ public static MatchingRule getCaseExactSubstringsMatchingRule()
+ {
+ return CASE_EXACT_SUBSTRINGS_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code caseIgnoreIA5Match} Matching Rule
+ * which has the OID {@code 1.3.6.1.4.1.1466.109.114.2}.
+ *
+ * @return A reference to the {@code caseIgnoreIA5Match} Matching Rule.
+ */
+ public static MatchingRule getCaseIgnoreIA5MatchingRule()
+ {
+ return CASE_IGNORE_IA5_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code caseIgnoreIA5SubstringsMatch} Matching Rule
+ * which has the OID {@code 1.3.6.1.4.1.1466.109.114.3}.
+ *
+ * @return A reference to the {@code caseIgnoreIA5SubstringsMatch} Matching Rule.
+ */
+ public static MatchingRule getCaseIgnoreIA5SubstringsMatchingRule()
+ {
+ return CASE_IGNORE_IA5_SUBSTRINGS_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code caseIgnoreListMatch} Matching Rule
+ * which has the OID {@code 2.5.13.11}.
+ *
+ * @return A reference to the {@code caseIgnoreListMatch} Matching Rule.
+ */
+ public static MatchingRule getCaseIgnoreListMatchingRule()
+ {
+ return CASE_IGNORE_LIST_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code caseIgnoreListSubstringsMatch} Matching Rule
+ * which has the OID {@code 2.5.13.12}.
+ *
+ * @return A reference to the {@code caseIgnoreListSubstringsMatch} Matching Rule.
+ */
+ public static MatchingRule getCaseIgnoreListSubstringsMatchingRule()
+ {
+ return CASE_IGNORE_LIST_SUBSTRINGS_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code caseIgnoreMatch} Matching Rule
+ * which has the OID {@code 2.5.13.2}.
+ *
+ * @return A reference to the {@code caseIgnoreMatch} Matching Rule.
+ */
+ public static MatchingRule getCaseIgnoreMatchingRule()
+ {
+ return CASE_IGNORE_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code caseIgnoreOrderingMatch} Matching Rule
+ * which has the OID {@code 2.5.13.3}.
+ *
+ * @return A reference to the {@code caseIgnoreOrderingMatch} Matching Rule.
+ */
+ public static MatchingRule getCaseIgnoreOrderingMatchingRule()
+ {
+ return CASE_IGNORE_ORDERING_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code caseIgnoreSubstringsMatch} Matching Rule
+ * which has the OID {@code 2.5.13.4}.
+ *
+ * @return A reference to the {@code caseIgnoreSubstringsMatch} Matching Rule.
+ */
+ public static MatchingRule getCaseIgnoreSubstringsMatchingRule()
+ {
+ return CASE_IGNORE_SUBSTRINGS_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code directoryStringFirstComponentMatch} Matching Rule
+ * which has the OID {@code 2.5.13.31}.
+ *
+ * @return A reference to the {@code directoryStringFirstComponentMatch} Matching Rule.
+ */
+ public static MatchingRule getDirectoryStringFirstComponentMatchingRule()
+ {
+ return DIRECTORY_STRING_FIRST_COMPONENT_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code distinguishedNameMatch} Matching Rule
+ * which has the OID {@code 2.5.13.1}.
+ *
+ * @return A reference to the {@code distinguishedNameMatch} Matching Rule.
+ */
+ public static MatchingRule getDistinguishedNameMatchingRule()
+ {
+ return DISTINGUISHED_NAME_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code generalizedTimeMatch} Matching Rule
+ * which has the OID {@code 2.5.13.27}.
+ *
+ * @return A reference to the {@code generalizedTimeMatch} Matching Rule.
+ */
+ public static MatchingRule getGeneralizedTimeMatchingRule()
+ {
+ return GENERALIZED_TIME_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code generalizedTimeOrderingMatch} Matching Rule
+ * which has the OID {@code 2.5.13.28}.
+ *
+ * @return A reference to the {@code generalizedTimeOrderingMatch} Matching Rule.
+ */
+ public static MatchingRule getGeneralizedTimeOrderingMatchingRule()
+ {
+ return GENERALIZED_TIME_ORDERING_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code integerFirstComponentMatch} Matching Rule
+ * which has the OID {@code 2.5.13.29}.
+ *
+ * @return A reference to the {@code integerFirstComponentMatch} Matching Rule.
+ */
+ public static MatchingRule getIntegerFirstComponentMatchingRule()
+ {
+ return INTEGER_FIRST_COMPONENT_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code integerMatch} Matching Rule
+ * which has the OID {@code 2.5.13.14}.
+ *
+ * @return A reference to the {@code integerMatch} Matching Rule.
+ */
+ public static MatchingRule getIntegerMatchingRule()
+ {
+ return INTEGER_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code integerOrderingMatch} Matching Rule
+ * which has the OID {@code 2.5.13.15}.
+ *
+ * @return A reference to the {@code integerOrderingMatch} Matching Rule.
+ */
+ public static MatchingRule getIntegerOrderingMatchingRule()
+ {
+ return INTEGER_ORDERING_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code keywordMatch} Matching Rule
+ * which has the OID {@code 2.5.13.33}.
+ *
+ * @return A reference to the {@code keywordMatch} Matching Rule.
+ */
+ public static MatchingRule getKeywordMatchingRule()
+ {
+ return KEYWORD_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code numericStringMatch} Matching Rule
+ * which has the OID {@code 2.5.13.8}.
+ *
+ * @return A reference to the {@code numericStringMatch} Matching Rule.
+ */
+ public static MatchingRule getNumericStringMatchingRule()
+ {
+ return NUMERIC_STRING_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code numericStringOrderingMatch} Matching Rule
+ * which has the OID {@code 2.5.13.9}.
+ *
+ * @return A reference to the {@code numericStringOrderingMatch} Matching Rule.
+ */
+ public static MatchingRule getNumericStringOrderingMatchingRule()
+ {
+ return NUMERIC_STRING_ORDERING_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code numericStringSubstringsMatch} Matching Rule
+ * which has the OID {@code 2.5.13.10}.
+ *
+ * @return A reference to the {@code numericStringSubstringsMatch} Matching Rule.
+ */
+ public static MatchingRule getNumericStringSubstringsMatchingRule()
+ {
+ return NUMERIC_STRING_SUBSTRINGS_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code objectIdentifierFirstComponentMatch} Matching Rule
+ * which has the OID {@code 2.5.13.30}.
+ *
+ * @return A reference to the {@code objectIdentifierFirstComponentMatch} Matching Rule.
+ */
+ public static MatchingRule getObjectIdentifierFirstComponentMatchingRule()
+ {
+ return OBJECT_IDENTIFIER_FIRST_COMPONENT_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code objectIdentifierMatch} Matching Rule
+ * which has the OID {@code 2.5.13.0}.
+ *
+ * @return A reference to the {@code objectIdentifierMatch} Matching Rule.
+ */
+ public static MatchingRule getObjectIdentifierMatchingRule()
+ {
+ return OBJECT_IDENTIFIER_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code octetStringMatch} Matching Rule
+ * which has the OID {@code 2.5.13.17}.
+ *
+ * @return A reference to the {@code octetStringMatch} Matching Rule.
+ */
+ public static MatchingRule getOctetStringMatchingRule()
+ {
+ return OCTET_STRING_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code octetStringOrderingMatch} Matching Rule
+ * which has the OID {@code 2.5.13.18}.
+ *
+ * @return A reference to the {@code octetStringOrderingMatch} Matching Rule.
+ */
+ public static MatchingRule getOctetStringOrderingMatchingRule()
+ {
+ return OCTET_STRING_ORDERING_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code octetStringSubstringsMatch} Matching Rule
+ * which has the OID {@code 2.5.13.19}.
+ *
+ * @return A reference to the {@code octetStringSubstringsMatch} Matching Rule.
+ */
+ public static MatchingRule getOctetStringSubstringsMatchingRule()
+ {
+ return OCTET_STRING_SUBSTRINGS_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code presentationAddressMatch} Matching Rule
+ * which has the OID {@code 2.5.13.22}.
+ *
+ * @return A reference to the {@code presentationAddressMatch} Matching Rule.
+ */
+ public static MatchingRule getPresentationAddressMatchingRule()
+ {
+ return PRESENTATION_ADDRESS_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code protocolInformationMatch} Matching Rule
+ * which has the OID {@code 2.5.13.24}.
+ *
+ * @return A reference to the {@code protocolInformationMatch} Matching Rule.
+ */
+ public static MatchingRule getProtocolInformationMatchingRule()
+ {
+ return PROTOCOL_INFORMATION_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code telephoneNumberMatch} Matching Rule
+ * which has the OID {@code 2.5.13.20}.
+ *
+ * @return A reference to the {@code telephoneNumberMatch} Matching Rule.
+ */
+ public static MatchingRule getTelephoneNumberMatchingRule()
+ {
+ return TELEPHONE_NUMBER_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code telephoneNumberSubstringsMatch} Matching Rule
+ * which has the OID {@code 2.5.13.21}.
+ *
+ * @return A reference to the {@code telephoneNumberSubstringsMatch} Matching Rule.
+ */
+ public static MatchingRule getTelephoneNumberSubstringsMatchingRule()
+ {
+ return TELEPHONE_NUMBER_SUBSTRINGS_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code uniqueMemberMatch} Matching Rule
+ * which has the OID {@code 2.5.13.23}.
+ *
+ * @return A reference to the {@code uniqueMemberMatch} Matching Rule.
+ */
+ public static MatchingRule getUniqueMemberMatchingRule()
+ {
+ return UNIQUE_MEMBER_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code uuidMatch} Matching Rule
+ * which has the OID {@code 1.3.6.1.1.16.2}.
+ *
+ * @return A reference to the {@code uuidMatch} Matching Rule.
+ */
+ public static MatchingRule getUUIDMatchingRule()
+ {
+ return UUID_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code uuidOrderingMatch} Matching Rule
+ * which has the OID {@code 1.3.6.1.1.16.3}.
+ *
+ * @return A reference to the {@code uuidOrderingMatch} Matching Rule.
+ */
+ public static MatchingRule getUUIDOrderingMatchingRule()
+ {
+ return UUID_ORDERING_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code wordMatch} Matching Rule
+ * which has the OID {@code 2.5.13.32}.
+ *
+ * @return A reference to the {@code wordMatch} Matching Rule.
+ */
+ public static MatchingRule getWordMatchingRule()
+ {
+ return WORD_MATCHING_RULE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code aliasedObjectName} Attribute Type
+ * which has the OID {@code 2.5.4.1}.
+ *
+ * @return A reference to the {@code aliasedObjectName} Attribute Type.
+ */
+ public static AttributeType getAliasedObjectNameAttributeType()
+ {
+ return ALIASED_OBJECT_NAME_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code altServer} Attribute Type
+ * which has the OID {@code 1.3.6.1.4.1.1466.101.120.6}.
+ *
+ * @return A reference to the {@code altServer} Attribute Type.
+ */
+ public static AttributeType getAltServerAttributeType()
+ {
+ return ALT_SERVER_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code attributeTypes} Attribute Type
+ * which has the OID {@code 2.5.21.5}.
+ *
+ * @return A reference to the {@code attributeTypes} Attribute Type.
+ */
+ public static AttributeType getAttributeTypesAttributeType()
+ {
+ return ATTRIBUTE_TYPES_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code authPassword} Attribute Type
+ * which has the OID {@code 1.3.6.1.4.1.4203.1.3.4}.
+ *
+ * @return A reference to the {@code authPassword} Attribute Type.
+ */
+ public static AttributeType getAuthPasswordAttributeType()
+ {
+ return AUTH_PASSWORD_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code businessCategory} Attribute Type
+ * which has the OID {@code 2.5.4.15}.
+ *
+ * @return A reference to the {@code businessCategory} Attribute Type.
+ */
+ public static AttributeType getBusinessCategoryAttributeType()
+ {
+ return BUSINESS_CATEGORY_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code cn} Attribute Type
+ * which has the OID {@code 2.5.4.3}.
+ *
+ * @return A reference to the {@code cn} Attribute Type.
+ */
+ public static AttributeType getCNAttributeType()
+ {
+ return CN_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code createTimestamp} Attribute Type
+ * which has the OID {@code 2.5.18.1}.
+ *
+ * @return A reference to the {@code createTimestamp} Attribute Type.
+ */
+ public static AttributeType getCreateTimestampAttributeType()
+ {
+ return CREATE_TIMESTAMP_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code creatorsName} Attribute Type
+ * which has the OID {@code 2.5.18.3}.
+ *
+ * @return A reference to the {@code creatorsName} Attribute Type.
+ */
+ public static AttributeType getCreatorsNameAttributeType()
+ {
+ return CREATORS_NAME_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code c} Attribute Type
+ * which has the OID {@code 2.5.4.6}.
+ *
+ * @return A reference to the {@code c} Attribute Type.
+ */
+ public static AttributeType getCAttributeType()
+ {
+ return C_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code dc} Attribute Type
+ * which has the OID {@code 0.9.2342.19200300.100.1.25}.
+ *
+ * @return A reference to the {@code dc} Attribute Type.
+ */
+ public static AttributeType getDCAttributeType()
+ {
+ return DC_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code description} Attribute Type
+ * which has the OID {@code 2.5.4.13}.
+ *
+ * @return A reference to the {@code description} Attribute Type.
+ */
+ public static AttributeType getDescriptionAttributeType()
+ {
+ return DESCRIPTION_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code destinationIndicator} Attribute Type
+ * which has the OID {@code 2.5.4.27}.
+ *
+ * @return A reference to the {@code destinationIndicator} Attribute Type.
+ */
+ public static AttributeType getDestinationIndicatorAttributeType()
+ {
+ return DESTINATION_INDICATOR_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code distinguishedName} Attribute Type
+ * which has the OID {@code 2.5.4.49}.
+ *
+ * @return A reference to the {@code distinguishedName} Attribute Type.
+ */
+ public static AttributeType getDistinguishedNameAttributeType()
+ {
+ return DISTINGUISHED_NAME_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code ditContentRules} Attribute Type
+ * which has the OID {@code 2.5.21.2}.
+ *
+ * @return A reference to the {@code ditContentRules} Attribute Type.
+ */
+ public static AttributeType getDITContentRulesAttributeType()
+ {
+ return DIT_CONTENT_RULES_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code ditStructureRules} Attribute Type
+ * which has the OID {@code 2.5.21.1}.
+ *
+ * @return A reference to the {@code ditStructureRules} Attribute Type.
+ */
+ public static AttributeType getDITStructureRulesAttributeType()
+ {
+ return DIT_STRUCTURE_RULES_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code dnQualifier} Attribute Type
+ * which has the OID {@code 2.5.4.46}.
+ *
+ * @return A reference to the {@code dnQualifier} Attribute Type.
+ */
+ public static AttributeType getDNQualifierAttributeType()
+ {
+ return DN_QUALIFIER_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code enhancedSearchGuide} Attribute Type
+ * which has the OID {@code 2.5.4.47}.
+ *
+ * @return A reference to the {@code enhancedSearchGuide} Attribute Type.
+ */
+ public static AttributeType getEnhancedSearchGuideAttributeType()
+ {
+ return ENHANCED_SEARCH_GUIDE_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code entryUUID} Attribute Type
+ * which has the OID {@code 1.3.6.1.1.16.4}.
+ *
+ * @return A reference to the {@code entryUUID} Attribute Type.
+ */
+ public static AttributeType getEntryUUIDAttributeType()
+ {
+ return ENTRY_UUID_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code facsimileTelephoneNumber} Attribute Type
+ * which has the OID {@code 2.5.4.23}.
+ *
+ * @return A reference to the {@code facsimileTelephoneNumber} Attribute Type.
+ */
+ public static AttributeType getFacsimileTelephoneNumberAttributeType()
+ {
+ return FACSIMILE_TELEPHONE_NUMBER_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code generationQualifier} Attribute Type
+ * which has the OID {@code 2.5.4.44}.
+ *
+ * @return A reference to the {@code generationQualifier} Attribute Type.
+ */
+ public static AttributeType getGenerationQualifierAttributeType()
+ {
+ return GENERATION_QUALIFIER_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code givenName} Attribute Type
+ * which has the OID {@code 2.5.4.42}.
+ *
+ * @return A reference to the {@code givenName} Attribute Type.
+ */
+ public static AttributeType getGivenNameAttributeType()
+ {
+ return GIVEN_NAME_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code governingStructureRule} Attribute Type
+ * which has the OID {@code 2.5.21.10}.
+ *
+ * @return A reference to the {@code governingStructureRule} Attribute Type.
+ */
+ public static AttributeType getGoverningStructureRuleAttributeType()
+ {
+ return GOVERNING_STRUCTURE_RULE_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code houseIdentifier} Attribute Type
+ * which has the OID {@code 2.5.4.51}.
+ *
+ * @return A reference to the {@code houseIdentifier} Attribute Type.
+ */
+ public static AttributeType getHouseIdentifierAttributeType()
+ {
+ return HOUSE_IDENTIFIER_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code initials} Attribute Type
+ * which has the OID {@code 2.5.4.43}.
+ *
+ * @return A reference to the {@code initials} Attribute Type.
+ */
+ public static AttributeType getInitialsAttributeType()
+ {
+ return INITIALS_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code internationalISDNNumber} Attribute Type
+ * which has the OID {@code 2.5.4.25}.
+ *
+ * @return A reference to the {@code internationalISDNNumber} Attribute Type.
+ */
+ public static AttributeType getInternationalISDNNumberAttributeType()
+ {
+ return INTERNATIONAL_ISDN_NUMBER_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code ldapSyntaxes} Attribute Type
+ * which has the OID {@code 1.3.6.1.4.1.1466.101.120.16}.
+ *
+ * @return A reference to the {@code ldapSyntaxes} Attribute Type.
+ */
+ public static AttributeType getLDAPSyntaxesAttributeType()
+ {
+ return LDAP_SYNTAXES_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code l} Attribute Type
+ * which has the OID {@code 2.5.4.7}.
+ *
+ * @return A reference to the {@code l} Attribute Type.
+ */
+ public static AttributeType getLAttributeType()
+ {
+ return L_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code matchingRules} Attribute Type
+ * which has the OID {@code 2.5.21.4}.
+ *
+ * @return A reference to the {@code matchingRules} Attribute Type.
+ */
+ public static AttributeType getMatchingRulesAttributeType()
+ {
+ return MATCHING_RULES_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code matchingRuleUse} Attribute Type
+ * which has the OID {@code 2.5.21.8}.
+ *
+ * @return A reference to the {@code matchingRuleUse} Attribute Type.
+ */
+ public static AttributeType getMatchingRuleUseAttributeType()
+ {
+ return MATCHING_RULE_USE_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code member} Attribute Type
+ * which has the OID {@code 2.5.4.31}.
+ *
+ * @return A reference to the {@code member} Attribute Type.
+ */
+ public static AttributeType getMemberAttributeType()
+ {
+ return MEMBER_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code modifiersName} Attribute Type
+ * which has the OID {@code 2.5.18.4}.
+ *
+ * @return A reference to the {@code modifiersName} Attribute Type.
+ */
+ public static AttributeType getModifiersNameAttributeType()
+ {
+ return MODIFIERS_NAME_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code modifyTimestamp} Attribute Type
+ * which has the OID {@code 2.5.18.2}.
+ *
+ * @return A reference to the {@code modifyTimestamp} Attribute Type.
+ */
+ public static AttributeType getModifyTimestampAttributeType()
+ {
+ return MODIFY_TIMESTAMP_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code name} Attribute Type
+ * which has the OID {@code 2.5.4.41}.
+ *
+ * @return A reference to the {@code name} Attribute Type.
+ */
+ public static AttributeType getNameAttributeType()
+ {
+ return NAME_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code nameForms} Attribute Type
+ * which has the OID {@code 2.5.21.7}.
+ *
+ * @return A reference to the {@code nameForms} Attribute Type.
+ */
+ public static AttributeType getNameFormsAttributeType()
+ {
+ return NAME_FORMS_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code namingContexts} Attribute Type
+ * which has the OID {@code 1.3.6.1.4.1.1466.101.120.5}.
+ *
+ * @return A reference to the {@code namingContexts} Attribute Type.
+ */
+ public static AttributeType getNamingContextsAttributeType()
+ {
+ return NAMING_CONTEXTS_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code objectClasses} Attribute Type
+ * which has the OID {@code 2.5.21.6}.
+ *
+ * @return A reference to the {@code objectClasses} Attribute Type.
+ */
+ public static AttributeType getObjectClassesAttributeType()
+ {
+ return OBJECT_CLASSES_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code objectClass} Attribute Type
+ * which has the OID {@code 2.5.4.0}.
+ *
+ * @return A reference to the {@code objectClass} Attribute Type.
+ */
+ public static AttributeType getObjectClassAttributeType()
+ {
+ return OBJECT_CLASS_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code ou} Attribute Type
+ * which has the OID {@code 2.5.4.11}.
+ *
+ * @return A reference to the {@code ou} Attribute Type.
+ */
+ public static AttributeType getOUAttributeType()
+ {
+ return OU_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code owner} Attribute Type
+ * which has the OID {@code 2.5.4.32}.
+ *
+ * @return A reference to the {@code owner} Attribute Type.
+ */
+ public static AttributeType getOwnerAttributeType()
+ {
+ return OWNER_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code o} Attribute Type
+ * which has the OID {@code 2.5.4.10}.
+ *
+ * @return A reference to the {@code o} Attribute Type.
+ */
+ public static AttributeType getOAttributeType()
+ {
+ return O_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code physicalDeliveryOfficeName} Attribute Type
+ * which has the OID {@code 2.5.4.19}.
+ *
+ * @return A reference to the {@code physicalDeliveryOfficeName} Attribute Type.
+ */
+ public static AttributeType getPhysicalDeliveryOfficeNameAttributeType()
+ {
+ return PHYSICAL_DELIVERY_OFFICE_NAME_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code postalAddress} Attribute Type
+ * which has the OID {@code 2.5.4.16}.
+ *
+ * @return A reference to the {@code postalAddress} Attribute Type.
+ */
+ public static AttributeType getPostalAddressAttributeType()
+ {
+ return POSTAL_ADDRESS_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code postalCode} Attribute Type
+ * which has the OID {@code 2.5.4.17}.
+ *
+ * @return A reference to the {@code postalCode} Attribute Type.
+ */
+ public static AttributeType getPostalCodeAttributeType()
+ {
+ return POSTAL_CODE_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code postOfficeBox} Attribute Type
+ * which has the OID {@code 2.5.4.18}.
+ *
+ * @return A reference to the {@code postOfficeBox} Attribute Type.
+ */
+ public static AttributeType getPostOfficeBoxAttributeType()
+ {
+ return POST_OFFICE_BOX_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code preferredDeliveryMethod} Attribute Type
+ * which has the OID {@code 2.5.4.28}.
+ *
+ * @return A reference to the {@code preferredDeliveryMethod} Attribute Type.
+ */
+ public static AttributeType getPreferredDeliveryMethodAttributeType()
+ {
+ return PREFERRED_DELIVERY_METHOD_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code registeredAddress} Attribute Type
+ * which has the OID {@code 2.5.4.26}.
+ *
+ * @return A reference to the {@code registeredAddress} Attribute Type.
+ */
+ public static AttributeType getRegisteredAddressAttributeType()
+ {
+ return REGISTERED_ADDRESS_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code roleOccupant} Attribute Type
+ * which has the OID {@code 2.5.4.33}.
+ *
+ * @return A reference to the {@code roleOccupant} Attribute Type.
+ */
+ public static AttributeType getRoleOccupantAttributeType()
+ {
+ return ROLE_OCCUPANT_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code searchGuide} Attribute Type
+ * which has the OID {@code 2.5.4.14}.
+ *
+ * @return A reference to the {@code searchGuide} Attribute Type.
+ */
+ public static AttributeType getSearchGuideAttributeType()
+ {
+ return SEARCH_GUIDE_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code seeAlso} Attribute Type
+ * which has the OID {@code 2.5.4.34}.
+ *
+ * @return A reference to the {@code seeAlso} Attribute Type.
+ */
+ public static AttributeType getSeeAlsoAttributeType()
+ {
+ return SEE_ALSO_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code serialNumber} Attribute Type
+ * which has the OID {@code 2.5.4.5}.
+ *
+ * @return A reference to the {@code serialNumber} Attribute Type.
+ */
+ public static AttributeType getSerialNumberAttributeType()
+ {
+ return SERIAL_NUMBER_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code sn} Attribute Type
+ * which has the OID {@code 2.5.4.4}.
+ *
+ * @return A reference to the {@code sn} Attribute Type.
+ */
+ public static AttributeType getSNAttributeType()
+ {
+ return SN_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code street} Attribute Type
+ * which has the OID {@code 2.5.4.9}.
+ *
+ * @return A reference to the {@code street} Attribute Type.
+ */
+ public static AttributeType getStreetAttributeType()
+ {
+ return STREET_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code structuralObjectClass} Attribute Type
+ * which has the OID {@code 2.5.21.9}.
+ *
+ * @return A reference to the {@code structuralObjectClass} Attribute Type.
+ */
+ public static AttributeType getStructuralObjectClassAttributeType()
+ {
+ return STRUCTURAL_OBJECT_CLASS_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code st} Attribute Type
+ * which has the OID {@code 2.5.4.8}.
+ *
+ * @return A reference to the {@code st} Attribute Type.
+ */
+ public static AttributeType getSTAttributeType()
+ {
+ return ST_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code subschemaSubentry} Attribute Type
+ * which has the OID {@code 2.5.18.10}.
+ *
+ * @return A reference to the {@code subschemaSubentry} Attribute Type.
+ */
+ public static AttributeType getSubschemaSubentryAttributeType()
+ {
+ return SUBSCHEMA_SUBENTRY_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code supportedAuthPasswordSchemes} Attribute Type
+ * which has the OID {@code 1.3.6.1.4.1.4203.1.3.3}.
+ *
+ * @return A reference to the {@code supportedAuthPasswordSchemes} Attribute Type.
+ */
+ public static AttributeType getSupportedAuthPasswordSchemesAttributeType()
+ {
+ return SUPPORTED_AUTH_PASSWORD_SCHEMES_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code supportedControl} Attribute Type
+ * which has the OID {@code 1.3.6.1.4.1.1466.101.120.13}.
+ *
+ * @return A reference to the {@code supportedControl} Attribute Type.
+ */
+ public static AttributeType getSupportedControlAttributeType()
+ {
+ return SUPPORTED_CONTROL_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code supportedExtension} Attribute Type
+ * which has the OID {@code 1.3.6.1.4.1.1466.101.120.7}.
+ *
+ * @return A reference to the {@code supportedExtension} Attribute Type.
+ */
+ public static AttributeType getSupportedExtensionAttributeType()
+ {
+ return SUPPORTED_EXTENSION_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code supportedFeatures} Attribute Type
+ * which has the OID {@code 1.3.6.1.4.1.4203.1.3.5}.
+ *
+ * @return A reference to the {@code supportedFeatures} Attribute Type.
+ */
+ public static AttributeType getSupportedFeaturesAttributeType()
+ {
+ return SUPPORTED_FEATURES_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code supportedLDAPVersion} Attribute Type
+ * which has the OID {@code 1.3.6.1.4.1.1466.101.120.15}.
+ *
+ * @return A reference to the {@code supportedLDAPVersion} Attribute Type.
+ */
+ public static AttributeType getSupportedLDAPVersionAttributeType()
+ {
+ return SUPPORTED_LDAP_VERSION_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code supportedSASLMechanisms} Attribute Type
+ * which has the OID {@code 1.3.6.1.4.1.1466.101.120.14}.
+ *
+ * @return A reference to the {@code supportedSASLMechanisms} Attribute Type.
+ */
+ public static AttributeType getSupportedSaslMechanismsAttributeType()
+ {
+ return SUPPORTED_SASL_MECHANISMS_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code telephoneNumber} Attribute Type
+ * which has the OID {@code 2.5.4.20}.
+ *
+ * @return A reference to the {@code telephoneNumber} Attribute Type.
+ */
+ public static AttributeType getTelephoneNumberAttributeType()
+ {
+ return TELEPHONE_NUMBER_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code teletexTerminalIdentifier} Attribute Type
+ * which has the OID {@code 2.5.4.22}.
+ *
+ * @return A reference to the {@code teletexTerminalIdentifier} Attribute Type.
+ */
+ public static AttributeType getTeletexTerminalIdentifierAttributeType()
+ {
+ return TELETEX_TERMINAL_IDENTIFIER_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code telexNumber} Attribute Type
+ * which has the OID {@code 2.5.4.21}.
+ *
+ * @return A reference to the {@code telexNumber} Attribute Type.
+ */
+ public static AttributeType getTelexNumberAttributeType()
+ {
+ return TELEX_NUMBER_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code title} Attribute Type
+ * which has the OID {@code 2.5.4.12}.
+ *
+ * @return A reference to the {@code title} Attribute Type.
+ */
+ public static AttributeType getTitleAttributeType()
+ {
+ return TITLE_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code uid} Attribute Type
+ * which has the OID {@code 0.9.2342.19200300.100.1.1}.
+ *
+ * @return A reference to the {@code uid} Attribute Type.
+ */
+ public static AttributeType getUIDAttributeType()
+ {
+ return UID_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code uniqueMember} Attribute Type
+ * which has the OID {@code 2.5.4.50}.
+ *
+ * @return A reference to the {@code uniqueMember} Attribute Type.
+ */
+ public static AttributeType getUniqueMemberAttributeType()
+ {
+ return UNIQUE_MEMBER_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code userPassword} Attribute Type
+ * which has the OID {@code 2.5.4.35}.
+ *
+ * @return A reference to the {@code userPassword} Attribute Type.
+ */
+ public static AttributeType getUserPasswordAttributeType()
+ {
+ return USER_PASSWORD_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code vendorName} Attribute Type
+ * which has the OID {@code 1.3.6.1.1.4}.
+ *
+ * @return A reference to the {@code vendorName} Attribute Type.
+ */
+ public static AttributeType getVendorNameAttributeType()
+ {
+ return VENDOR_NAME_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code vendorVersion} Attribute Type
+ * which has the OID {@code 1.3.6.1.1.5}.
+ *
+ * @return A reference to the {@code vendorVersion} Attribute Type.
+ */
+ public static AttributeType getVendorVersionAttributeType()
+ {
+ return VENDOR_VERSION_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code x121Address} Attribute Type
+ * which has the OID {@code 2.5.4.24}.
+ *
+ * @return A reference to the {@code x121Address} Attribute Type.
+ */
+ public static AttributeType getX121AddressAttributeType()
+ {
+ return X121_ADDRESS_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code x500UniqueIdentifier} Attribute Type
+ * which has the OID {@code 2.5.4.45}.
+ *
+ * @return A reference to the {@code x500UniqueIdentifier} Attribute Type.
+ */
+ public static AttributeType getX500UniqueIdentifierAttributeType()
+ {
+ return X500_UNIQUE_IDENTIFIER_ATTRIBUTE_TYPE;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code alias} Object Class
+ * which has the OID {@code 2.5.6.1}.
+ *
+ * @return A reference to the {@code alias} Object Class.
+ */
+ public static ObjectClass getAliasObjectClass()
+ {
+ return ALIAS_OBJECT_CLASS;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code applicationProcess} Object Class
+ * which has the OID {@code 2.5.6.11}.
+ *
+ * @return A reference to the {@code applicationProcess} Object Class.
+ */
+ public static ObjectClass getApplicationProcessObjectClass()
+ {
+ return APPLICATION_PROCESS_OBJECT_CLASS;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code authPasswordObject} Object Class
+ * which has the OID {@code 1.3.6.1.4.1.4203.1.4.7}.
+ *
+ * @return A reference to the {@code authPasswordObject} Object Class.
+ */
+ public static ObjectClass getAuthPasswordObjectObjectClass()
+ {
+ return AUTH_PASSWORD_OBJECT_OBJECT_CLASS;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code country} Object Class
+ * which has the OID {@code 2.5.6.2}.
+ *
+ * @return A reference to the {@code country} Object Class.
+ */
+ public static ObjectClass getCountryObjectClass()
+ {
+ return COUNTRY_OBJECT_CLASS;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code dcObject} Object Class
+ * which has the OID {@code 1.3.6.1.4.1.1466.344}.
+ *
+ * @return A reference to the {@code dcObject} Object Class.
+ */
+ public static ObjectClass getDCObjectObjectClass()
+ {
+ return DC_OBJECT_OBJECT_CLASS;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code device} Object Class
+ * which has the OID {@code 2.5.6.14}.
+ *
+ * @return A reference to the {@code device} Object Class.
+ */
+ public static ObjectClass getDeviceObjectClass()
+ {
+ return DEVICE_OBJECT_CLASS;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code extensibleObject} Object Class
+ * which has the OID {@code 1.3.6.1.4.1.1466.101.120.111}.
+ *
+ * @return A reference to the {@code extensibleObject} Object Class.
+ */
+ public static ObjectClass getExtensibleObjectObjectClass()
+ {
+ return EXTENSIBLE_OBJECT_OBJECT_CLASS;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code groupOfNames} Object Class
+ * which has the OID {@code 2.5.6.9}.
+ *
+ * @return A reference to the {@code groupOfNames} Object Class.
+ */
+ public static ObjectClass getGroupOfNamesObjectClass()
+ {
+ return GROUP_OF_NAMES_OBJECT_CLASS;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code groupOfUniqueNames} Object Class
+ * which has the OID {@code 2.5.6.17}.
+ *
+ * @return A reference to the {@code groupOfUniqueNames} Object Class.
+ */
+ public static ObjectClass getGroupOfUniqueNamesObjectClass()
+ {
+ return GROUP_OF_UNIQUE_NAMES_OBJECT_CLASS;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code locality} Object Class
+ * which has the OID {@code 2.5.6.3}.
+ *
+ * @return A reference to the {@code locality} Object Class.
+ */
+ public static ObjectClass getLocalityObjectClass()
+ {
+ return LOCALITY_OBJECT_CLASS;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code organizationalPerson} Object Class
+ * which has the OID {@code 2.5.6.7}.
+ *
+ * @return A reference to the {@code organizationalPerson} Object Class.
+ */
+ public static ObjectClass getOrganizationalPersonObjectClass()
+ {
+ return ORGANIZATIONAL_PERSON_OBJECT_CLASS;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code organizationalRole} Object Class
+ * which has the OID {@code 2.5.6.8}.
+ *
+ * @return A reference to the {@code organizationalRole} Object Class.
+ */
+ public static ObjectClass getOrganizationalRoleObjectClass()
+ {
+ return ORGANIZATIONAL_ROLE_OBJECT_CLASS;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code organizationalUnit} Object Class
+ * which has the OID {@code 2.5.6.5}.
+ *
+ * @return A reference to the {@code organizationalUnit} Object Class.
+ */
+ public static ObjectClass getOrganizationalUnitObjectClass()
+ {
+ return ORGANIZATIONAL_UNIT_OBJECT_CLASS;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code organization} Object Class
+ * which has the OID {@code 2.5.6.4}.
+ *
+ * @return A reference to the {@code organization} Object Class.
+ */
+ public static ObjectClass getOrganizationObjectClass()
+ {
+ return ORGANIZATION_OBJECT_CLASS;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code person} Object Class
+ * which has the OID {@code 2.5.6.6}.
+ *
+ * @return A reference to the {@code person} Object Class.
+ */
+ public static ObjectClass getPersonObjectClass()
+ {
+ return PERSON_OBJECT_CLASS;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code residentialPerson} Object Class
+ * which has the OID {@code 2.5.6.10}.
+ *
+ * @return A reference to the {@code residentialPerson} Object Class.
+ */
+ public static ObjectClass getResidentialPersonObjectClass()
+ {
+ return RESIDENTIAL_PERSON_OBJECT_CLASS;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code subschema} Object Class
+ * which has the OID {@code 2.5.20.1}.
+ *
+ * @return A reference to the {@code subschema} Object Class.
+ */
+ public static ObjectClass getSubschemaObjectClass()
+ {
+ return SUBSCHEMA_OBJECT_CLASS;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code top} Object Class
+ * which has the OID {@code 2.5.6.0}.
+ *
+ * @return A reference to the {@code top} Object Class.
+ */
+ public static ObjectClass getTopObjectClass()
+ {
+ return TOP_OBJECT_CLASS;
+ }
+
+
+
+ /**
+ * Returns a reference to the {@code uidObject} Object Class
+ * which has the OID {@code 1.3.6.1.1.3.1}.
+ *
+ * @return A reference to the {@code uidObject} Object Class.
+ */
+ public static ObjectClass getUIDObjectObjectClass()
+ {
+ return UID_OBJECT_OBJECT_CLASS;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CoreSchemaImpl.java b/sdk/src/org/opends/sdk/schema/CoreSchemaImpl.java
new file mode 100644
index 0000000..c48b5c2
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CoreSchemaImpl.java
@@ -0,0 +1,1146 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.*;
+
+import java.util.*;
+
+import org.opends.messages.Message;
+
+
+
+final class CoreSchemaImpl
+{
+ private static final Map<String, List<String>> X500_ORIGIN =
+ Collections.singletonMap(SCHEMA_PROPERTY_ORIGIN, Collections
+ .singletonList("X.500"));
+ private static final Map<String, List<String>> RFC2252_ORIGIN =
+ Collections.singletonMap(SCHEMA_PROPERTY_ORIGIN, Collections
+ .singletonList("RFC 2252"));
+ private static final Map<String, List<String>> RFC3045_ORIGIN =
+ Collections.singletonMap(SCHEMA_PROPERTY_ORIGIN, Collections
+ .singletonList("RFC 3045"));
+ private static final Map<String, List<String>> RFC3112_ORIGIN =
+ Collections.singletonMap(SCHEMA_PROPERTY_ORIGIN, Collections
+ .singletonList("RFC 3112"));
+ private static final Map<String, List<String>> RFC4512_ORIGIN =
+ Collections.singletonMap(SCHEMA_PROPERTY_ORIGIN, Collections
+ .singletonList("RFC 4512"));
+ private static final Map<String, List<String>> RFC4517_ORIGIN =
+ Collections.singletonMap(SCHEMA_PROPERTY_ORIGIN, Collections
+ .singletonList("RFC 4517"));
+ private static final Map<String, List<String>> RFC4519_ORIGIN =
+ Collections.singletonMap(SCHEMA_PROPERTY_ORIGIN, Collections
+ .singletonList("RFC 4519"));
+ private static final Map<String, List<String>> RFC4530_ORIGIN =
+ Collections.singletonMap(SCHEMA_PROPERTY_ORIGIN, Collections
+ .singletonList("RFC 4530"));
+ static final Map<String, List<String>> OPENDS_ORIGIN =
+ Collections.singletonMap(SCHEMA_PROPERTY_ORIGIN, Collections
+ .singletonList("OpenDS Directory Server"));
+ private static final String EMPTY_STRING = "".intern();
+ private static final Set<String> EMPTY_STRING_SET =
+ Collections.emptySet();
+
+ private static final Schema SINGLETON;
+
+ // Package private so that we can check for warnings in the unit
+ // tests.
+ static final List<Message> CORE_SCHEMA_WARNINGS =
+ new LinkedList<Message>();
+
+ static
+ {
+ final SchemaBuilder builder = new SchemaBuilder();
+ defaultSyntaxes(builder);
+ defaultMatchingRules(builder);
+ defaultAttributeTypes(builder);
+ defaultObjectClasses(builder);
+
+ addRFC4519(builder);
+ addRFC4530(builder);
+ addRFC3045(builder);
+ addRFC3112(builder);
+ addSunProprietary(builder);
+
+ SINGLETON = builder.toSchema(CORE_SCHEMA_WARNINGS).nonStrict();
+ }
+
+
+
+ static Schema getInstance()
+ {
+ return SINGLETON;
+ }
+
+
+
+ private static void addRFC3045(SchemaBuilder builder)
+ {
+ builder.addAttributeType("1.3.6.1.1.4", Collections
+ .singletonList("vendorName"), EMPTY_STRING, false, null,
+ EMR_CASE_EXACT_IA5_OID, null, null, null,
+ SYNTAX_DIRECTORY_STRING_OID, true, false, true,
+ AttributeUsage.DSA_OPERATION, RFC3045_ORIGIN, false);
+
+ builder.addAttributeType("1.3.6.1.1.5", Collections
+ .singletonList("vendorVersion"), EMPTY_STRING, false, null,
+ EMR_CASE_EXACT_IA5_OID, null, null, null,
+ SYNTAX_DIRECTORY_STRING_OID, true, false, true,
+ AttributeUsage.DSA_OPERATION, RFC3045_ORIGIN, false);
+ }
+
+
+
+ private static void addRFC3112(SchemaBuilder builder)
+ {
+ builder.addSyntax(SYNTAX_AUTH_PASSWORD_OID,
+ SYNTAX_AUTH_PASSWORD_DESCRIPTION, RFC3112_ORIGIN,
+ new AuthPasswordSyntaxImpl(), false);
+ builder.addMatchingRule(EMR_AUTH_PASSWORD_EXACT_OID, Collections
+ .singletonList(EMR_AUTH_PASSWORD_EXACT_NAME),
+ EMR_AUTH_PASSWORD_EXACT_DESCRIPTION, false,
+ SYNTAX_AUTH_PASSWORD_OID, RFC3112_ORIGIN,
+ new AuthPasswordExactEqualityMatchingRuleImpl(), false);
+ builder.addAttributeType("1.3.6.1.4.1.4203.1.3.3", Collections
+ .singletonList("supportedAuthPasswordSchemes"),
+ "supported password storage schemes", false, null,
+ EMR_CASE_EXACT_IA5_OID, null, null, null,
+ SYNTAX_IA5_STRING_OID, false, false, false,
+ AttributeUsage.DSA_OPERATION, RFC3112_ORIGIN, false);
+ builder.addAttributeType("1.3.6.1.4.1.4203.1.3.4", Collections
+ .singletonList("authPassword"),
+ "password authentication information", false, null,
+ EMR_AUTH_PASSWORD_EXACT_OID, null, null, null,
+ SYNTAX_AUTH_PASSWORD_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC3112_ORIGIN, false);
+ builder.addObjectClass("1.3.6.1.4.1.4203.1.4.7", Collections
+ .singletonList("authPasswordObject"),
+ "authentication password mix in class", false,
+ EMPTY_STRING_SET, EMPTY_STRING_SET, Collections
+ .singleton("authPassword"), ObjectClassType.AUXILIARY,
+ RFC3112_ORIGIN, false);
+ }
+
+
+
+ private static void addRFC4519(SchemaBuilder builder)
+ {
+ builder.addAttributeType("2.5.4.15", Collections
+ .singletonList("businessCategory"), EMPTY_STRING, false, null,
+ EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+ SYNTAX_DIRECTORY_STRING_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.6", Arrays.asList("c",
+ "countryName"), EMPTY_STRING, false, "name", null, null, null,
+ null, SYNTAX_COUNTRY_STRING_OID, true, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.3", Arrays.asList("cn",
+ "commonName"), EMPTY_STRING, false, "name", null, null, null,
+ null, null, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("0.9.2342.19200300.100.1.25", Arrays
+ .asList("dc", "domainComponent"), EMPTY_STRING, false, null,
+ EMR_CASE_IGNORE_IA5_OID, null, SMR_CASE_IGNORE_IA5_OID, null,
+ SYNTAX_IA5_STRING_OID, true, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.13", Collections
+ .singletonList("description"), EMPTY_STRING, false, null,
+ EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+ SYNTAX_DIRECTORY_STRING_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.27", Collections
+ .singletonList("destinationIndicator"), EMPTY_STRING, false,
+ null, EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+ SYNTAX_PRINTABLE_STRING_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.49", Collections
+ .singletonList("distinguishedName"), EMPTY_STRING, false, null,
+ EMR_DN_OID, null, null, null, SYNTAX_DN_OID, false, false,
+ false, AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.46", Collections
+ .singletonList("dnQualifier"), EMPTY_STRING, false, null,
+ EMR_CASE_IGNORE_OID, OMR_CASE_IGNORE_OID, SMR_CASE_IGNORE_OID,
+ null, SYNTAX_PRINTABLE_STRING_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.47", Collections
+ .singletonList("enhancedSearchGuide"), EMPTY_STRING, false,
+ null, null, null, null, null, SYNTAX_ENHANCED_GUIDE_OID, false,
+ false, false, AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN,
+ false);
+
+ builder.addAttributeType("2.5.4.23", Collections
+ .singletonList("facsimileTelephoneNumber"), EMPTY_STRING,
+ false, null, null, null, null, null, SYNTAX_FAXNUMBER_OID,
+ false, false, false, AttributeUsage.USER_APPLICATIONS,
+ RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.44", Collections
+ .singletonList("generationQualifier"), EMPTY_STRING, false,
+ "name", null, null, null, null, null, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.42", Collections
+ .singletonList("givenName"), EMPTY_STRING, false, "name", null,
+ null, null, null, null, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.51", Collections
+ .singletonList("houseIdentifier"), EMPTY_STRING, false, null,
+ EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+ SYNTAX_DIRECTORY_STRING_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.43", Collections
+ .singletonList("initials"), EMPTY_STRING, false, "name", null,
+ null, null, null, null, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.25", Collections
+ .singletonList("internationalISDNNumber"), EMPTY_STRING, false,
+ null, EMR_NUMERIC_STRING_OID, null, SMR_NUMERIC_STRING_OID,
+ null, SYNTAX_NUMERIC_STRING_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.7", Arrays.asList("l",
+ "localityName"), EMPTY_STRING, false, "name", null, null, null,
+ null, null, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.31", Collections
+ .singletonList("member"), EMPTY_STRING, false,
+ "distinguishedName", null, null, null, null, null, false,
+ false, false, AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN,
+ false);
+
+ builder.addAttributeType("2.5.4.41", Collections
+ .singletonList("name"), EMPTY_STRING, false, null,
+ EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+ SYNTAX_DIRECTORY_STRING_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.10", Arrays.asList("o",
+ "organizationName"), EMPTY_STRING, false, null,
+ EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+ SYNTAX_DIRECTORY_STRING_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.11", Arrays.asList("ou",
+ "organizationalUnitName"), EMPTY_STRING, false, null,
+ EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+ SYNTAX_DIRECTORY_STRING_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.32", Collections
+ .singletonList("owner"), EMPTY_STRING, false,
+ "distinguishedName", null, null, null, null, null, false,
+ false, false, AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN,
+ false);
+
+ builder.addAttributeType("2.5.4.19", Collections
+ .singletonList("physicalDeliveryOfficeName"), EMPTY_STRING,
+ false, null, EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID,
+ null, SYNTAX_DIRECTORY_STRING_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.16", Collections
+ .singletonList("postalAddress"), EMPTY_STRING, false, null,
+ EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+ SYNTAX_DIRECTORY_STRING_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.17", Collections
+ .singletonList("postalCode"), EMPTY_STRING, false, null,
+ EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+ SYNTAX_DIRECTORY_STRING_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.18", Collections
+ .singletonList("postOfficeBox"), EMPTY_STRING, false, null,
+ EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+ SYNTAX_DIRECTORY_STRING_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.28", Collections
+ .singletonList("preferredDeliveryMethod"), EMPTY_STRING, false,
+ null, null, null, null, null, SYNTAX_DELIVERY_METHOD_OID, true,
+ false, false, AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN,
+ false);
+
+ builder.addAttributeType("2.5.4.26", Collections
+ .singletonList("registeredAddress"), EMPTY_STRING, false,
+ "postalAddress", null, null, null, null,
+ SYNTAX_POSTAL_ADDRESS_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.33", Collections
+ .singletonList("roleOccupant"), EMPTY_STRING, false,
+ "distinguishedName", null, null, null, null, null, false,
+ false, false, AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN,
+ false);
+
+ builder.addAttributeType("2.5.4.14", Collections
+ .singletonList("searchGuide"), EMPTY_STRING, false, null, null,
+ null, null, null, SYNTAX_GUIDE_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.34", Collections
+ .singletonList("seeAlso"), EMPTY_STRING, false,
+ "distinguishedName", null, null, null, null, null, false,
+ false, false, AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN,
+ false);
+
+ builder.addAttributeType("2.5.4.5", Collections
+ .singletonList("serialNumber"), EMPTY_STRING, false, null,
+ EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+ SYNTAX_PRINTABLE_STRING_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.4", Arrays.asList("sn", "surname"),
+ EMPTY_STRING, false, "name", null, null, null, null, null,
+ false, false, false, AttributeUsage.USER_APPLICATIONS,
+ RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.8", Arrays.asList("st",
+ "stateOrProvinceName"), EMPTY_STRING, false, "name", null,
+ null, null, null, null, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.9", Arrays.asList("street",
+ "streetAddress"), EMPTY_STRING, false, null,
+ EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+ SYNTAX_DIRECTORY_STRING_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.20", Collections
+ .singletonList("telephoneNumber"), EMPTY_STRING, false, null,
+ EMR_TELEPHONE_OID, null, SMR_TELEPHONE_OID, null,
+ SYNTAX_TELEPHONE_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.22", Collections
+ .singletonList("teletexTerminalIdentifier"), EMPTY_STRING,
+ false, null, null, null, null, null,
+ SYNTAX_TELETEX_TERM_ID_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.21", Collections
+ .singletonList("telexNumber"), EMPTY_STRING, false, null, null,
+ null, null, null, SYNTAX_TELEX_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.12", Collections
+ .singletonList("title"), EMPTY_STRING, false, "name", null,
+ null, null, null, null, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("0.9.2342.19200300.100.1.1", Arrays
+ .asList("uid", "userid"), EMPTY_STRING, false, null,
+ EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+ SYNTAX_DIRECTORY_STRING_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.50", Collections
+ .singletonList("uniqueMember"), EMPTY_STRING, false, null,
+ EMR_UNIQUE_MEMBER_OID, null, null, null,
+ SYNTAX_NAME_AND_OPTIONAL_UID_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.35", Collections
+ .singletonList("userPassword"), EMPTY_STRING, false, null,
+ EMR_OCTET_STRING_OID, null, null, null,
+ SYNTAX_OCTET_STRING_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.24", Collections
+ .singletonList("x121Address"), EMPTY_STRING, false, null,
+ EMR_NUMERIC_STRING_OID, null, SMR_NUMERIC_STRING_OID, null,
+ SYNTAX_NUMERIC_STRING_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.45", Collections
+ .singletonList("x500UniqueIdentifier"), EMPTY_STRING, false,
+ null, EMR_BIT_STRING_OID, null, null, null,
+ SYNTAX_BIT_STRING_OID, false, false, false,
+ AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+ Set<String> attrs = new HashSet<String>();
+ attrs.add("seeAlso");
+ attrs.add("ou");
+ attrs.add("l");
+ attrs.add("description");
+
+ builder.addObjectClass("2.5.6.11", Collections
+ .singletonList("applicationProcess"), EMPTY_STRING, false,
+ Collections.singleton(TOP_OBJECTCLASS_NAME), Collections
+ .singleton("cn"), attrs, ObjectClassType.STRUCTURAL,
+ RFC4519_ORIGIN, false);
+
+ attrs = new HashSet<String>();
+ attrs.add("searchGuide");
+ attrs.add("description");
+
+ builder.addObjectClass("2.5.6.2", Collections
+ .singletonList("country"), EMPTY_STRING, false, Collections
+ .singleton(TOP_OBJECTCLASS_NAME), Collections.singleton("c"),
+ attrs, ObjectClassType.STRUCTURAL, RFC4519_ORIGIN, false);
+
+ builder.addObjectClass("1.3.6.1.4.1.1466.344", Collections
+ .singletonList("dcObject"), EMPTY_STRING, false, Collections
+ .singleton(TOP_OBJECTCLASS_NAME), Collections.singleton("dc"),
+ EMPTY_STRING_SET, ObjectClassType.AUXILIARY, RFC4519_ORIGIN,
+ false);
+
+ attrs = new HashSet<String>();
+ attrs.add("serialNumber");
+ attrs.add("seeAlso");
+ attrs.add("owner");
+ attrs.add("ou");
+ attrs.add("o");
+ attrs.add("l");
+ attrs.add("description");
+
+ builder.addObjectClass("2.5.6.14", Collections
+ .singletonList("device"), EMPTY_STRING, false, Collections
+ .singleton(TOP_OBJECTCLASS_NAME), Collections.singleton("cn"),
+ attrs, ObjectClassType.STRUCTURAL, RFC4519_ORIGIN, false);
+
+ Set<String> must = new HashSet<String>();
+ must.add("member");
+ must.add("cn");
+
+ attrs = new HashSet<String>();
+ attrs.add("businessCategory");
+ attrs.add("seeAlso");
+ attrs.add("owner");
+ attrs.add("ou");
+ attrs.add("o");
+ attrs.add("description");
+
+ builder.addObjectClass("2.5.6.9", Collections
+ .singletonList("groupOfNames"), EMPTY_STRING, false,
+ Collections.singleton(TOP_OBJECTCLASS_NAME), must, attrs,
+ ObjectClassType.STRUCTURAL, RFC4519_ORIGIN, false);
+
+ attrs = new HashSet<String>();
+ attrs.add("businessCategory");
+ attrs.add("seeAlso");
+ attrs.add("owner");
+ attrs.add("ou");
+ attrs.add("o");
+ attrs.add("description");
+
+ builder.addObjectClass("2.5.6.17", Collections
+ .singletonList("groupOfUniqueNames"), EMPTY_STRING, false,
+ Collections.singleton(TOP_OBJECTCLASS_NAME), must, attrs,
+ ObjectClassType.STRUCTURAL, RFC4519_ORIGIN, false);
+
+ attrs = new HashSet<String>();
+ attrs.add("street");
+ attrs.add("seeAlso");
+ attrs.add("searchGuide");
+ attrs.add("st");
+ attrs.add("l");
+ attrs.add("description");
+
+ builder.addObjectClass("2.5.6.3", Collections
+ .singletonList("locality"), EMPTY_STRING, false, Collections
+ .singleton(TOP_OBJECTCLASS_NAME), EMPTY_STRING_SET, attrs,
+ ObjectClassType.STRUCTURAL, RFC4519_ORIGIN, false);
+
+ attrs = new HashSet<String>();
+ attrs.add("userPassword");
+ attrs.add("searchGuide");
+ attrs.add("seeAlso");
+ attrs.add("businessCategory");
+ attrs.add("x121Address");
+ attrs.add("registeredAddress");
+ attrs.add("destinationIndicator");
+ attrs.add("preferredDeliveryMethod");
+ attrs.add("telexNumber");
+ attrs.add("teletexTerminalIdentifier");
+ attrs.add("telephoneNumber");
+ attrs.add("internationalISDNNumber");
+ attrs.add("facsimileTelephoneNumber");
+ attrs.add("street");
+ attrs.add("postOfficeBox");
+ attrs.add("postalCode");
+ attrs.add("postalAddress");
+ attrs.add("physicalDeliveryOfficeName");
+ attrs.add("st");
+ attrs.add("l");
+ attrs.add("description");
+
+ builder.addObjectClass("2.5.6.4", Collections
+ .singletonList("organization"), EMPTY_STRING, false,
+ Collections.singleton(TOP_OBJECTCLASS_NAME), Collections
+ .singleton("o"), attrs, ObjectClassType.STRUCTURAL,
+ RFC4519_ORIGIN, false);
+
+ attrs = new HashSet<String>();
+ attrs.add("title");
+ attrs.add("x121Address");
+ attrs.add("registeredAddress");
+ attrs.add("destinationIndicator");
+ attrs.add("preferredDeliveryMethod");
+ attrs.add("telexNumber");
+ attrs.add("teletexTerminalIdentifier");
+ attrs.add("telephoneNumber");
+ attrs.add("internationalISDNNumber");
+ attrs.add("facsimileTelephoneNumber");
+ attrs.add("street");
+ attrs.add("postOfficeBox");
+ attrs.add("postalCode");
+ attrs.add("postalAddress");
+ attrs.add("physicalDeliveryOfficeName");
+ attrs.add("ou");
+ attrs.add("st");
+ attrs.add("l");
+
+ builder.addObjectClass("2.5.6.7", Collections
+ .singletonList("organizationalPerson"), EMPTY_STRING, false,
+ Collections.singleton("person"), EMPTY_STRING_SET, attrs,
+ ObjectClassType.STRUCTURAL, RFC4519_ORIGIN, false);
+
+ attrs = new HashSet<String>();
+ attrs.add("x121Address");
+ attrs.add("registeredAddress");
+ attrs.add("destinationIndicator");
+ attrs.add("preferredDeliveryMethod");
+ attrs.add("telexNumber");
+ attrs.add("teletexTerminalIdentifier");
+ attrs.add("telephoneNumber");
+ attrs.add("internationalISDNNumber");
+ attrs.add("facsimileTelephoneNumber");
+ attrs.add("seeAlso");
+ attrs.add("roleOccupant");
+ attrs.add("preferredDeliveryMethod");
+ attrs.add("street");
+ attrs.add("postOfficeBox");
+ attrs.add("postalCode");
+ attrs.add("postalAddress");
+ attrs.add("physicalDeliveryOfficeName");
+ attrs.add("ou");
+ attrs.add("st");
+ attrs.add("l");
+ attrs.add("description");
+
+ builder.addObjectClass("2.5.6.8", Collections
+ .singletonList("organizationalRole"), EMPTY_STRING, false,
+ Collections.singleton(TOP_OBJECTCLASS_NAME), Collections
+ .singleton("cn"), attrs, ObjectClassType.STRUCTURAL,
+ RFC4519_ORIGIN, false);
+
+ attrs = new HashSet<String>();
+ attrs.add("businessCategory");
+ attrs.add("description");
+ attrs.add("destinationIndicator");
+ attrs.add("facsimileTelephoneNumber");
+ attrs.add("internationalISDNNumber");
+ attrs.add("l");
+ attrs.add("physicalDeliveryOfficeName");
+ attrs.add("postalAddress");
+ attrs.add("postalCode");
+ attrs.add("postOfficeBox");
+ attrs.add("preferredDeliveryMethod");
+ attrs.add("registeredAddress");
+ attrs.add("searchGuide");
+ attrs.add("seeAlso");
+ attrs.add("st");
+ attrs.add("street");
+ attrs.add("telephoneNumber");
+ attrs.add("teletexTerminalIdentifier");
+ attrs.add("telexNumber");
+ attrs.add("userPassword");
+ attrs.add("x121Address");
+
+ builder.addObjectClass("2.5.6.5", Collections
+ .singletonList("organizationalUnit"), EMPTY_STRING, false,
+ Collections.singleton(TOP_OBJECTCLASS_NAME), Collections
+ .singleton("ou"), attrs, ObjectClassType.STRUCTURAL,
+ RFC4519_ORIGIN, false);
+
+ must = new HashSet<String>();
+ must.add("sn");
+ must.add("cn");
+
+ attrs = new HashSet<String>();
+ attrs.add("userPassword");
+ attrs.add("telephoneNumber");
+ attrs.add("destinationIndicator");
+ attrs.add("seeAlso");
+ attrs.add("description");
+
+ builder.addObjectClass("2.5.6.6", Collections
+ .singletonList("person"), EMPTY_STRING, false, Collections
+ .singleton(TOP_OBJECTCLASS_NAME), must, attrs,
+ ObjectClassType.STRUCTURAL, RFC4519_ORIGIN, false);
+
+ attrs = new HashSet<String>();
+ attrs.add("businessCategory");
+ attrs.add("x121Address");
+ attrs.add("registeredAddress");
+ attrs.add("destinationIndicator");
+ attrs.add("preferredDeliveryMethod");
+ attrs.add("telexNumber");
+ attrs.add("teletexTerminalIdentifier");
+ attrs.add("telephoneNumber");
+ attrs.add("internationalISDNNumber");
+ attrs.add("facsimileTelephoneNumber");
+ attrs.add("preferredDeliveryMethod");
+ attrs.add("street");
+ attrs.add("postOfficeBox");
+ attrs.add("postalCode");
+ attrs.add("postalAddress");
+ attrs.add("physicalDeliveryOfficeName");
+ attrs.add("st");
+ attrs.add("l");
+
+ builder.addObjectClass("2.5.6.10", Collections
+ .singletonList("residentialPerson"), EMPTY_STRING, false,
+ Collections.singleton("person"), Collections.singleton("l"),
+ attrs, ObjectClassType.STRUCTURAL, RFC4519_ORIGIN, false);
+
+ builder.addObjectClass("1.3.6.1.1.3.1", Collections
+ .singletonList("uidObject"), EMPTY_STRING, false, Collections
+ .singleton(TOP_OBJECTCLASS_NAME), Collections.singleton("uid"),
+ attrs, ObjectClassType.AUXILIARY, RFC4519_ORIGIN, false);
+ }
+
+
+
+ private static void addRFC4530(SchemaBuilder builder)
+ {
+ builder.addSyntax(SYNTAX_UUID_OID, SYNTAX_UUID_DESCRIPTION,
+ RFC4530_ORIGIN, new UUIDSyntaxImpl(), false);
+ builder.addMatchingRule(EMR_UUID_OID, Collections
+ .singletonList(EMR_UUID_NAME), EMPTY_STRING, false,
+ SYNTAX_UUID_OID, RFC4530_ORIGIN,
+ new UUIDEqualityMatchingRuleImpl(), false);
+ builder.addMatchingRule(OMR_UUID_OID, Collections
+ .singletonList(OMR_UUID_NAME), EMPTY_STRING, false,
+ SYNTAX_UUID_OID, RFC4530_ORIGIN,
+ new UUIDOrderingMatchingRuleImpl(), false);
+ builder.addAttributeType("1.3.6.1.1.16.4", Collections
+ .singletonList("entryUUID"), "UUID of the entry", false, null,
+ EMR_UUID_OID, OMR_UUID_OID, null, null, SYNTAX_UUID_OID, true,
+ false, true, AttributeUsage.DIRECTORY_OPERATION,
+ RFC4530_ORIGIN, false);
+ }
+
+
+
+ private static void addSunProprietary(SchemaBuilder builder)
+ {
+ builder.addSyntax(SYNTAX_USER_PASSWORD_OID,
+ SYNTAX_USER_PASSWORD_DESCRIPTION, OPENDS_ORIGIN,
+ new UserPasswordSyntaxImpl(), false);
+ builder.addMatchingRule(EMR_USER_PASSWORD_EXACT_OID, Collections
+ .singletonList(EMR_USER_PASSWORD_EXACT_NAME),
+ EMR_USER_PASSWORD_EXACT_DESCRIPTION, false,
+ SYNTAX_USER_PASSWORD_OID, OPENDS_ORIGIN,
+ new UserPasswordExactEqualityMatchingRuleImpl(), false);
+ builder.addMatchingRule(AMR_DOUBLE_METAPHONE_OID, Collections
+ .singletonList(AMR_DOUBLE_METAPHONE_NAME),
+ AMR_DOUBLE_METAPHONE_DESCRIPTION, false,
+ SYNTAX_DIRECTORY_STRING_OID, OPENDS_ORIGIN,
+ new DoubleMetaphoneApproximateMatchingRuleImpl(), false);
+
+ }
+
+
+
+ private static void defaultAttributeTypes(SchemaBuilder builder)
+ {
+ builder.addAttributeType("2.5.4.0", Collections
+ .singletonList("objectClass"), EMPTY_STRING, false, null,
+ EMR_OID_NAME, null, null, null, SYNTAX_OID_OID, false, false,
+ false, AttributeUsage.USER_APPLICATIONS, RFC4512_ORIGIN, false);
+
+ builder.addAttributeType("2.5.4.1", Collections
+ .singletonList("aliasedObjectName"), EMPTY_STRING, false, null,
+ EMR_DN_NAME, null, null, null, SYNTAX_DN_OID, true, false,
+ false, AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN,
+ false);
+
+ builder.addAttributeType("2.5.18.1", Collections
+ .singletonList("createTimestamp"), EMPTY_STRING, false, null,
+ EMR_GENERALIZED_TIME_NAME, OMR_GENERALIZED_TIME_NAME, null,
+ null, SYNTAX_GENERALIZED_TIME_OID, true, false, true,
+ AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN, false);
+
+ builder.addAttributeType("2.5.18.2", Collections
+ .singletonList("modifyTimestamp"), EMPTY_STRING, false, null,
+ EMR_GENERALIZED_TIME_NAME, OMR_GENERALIZED_TIME_NAME, null,
+ null, SYNTAX_GENERALIZED_TIME_OID, true, false, true,
+ AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN, false);
+
+ builder
+ .addAttributeType("2.5.18.3", Collections
+ .singletonList("creatorsName"), EMPTY_STRING, false, null,
+ EMR_DN_NAME, null, null, null, SYNTAX_DN_OID, true, false,
+ true, AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN,
+ false);
+
+ builder
+ .addAttributeType("2.5.18.4", Collections
+ .singletonList("modifiersName"), EMPTY_STRING, false, null,
+ EMR_DN_NAME, null, null, null, SYNTAX_DN_OID, true, false,
+ true, AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN,
+ false);
+
+ builder
+ .addAttributeType("2.5.18.10", Collections
+ .singletonList("subschemaSubentry"), EMPTY_STRING, false,
+ null, EMR_DN_NAME, null, null, null, SYNTAX_DN_OID, true,
+ false, true, AttributeUsage.DIRECTORY_OPERATION,
+ RFC4512_ORIGIN, false);
+
+ builder.addAttributeType("2.5.21.5", Collections
+ .singletonList("attributeTypes"), EMPTY_STRING, false, null,
+ EMR_OID_FIRST_COMPONENT_NAME, null, null, null,
+ SYNTAX_ATTRIBUTE_TYPE_OID, false, false, false,
+ AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN, false);
+
+ builder.addAttributeType("2.5.21.6", Collections
+ .singletonList("objectClasses"), EMPTY_STRING, false, null,
+ EMR_OID_FIRST_COMPONENT_NAME, null, null, null,
+ SYNTAX_OBJECTCLASS_OID, false, false, false,
+ AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN, false);
+
+ builder.addAttributeType("2.5.21.4", Collections
+ .singletonList("matchingRules"), EMPTY_STRING, false, null,
+ EMR_OID_FIRST_COMPONENT_NAME, null, null, null,
+ SYNTAX_MATCHING_RULE_OID, false, false, false,
+ AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN, false);
+
+ builder.addAttributeType("2.5.21.8", Collections
+ .singletonList("matchingRuleUse"), EMPTY_STRING, false, null,
+ EMR_OID_FIRST_COMPONENT_NAME, null, null, null,
+ SYNTAX_MATCHING_RULE_USE_OID, false, false, false,
+ AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN, false);
+
+ builder.addAttributeType("2.5.21.9", Collections
+ .singletonList("structuralObjectClass"), EMPTY_STRING, false,
+ null, EMR_OID_NAME, null, null, null, SYNTAX_OID_OID, true,
+ false, true, AttributeUsage.DIRECTORY_OPERATION,
+ RFC4512_ORIGIN, false);
+
+ builder.addAttributeType("2.5.21.10", Collections
+ .singletonList("governingStructureRule"), EMPTY_STRING, false,
+ null, EMR_INTEGER_NAME, null, null, null, SYNTAX_INTEGER_OID,
+ true, false, true, AttributeUsage.DIRECTORY_OPERATION,
+ RFC4512_ORIGIN, false);
+
+ builder.addAttributeType("1.3.6.1.4.1.1466.101.120.5", Collections
+ .singletonList("namingContexts"), EMPTY_STRING, false, null,
+ null, null, null, null, SYNTAX_DN_OID, false, false, false,
+ AttributeUsage.DSA_OPERATION, RFC4512_ORIGIN, false);
+
+ builder.addAttributeType("1.3.6.1.4.1.1466.101.120.6", Collections
+ .singletonList("altServer"), EMPTY_STRING, false, null, null,
+ null, null, null, SYNTAX_IA5_STRING_OID, false, false, false,
+ AttributeUsage.DSA_OPERATION, RFC4512_ORIGIN, false);
+
+ builder.addAttributeType("1.3.6.1.4.1.1466.101.120.7", Collections
+ .singletonList("supportedExtension"), EMPTY_STRING, false,
+ null, null, null, null, null, SYNTAX_OID_OID, false, false,
+ false, AttributeUsage.DSA_OPERATION, RFC4512_ORIGIN, false);
+
+ builder.addAttributeType("1.3.6.1.4.1.1466.101.120.13", Collections
+ .singletonList("supportedControl"), EMPTY_STRING, false, null,
+ null, null, null, null, SYNTAX_OID_OID, false, false, false,
+ AttributeUsage.DSA_OPERATION, RFC4512_ORIGIN, false);
+
+ builder.addAttributeType("1.3.6.1.4.1.1466.101.120.14", Collections
+ .singletonList("supportedSASLMechanisms"), EMPTY_STRING, false,
+ null, null, null, null, null, SYNTAX_DIRECTORY_STRING_OID,
+ false, false, false, AttributeUsage.DSA_OPERATION,
+ RFC4512_ORIGIN, false);
+
+ builder.addAttributeType("1.3.6.1.4.1.4203.1.3.5", Collections
+ .singletonList("supportedFeatures"), EMPTY_STRING, false, null,
+ EMR_OID_NAME, null, null, null, SYNTAX_OID_OID, false, false,
+ false, AttributeUsage.DSA_OPERATION, RFC4512_ORIGIN, false);
+
+ builder.addAttributeType("1.3.6.1.4.1.1466.101.120.15", Collections
+ .singletonList("supportedLDAPVersion"), EMPTY_STRING, false,
+ null, null, null, null, null, SYNTAX_INTEGER_OID, false, false,
+ false, AttributeUsage.DSA_OPERATION, RFC4512_ORIGIN, false);
+
+ builder.addAttributeType("1.3.6.1.4.1.1466.101.120.16", Collections
+ .singletonList("ldapSyntaxes"), EMPTY_STRING, false, null,
+ EMR_OID_FIRST_COMPONENT_NAME, null, null, null,
+ SYNTAX_LDAP_SYNTAX_OID, false, false, false,
+ AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN, false);
+
+ builder.addAttributeType("2.5.21.1", Collections
+ .singletonList("ditStructureRules"), EMPTY_STRING, false, null,
+ EMR_INTEGER_FIRST_COMPONENT_NAME, null, null, null,
+ SYNTAX_DIT_STRUCTURE_RULE_OID, false, false, false,
+ AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN, false);
+
+ builder.addAttributeType("2.5.21.7", Collections
+ .singletonList("nameForms"), EMPTY_STRING, false, null,
+ EMR_OID_FIRST_COMPONENT_NAME, null, null, null,
+ SYNTAX_NAME_FORM_OID, false, false, false,
+ AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN, false);
+
+ builder.addAttributeType("2.5.21.2", Collections
+ .singletonList("ditContentRules"), EMPTY_STRING, false, null,
+ EMR_OID_FIRST_COMPONENT_NAME, null, null, null,
+ SYNTAX_DIT_CONTENT_RULE_OID, false, false, false,
+ AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN, false);
+ }
+
+
+
+ private static void defaultMatchingRules(SchemaBuilder builder)
+ {
+ builder.addMatchingRule(EMR_BIT_STRING_OID, Collections
+ .singletonList(EMR_BIT_STRING_NAME), EMPTY_STRING, false,
+ SYNTAX_BIT_STRING_OID, RFC4512_ORIGIN,
+ new BitStringEqualityMatchingRuleImpl(), false);
+ builder.addMatchingRule(EMR_BOOLEAN_OID, Collections
+ .singletonList(EMR_BOOLEAN_NAME), EMPTY_STRING, false,
+ SYNTAX_BOOLEAN_OID, RFC4512_ORIGIN,
+ new BooleanEqualityMatchingRuleImpl(), false);
+ builder.addMatchingRule(EMR_CASE_EXACT_IA5_OID, Collections
+ .singletonList(EMR_CASE_EXACT_IA5_NAME), EMPTY_STRING, false,
+ SYNTAX_IA5_STRING_OID, RFC4512_ORIGIN,
+ new CaseExactIA5EqualityMatchingRuleImpl(), false);
+ builder.addMatchingRule(SMR_CASE_EXACT_IA5_OID, Collections
+ .singletonList(SMR_CASE_EXACT_IA5_NAME), EMPTY_STRING, false,
+ SYNTAX_SUBSTRING_ASSERTION_OID, RFC4512_ORIGIN,
+ new CaseExactIA5SubstringMatchingRuleImpl(), false);
+ builder.addMatchingRule(EMR_CASE_EXACT_OID, Collections
+ .singletonList(EMR_CASE_EXACT_NAME), EMPTY_STRING, false,
+ SYNTAX_DIRECTORY_STRING_OID, RFC4512_ORIGIN,
+ new CaseExactEqualityMatchingRuleImpl(), false);
+ builder.addMatchingRule(OMR_CASE_EXACT_OID, Collections
+ .singletonList(OMR_CASE_EXACT_NAME), EMPTY_STRING, false,
+ SYNTAX_DIRECTORY_STRING_OID, RFC4512_ORIGIN,
+ new CaseExactOrderingMatchingRuleImpl(), false);
+ builder.addMatchingRule(SMR_CASE_EXACT_OID, Collections
+ .singletonList(SMR_CASE_EXACT_NAME), EMPTY_STRING, false,
+ SYNTAX_SUBSTRING_ASSERTION_OID, RFC4512_ORIGIN,
+ new CaseExactSubstringMatchingRuleImpl(), false);
+ builder.addMatchingRule(EMR_CASE_IGNORE_IA5_OID, Collections
+ .singletonList(EMR_CASE_IGNORE_IA5_NAME), EMPTY_STRING, false,
+ SYNTAX_IA5_STRING_OID, RFC4512_ORIGIN,
+ new CaseIgnoreIA5EqualityMatchingRuleImpl(), false);
+ builder.addMatchingRule(SMR_CASE_IGNORE_IA5_OID, Collections
+ .singletonList(SMR_CASE_IGNORE_IA5_NAME), EMPTY_STRING, false,
+ SYNTAX_SUBSTRING_ASSERTION_OID, RFC4512_ORIGIN,
+ new CaseIgnoreIA5SubstringMatchingRuleImpl(), false);
+ builder.addMatchingRule(EMR_CASE_IGNORE_LIST_OID, Collections
+ .singletonList(EMR_CASE_IGNORE_LIST_NAME), EMPTY_STRING, false,
+ SYNTAX_POSTAL_ADDRESS_OID, RFC4512_ORIGIN,
+ new CaseIgnoreListEqualityMatchingRuleImpl(), false);
+ builder.addMatchingRule(SMR_CASE_IGNORE_LIST_OID, Collections
+ .singletonList(SMR_CASE_IGNORE_LIST_NAME), EMPTY_STRING, false,
+ SYNTAX_SUBSTRING_ASSERTION_OID, RFC4512_ORIGIN,
+ new CaseIgnoreListSubstringMatchingRuleImpl(), false);
+ builder.addMatchingRule(EMR_CASE_IGNORE_OID, Collections
+ .singletonList(EMR_CASE_IGNORE_NAME), EMPTY_STRING, false,
+ SYNTAX_DIRECTORY_STRING_OID, RFC4512_ORIGIN,
+ new CaseIgnoreEqualityMatchingRuleImpl(), false);
+ builder.addMatchingRule(OMR_CASE_IGNORE_OID, Collections
+ .singletonList(OMR_CASE_IGNORE_NAME), EMPTY_STRING, false,
+ SYNTAX_DIRECTORY_STRING_OID, RFC4512_ORIGIN,
+ new CaseIgnoreOrderingMatchingRuleImpl(), false);
+ builder.addMatchingRule(SMR_CASE_IGNORE_OID, Collections
+ .singletonList(SMR_CASE_IGNORE_NAME), EMPTY_STRING, false,
+ SYNTAX_SUBSTRING_ASSERTION_OID, RFC4512_ORIGIN,
+ new CaseIgnoreSubstringMatchingRuleImpl(), false);
+ builder.addMatchingRule(EMR_DIRECTORY_STRING_FIRST_COMPONENT_OID,
+ Collections
+ .singletonList(EMR_DIRECTORY_STRING_FIRST_COMPONENT_NAME),
+ EMPTY_STRING, false, SYNTAX_DIRECTORY_STRING_OID,
+ RFC4512_ORIGIN,
+ new DirectoryStringFirstComponentEqualityMatchingRuleImpl(),
+ false);
+ builder.addMatchingRule(EMR_DN_OID, Collections
+ .singletonList(EMR_DN_NAME), EMPTY_STRING, false,
+ SYNTAX_DN_OID, RFC4512_ORIGIN,
+ new DistinguishedNameEqualityMatchingRuleImpl(), false);
+ builder.addMatchingRule(EMR_GENERALIZED_TIME_OID, Collections
+ .singletonList(EMR_GENERALIZED_TIME_NAME), EMPTY_STRING, false,
+ SYNTAX_GENERALIZED_TIME_OID, RFC4512_ORIGIN,
+ new GeneralizedTimeEqualityMatchingRuleImpl(), false);
+ builder.addMatchingRule(OMR_GENERALIZED_TIME_OID, Collections
+ .singletonList(OMR_GENERALIZED_TIME_NAME), EMPTY_STRING, false,
+ SYNTAX_GENERALIZED_TIME_OID, RFC4512_ORIGIN,
+ new GeneralizedTimeOrderingMatchingRuleImpl(), false);
+ builder.addMatchingRule(EMR_INTEGER_FIRST_COMPONENT_OID,
+ Collections.singletonList(EMR_INTEGER_FIRST_COMPONENT_NAME),
+ EMPTY_STRING, false, SYNTAX_INTEGER_OID, RFC4512_ORIGIN,
+ new IntegerFirstComponentEqualityMatchingRuleImpl(), false);
+ builder.addMatchingRule(EMR_INTEGER_OID, Collections
+ .singletonList(EMR_INTEGER_NAME), EMPTY_STRING, false,
+ SYNTAX_INTEGER_OID, RFC4512_ORIGIN,
+ new IntegerEqualityMatchingRuleImpl(), false);
+ builder.addMatchingRule(OMR_INTEGER_OID, Collections
+ .singletonList(OMR_INTEGER_NAME), EMPTY_STRING, false,
+ SYNTAX_INTEGER_OID, RFC4512_ORIGIN,
+ new IntegerOrderingMatchingRuleImpl(), false);
+ builder.addMatchingRule(EMR_KEYWORD_OID, Collections
+ .singletonList(EMR_KEYWORD_NAME), EMPTY_STRING, false,
+ SYNTAX_DIRECTORY_STRING_OID, RFC4512_ORIGIN,
+ new KeywordEqualityMatchingRuleImpl(), false);
+ builder.addMatchingRule(EMR_NUMERIC_STRING_OID, Collections
+ .singletonList(EMR_NUMERIC_STRING_NAME), EMPTY_STRING, false,
+ SYNTAX_NUMERIC_STRING_OID, RFC4512_ORIGIN,
+ new NumericStringEqualityMatchingRuleImpl(), false);
+ builder.addMatchingRule(OMR_NUMERIC_STRING_OID, Collections
+ .singletonList(OMR_NUMERIC_STRING_NAME), EMPTY_STRING, false,
+ SYNTAX_NUMERIC_STRING_OID, RFC4512_ORIGIN,
+ new NumericStringOrderingMatchingRuleImpl(), false);
+ builder.addMatchingRule(SMR_NUMERIC_STRING_OID, Collections
+ .singletonList(SMR_NUMERIC_STRING_NAME), EMPTY_STRING, false,
+ SYNTAX_SUBSTRING_ASSERTION_OID, RFC4512_ORIGIN,
+ new NumericStringSubstringMatchingRuleImpl(), false);
+ builder.addMatchingRule(EMR_OID_FIRST_COMPONENT_OID, Collections
+ .singletonList(EMR_OID_FIRST_COMPONENT_NAME), EMPTY_STRING,
+ false, SYNTAX_OID_OID, RFC4512_ORIGIN,
+ new ObjectIdentifierFirstComponentEqualityMatchingRuleImpl(),
+ false);
+ builder.addMatchingRule(EMR_OID_OID, Collections
+ .singletonList(EMR_OID_NAME), EMPTY_STRING, false,
+ SYNTAX_OID_OID, RFC4512_ORIGIN,
+ new ObjectIdentifierEqualityMatchingRuleImpl(), false);
+ builder.addMatchingRule(EMR_OCTET_STRING_OID, Collections
+ .singletonList(EMR_OCTET_STRING_NAME), EMPTY_STRING, false,
+ SYNTAX_OCTET_STRING_OID, RFC4512_ORIGIN,
+ new OctetStringEqualityMatchingRuleImpl(), false);
+ builder.addMatchingRule(OMR_OCTET_STRING_OID, Collections
+ .singletonList(OMR_OCTET_STRING_NAME), EMPTY_STRING, false,
+ SYNTAX_OCTET_STRING_OID, RFC4512_ORIGIN,
+ new OctetStringOrderingMatchingRuleImpl(), false);
+ // SMR octet string is not in any LDAP RFC and its from X.500
+ builder.addMatchingRule(SMR_OCTET_STRING_OID, Collections
+ .singletonList(SMR_OCTET_STRING_NAME), EMPTY_STRING, false,
+ SYNTAX_OCTET_STRING_OID, X500_ORIGIN,
+ new OctetStringSubstringMatchingRuleImpl(), false);
+ // Depreciated in RFC 4512
+ builder.addMatchingRule(EMR_PROTOCOL_INFORMATION_OID, Collections
+ .singletonList(EMR_PROTOCOL_INFORMATION_NAME), EMPTY_STRING,
+ false, SYNTAX_PROTOCOL_INFORMATION_OID, RFC2252_ORIGIN,
+ new ProtocolInformationEqualityMatchingRuleImpl(), false);
+ // Depreciated in RFC 4512
+ builder.addMatchingRule(EMR_PRESENTATION_ADDRESS_OID, Collections
+ .singletonList(EMR_PRESENTATION_ADDRESS_NAME), EMPTY_STRING,
+ false, SYNTAX_PRESENTATION_ADDRESS_OID, RFC2252_ORIGIN,
+ new PresentationAddressEqualityMatchingRuleImpl(), false);
+ builder.addMatchingRule(EMR_TELEPHONE_OID, Collections
+ .singletonList(EMR_TELEPHONE_NAME), EMPTY_STRING, false,
+ SYNTAX_TELEPHONE_OID, RFC4512_ORIGIN,
+ new TelephoneNumberEqualityMatchingRuleImpl(), false);
+ builder.addMatchingRule(SMR_TELEPHONE_OID, Collections
+ .singletonList(SMR_TELEPHONE_NAME), EMPTY_STRING, false,
+ SYNTAX_SUBSTRING_ASSERTION_OID, RFC4512_ORIGIN,
+ new TelephoneNumberSubstringMatchingRuleImpl(), false);
+ builder.addMatchingRule(EMR_UNIQUE_MEMBER_OID, Collections
+ .singletonList(EMR_UNIQUE_MEMBER_NAME), EMPTY_STRING, false,
+ SYNTAX_NAME_AND_OPTIONAL_UID_OID, RFC4512_ORIGIN,
+ new UniqueMemberEqualityMatchingRuleImpl(), false);
+ builder.addMatchingRule(EMR_WORD_OID, Collections
+ .singletonList(EMR_WORD_NAME), EMPTY_STRING, false,
+ SYNTAX_DIRECTORY_STRING_OID, RFC4512_ORIGIN,
+ new WordEqualityMatchingRuleImpl(), false);
+ }
+
+
+
+ private static void defaultObjectClasses(SchemaBuilder builder)
+ {
+ builder.addObjectClass(TOP_OBJECTCLASS_OID, Collections
+ .singletonList(TOP_OBJECTCLASS_NAME),
+ TOP_OBJECTCLASS_DESCRIPTION, false, EMPTY_STRING_SET,
+ Collections.singleton("objectClass"), EMPTY_STRING_SET,
+ ObjectClassType.ABSTRACT, RFC4512_ORIGIN, false);
+
+ builder.addObjectClass("2.5.6.1", Collections
+ .singletonList("alias"), EMPTY_STRING, false, Collections
+ .singleton("top"), Collections.singleton("aliasedObjectName"),
+ EMPTY_STRING_SET, ObjectClassType.STRUCTURAL, RFC4512_ORIGIN,
+ false);
+
+ builder.addObjectClass(EXTENSIBLE_OBJECT_OBJECTCLASS_OID,
+ Collections.singletonList(EXTENSIBLE_OBJECT_OBJECTCLASS_NAME),
+ EMPTY_STRING, false, Collections
+ .singleton(TOP_OBJECTCLASS_NAME), EMPTY_STRING_SET,
+ EMPTY_STRING_SET, ObjectClassType.AUXILIARY, RFC4512_ORIGIN,
+ false);
+
+ final Set<String> subschemaAttrs = new HashSet<String>();
+ subschemaAttrs.add("dITStructureRules");
+ subschemaAttrs.add("nameForms");
+ subschemaAttrs.add("ditContentRules");
+ subschemaAttrs.add("objectClasses");
+ subschemaAttrs.add("attributeTypes");
+ subschemaAttrs.add("matchingRules");
+ subschemaAttrs.add("matchingRuleUse");
+
+ builder.addObjectClass("2.5.20.1", Collections
+ .singletonList("subschema"), EMPTY_STRING, false, Collections
+ .singleton(TOP_OBJECTCLASS_NAME), EMPTY_STRING_SET,
+ subschemaAttrs, ObjectClassType.AUXILIARY, RFC4512_ORIGIN,
+ false);
+ }
+
+
+
+ private static void defaultSyntaxes(SchemaBuilder builder)
+ {
+ // All RFC 4512 / 4517
+ builder.addSyntax(SYNTAX_ATTRIBUTE_TYPE_OID,
+ SYNTAX_ATTRIBUTE_TYPE_DESCRIPTION, RFC4512_ORIGIN,
+ new AttributeTypeSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_BINARY_OID, SYNTAX_BINARY_DESCRIPTION,
+ RFC4512_ORIGIN, new BinarySyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_BIT_STRING_OID,
+ SYNTAX_BIT_STRING_DESCRIPTION, RFC4512_ORIGIN,
+ new BitStringSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_BOOLEAN_OID, SYNTAX_BOOLEAN_DESCRIPTION,
+ RFC4512_ORIGIN, new BooleanSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_CERTLIST_OID, SYNTAX_CERTLIST_DESCRIPTION,
+ RFC4512_ORIGIN, new CertificateListSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_CERTPAIR_OID, SYNTAX_CERTPAIR_DESCRIPTION,
+ RFC4512_ORIGIN, new CertificatePairSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_CERTIFICATE_OID,
+ SYNTAX_CERTIFICATE_DESCRIPTION, RFC4512_ORIGIN,
+ new CertificateSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_COUNTRY_STRING_OID,
+ SYNTAX_COUNTRY_STRING_DESCRIPTION, RFC4512_ORIGIN,
+ new CountryStringSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_DELIVERY_METHOD_OID,
+ SYNTAX_DELIVERY_METHOD_DESCRIPTION, RFC4512_ORIGIN,
+ new DeliveryMethodSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_DIRECTORY_STRING_OID,
+ SYNTAX_DIRECTORY_STRING_DESCRIPTION, RFC4512_ORIGIN,
+ new DirectoryStringSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_DIT_CONTENT_RULE_OID,
+ SYNTAX_DIT_CONTENT_RULE_DESCRIPTION, RFC4512_ORIGIN,
+ new DITContentRuleSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_DIT_STRUCTURE_RULE_OID,
+ SYNTAX_DIT_STRUCTURE_RULE_DESCRIPTION, RFC4512_ORIGIN,
+ new DITStructureRuleSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_DN_OID, SYNTAX_DN_DESCRIPTION,
+ RFC4512_ORIGIN, new DistinguishedNameSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_ENHANCED_GUIDE_OID,
+ SYNTAX_ENHANCED_GUIDE_DESCRIPTION, RFC4512_ORIGIN,
+ new EnhancedGuideSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_FAXNUMBER_OID,
+ SYNTAX_FAXNUMBER_DESCRIPTION, RFC4512_ORIGIN,
+ new FacsimileNumberSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_FAX_OID, SYNTAX_FAX_DESCRIPTION,
+ RFC4512_ORIGIN, new FaxSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_GENERALIZED_TIME_OID,
+ SYNTAX_GENERALIZED_TIME_DESCRIPTION, RFC4512_ORIGIN,
+ new GeneralizedTimeSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_GUIDE_OID, SYNTAX_GUIDE_DESCRIPTION,
+ RFC4512_ORIGIN, new GuideSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_IA5_STRING_OID,
+ SYNTAX_IA5_STRING_DESCRIPTION, RFC4512_ORIGIN,
+ new IA5StringSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_INTEGER_OID, SYNTAX_INTEGER_DESCRIPTION,
+ RFC4512_ORIGIN, new IntegerSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_JPEG_OID, SYNTAX_JPEG_DESCRIPTION,
+ RFC4512_ORIGIN, new JPEGSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_MATCHING_RULE_OID,
+ SYNTAX_MATCHING_RULE_DESCRIPTION, RFC4512_ORIGIN,
+ new MatchingRuleSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_MATCHING_RULE_USE_OID,
+ SYNTAX_MATCHING_RULE_USE_DESCRIPTION, RFC4512_ORIGIN,
+ new MatchingRuleUseSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_LDAP_SYNTAX_OID,
+ SYNTAX_LDAP_SYNTAX_DESCRIPTION, RFC4512_ORIGIN,
+ new LDAPSyntaxDescriptionSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_NAME_AND_OPTIONAL_UID_OID,
+ SYNTAX_NAME_AND_OPTIONAL_UID_DESCRIPTION, RFC4517_ORIGIN,
+ new NameAndOptionalUIDSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_NAME_FORM_OID,
+ SYNTAX_NAME_FORM_DESCRIPTION, RFC4512_ORIGIN,
+ new NameFormSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_NUMERIC_STRING_OID,
+ SYNTAX_NUMERIC_STRING_DESCRIPTION, RFC4512_ORIGIN,
+ new NumericStringSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_OBJECTCLASS_OID,
+ SYNTAX_OBJECTCLASS_DESCRIPTION, RFC4512_ORIGIN,
+ new ObjectClassSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_OCTET_STRING_OID,
+ SYNTAX_OCTET_STRING_DESCRIPTION, RFC4512_ORIGIN,
+ new OctetStringSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_OID_OID, SYNTAX_OID_DESCRIPTION,
+ RFC4512_ORIGIN, new OIDSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_OTHER_MAILBOX_OID,
+ SYNTAX_OTHER_MAILBOX_DESCRIPTION, RFC4512_ORIGIN,
+ new OtherMailboxSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_POSTAL_ADDRESS_OID,
+ SYNTAX_POSTAL_ADDRESS_DESCRIPTION, RFC4512_ORIGIN,
+ new PostalAddressSyntaxImpl(), false);
+ // Depreciated in RFC 4512
+ builder.addSyntax(SYNTAX_PRESENTATION_ADDRESS_OID,
+ SYNTAX_PRESENTATION_ADDRESS_DESCRIPTION, RFC2252_ORIGIN,
+ new PresentationAddressSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_PRINTABLE_STRING_OID,
+ SYNTAX_PRINTABLE_STRING_DESCRIPTION, RFC4512_ORIGIN,
+ new PrintableStringSyntaxImpl(), false);
+ // Depreciated in RFC 4512
+ builder.addSyntax(SYNTAX_PROTOCOL_INFORMATION_OID,
+ SYNTAX_PROTOCOL_INFORMATION_DESCRIPTION, RFC2252_ORIGIN,
+ new ProtocolInformationSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_SUBSTRING_ASSERTION_OID,
+ SYNTAX_SUBSTRING_ASSERTION_DESCRIPTION, RFC4512_ORIGIN,
+ new SubstringAssertionSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_SUPPORTED_ALGORITHM_OID,
+ SYNTAX_SUPPORTED_ALGORITHM_DESCRIPTION, RFC4512_ORIGIN,
+ new SupportedAlgorithmSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_TELEPHONE_OID,
+ SYNTAX_TELEPHONE_DESCRIPTION, RFC4512_ORIGIN,
+ new TelephoneNumberSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_TELETEX_TERM_ID_OID,
+ SYNTAX_TELETEX_TERM_ID_DESCRIPTION, RFC4512_ORIGIN,
+ new TeletexTerminalIdentifierSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_TELEX_OID, SYNTAX_TELEX_DESCRIPTION,
+ RFC4512_ORIGIN, new TelexNumberSyntaxImpl(), false);
+ builder.addSyntax(SYNTAX_UTC_TIME_OID, SYNTAX_UTC_TIME_DESCRIPTION,
+ RFC4512_ORIGIN, new UTCTimeSyntaxImpl(), false);
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CountryStringSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/CountryStringSyntaxImpl.java
new file mode 100644
index 0000000..b3cbe22
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CountryStringSyntaxImpl.java
@@ -0,0 +1,136 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_COUNTRY_STRING_INVALID_LENGTH;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_COUNTRY_STRING_NOT_PRINTABLE;
+import static org.opends.sdk.schema.SchemaConstants.*;
+import static org.opends.sdk.util.StaticUtils.toLowerCase;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class defines the country string attribute syntax, which should
+ * be a two-character ISO 3166 country code. However, for
+ * maintainability, it will accept any value consisting entirely of two
+ * printable characters. In most ways, it will behave like the directory
+ * string attribute syntax.
+ */
+final class CountryStringSyntaxImpl extends AbstractSyntaxImpl
+{
+
+ @Override
+ public String getApproximateMatchingRule()
+ {
+ return AMR_DOUBLE_METAPHONE_OID;
+ }
+
+
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_COUNTRY_STRING_NAME;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_CASE_IGNORE_OID;
+ }
+
+
+
+ @Override
+ public String getSubstringMatchingRule()
+ {
+ return SMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ final String stringValue = toLowerCase(value.toString());
+ if (stringValue.length() != 2)
+ {
+ invalidReason
+ .append(ERR_ATTR_SYNTAX_COUNTRY_STRING_INVALID_LENGTH
+ .get(stringValue));
+ return false;
+ }
+
+ if (!PrintableStringSyntaxImpl.isPrintableCharacter(stringValue
+ .charAt(0))
+ || !PrintableStringSyntaxImpl.isPrintableCharacter(stringValue
+ .charAt(1)))
+ {
+ invalidReason.append(ERR_ATTR_SYNTAX_COUNTRY_STRING_NOT_PRINTABLE
+ .get(stringValue));
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/DITContentRule.java b/sdk/src/org/opends/sdk/schema/DITContentRule.java
new file mode 100644
index 0000000..db5309b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/DITContentRule.java
@@ -0,0 +1,623 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class defines a DIT content rule, which defines the set of
+ * allowed, required, and prohibited attributes for entries with a given
+ * structural objectclass, and also indicates which auxiliary classes
+ * that may be included in the entry.
+ */
+public final class DITContentRule extends SchemaElement
+{
+ // The structural objectclass for this DIT content rule.
+ private final String structuralClassOID;
+
+ // The set of user defined names for this definition.
+ private final List<String> names;
+
+ // Indicates whether this definition is declared "obsolete".
+ private final boolean isObsolete;
+
+ // The set of auxiliary objectclasses that entries with this content
+ // rule may contain, in a mapping between the objectclass and the
+ // user-defined name for that class.
+ private final Set<String> auxiliaryClassOIDs;
+
+ // The set of optional attribute types for this DIT content rule.
+ private final Set<String> optionalAttributeOIDs;
+
+ // The set of prohibited attribute types for this DIT content rule.
+ private final Set<String> prohibitedAttributeOIDs;
+
+ // The set of required attribute types for this DIT content rule.
+ private final Set<String> requiredAttributeOIDs;
+
+ // The definition string used to create this objectclass.
+ private final String definition;
+
+ private ObjectClass structuralClass;
+ private Set<ObjectClass> auxiliaryClasses = Collections.emptySet();
+ private Set<AttributeType> optionalAttributes =
+ Collections.emptySet();
+ private Set<AttributeType> prohibitedAttributes =
+ Collections.emptySet();
+ private Set<AttributeType> requiredAttributes =
+ Collections.emptySet();
+
+
+
+ DITContentRule(String structuralClassOID, List<String> names,
+ String description, boolean obsolete,
+ Set<String> auxiliaryClassOIDs,
+ Set<String> optionalAttributeOIDs,
+ Set<String> prohibitedAttributeOIDs,
+ Set<String> requiredAttributeOIDs,
+ Map<String, List<String>> extraProperties, String definition)
+ {
+ super(description, extraProperties);
+
+ Validator.ensureNotNull(structuralClassOID, names);
+ Validator.ensureNotNull(auxiliaryClassOIDs, optionalAttributeOIDs,
+ prohibitedAttributeOIDs, requiredAttributeOIDs);
+ this.names = names;
+ this.isObsolete = obsolete;
+ this.structuralClassOID = structuralClassOID;
+ this.auxiliaryClassOIDs = auxiliaryClassOIDs;
+ this.optionalAttributeOIDs = optionalAttributeOIDs;
+ this.prohibitedAttributeOIDs = prohibitedAttributeOIDs;
+ this.requiredAttributeOIDs = requiredAttributeOIDs;
+
+ if (definition != null)
+ {
+ this.definition = definition;
+ }
+ else
+ {
+ this.definition = buildDefinition();
+ }
+ }
+
+
+
+ /**
+ * Retrieves the set of auxiliary objectclasses that may be used for
+ * entries associated with this DIT content rule.
+ *
+ * @return The set of auxiliary objectclasses that may be used for
+ * entries associated with this DIT content rule.
+ */
+ public Iterable<ObjectClass> getAuxiliaryClasses()
+ {
+ return auxiliaryClasses;
+ }
+
+
+
+ /**
+ * Retrieves the name or structural class OID for this schema
+ * definition. If it has one or more names, then the primary name will
+ * be returned. If it does not have any names, then the OID will be
+ * returned.
+ *
+ * @return The name or OID for this schema definition.
+ */
+ public String getNameOrOID()
+ {
+ if (names.isEmpty())
+ {
+ return structuralClassOID;
+ }
+ return names.get(0);
+ }
+
+
+
+ /**
+ * Retrieves an iterable over the set of user-defined names that may
+ * be used to reference this schema definition.
+ *
+ * @return Returns an iterable over the set of user-defined names that
+ * may be used to reference this schema definition.
+ */
+ public Iterable<String> getNames()
+ {
+ return names;
+ }
+
+
+
+ /**
+ * Retrieves the set of optional attributes for this DIT content rule.
+ *
+ * @return The set of optional attributes for this DIT content rule.
+ */
+ public Iterable<AttributeType> getOptionalAttributes()
+ {
+ return optionalAttributes;
+ }
+
+
+
+ /**
+ * Retrieves the set of prohibited attributes for this DIT content
+ * rule.
+ *
+ * @return The set of prohibited attributes for this DIT content rule.
+ */
+ public Iterable<AttributeType> getProhibitedAttributes()
+ {
+ return prohibitedAttributes;
+ }
+
+
+
+ /**
+ * Retrieves the set of required attributes for this DIT content rule.
+ *
+ * @return The set of required attributes for this DIT content rule.
+ */
+ public Iterable<AttributeType> getRequiredAttributes()
+ {
+ return requiredAttributes;
+ }
+
+
+
+ /**
+ * Retrieves the structural objectclass for this DIT content rule.
+ *
+ * @return The structural objectclass for this DIT content rule.
+ */
+ public ObjectClass getStructuralClass()
+ {
+ return structuralClass;
+ }
+
+
+
+ /**
+ * Retrieves the structural class OID for this schema definition.
+ *
+ * @return The structural class OID for this schema definition.
+ */
+ public String getStructuralClassOID()
+ {
+ return structuralClassOID;
+ }
+
+
+
+ @Override
+ public int hashCode()
+ {
+ return structuralClassOID.hashCode();
+ }
+
+
+
+ /**
+ * Indicates whether this schema definition has the specified name.
+ *
+ * @param name
+ * The name for which to make the determination.
+ * @return <code>true</code> if the specified name is assigned to this
+ * schema definition, or <code>false</code> if not.
+ */
+ public boolean hasName(String name)
+ {
+ for (final String n : names)
+ {
+ if (n.equalsIgnoreCase(name))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether this schema definition has the specified name or
+ * structural class OID.
+ *
+ * @param value
+ * The value for which to make the determination.
+ * @return <code>true</code> if the provided value matches the OID or
+ * one of the names assigned to this schema definition, or
+ * <code>false</code> if not.
+ */
+ public boolean hasNameOrOID(String value)
+ {
+ return hasName(value) || structuralClassOID.equals(value);
+ }
+
+
+
+ /**
+ * Indicates whether this schema definition is declared "obsolete".
+ *
+ * @return <code>true</code> if this schema definition is declared
+ * "obsolete", or <code>false</code> if not.
+ */
+ public boolean isObsolete()
+ {
+ return isObsolete;
+ }
+
+
+
+ /**
+ * Retrieves the string representation of this schema definition in
+ * the form specified in RFC 2252.
+ *
+ * @return The string representation of this schema definition in the
+ * form specified in RFC 2252.
+ */
+ @Override
+ public String toString()
+ {
+ return definition;
+ }
+
+
+
+ DITContentRule duplicate()
+ {
+ return new DITContentRule(structuralClassOID, names, description,
+ isObsolete, auxiliaryClassOIDs, optionalAttributeOIDs,
+ prohibitedAttributeOIDs, requiredAttributeOIDs,
+ extraProperties, definition);
+ }
+
+
+
+ @Override
+ void toStringContent(StringBuilder buffer)
+ {
+ buffer.append(structuralClassOID);
+
+ if (!names.isEmpty())
+ {
+ final Iterator<String> iterator = names.iterator();
+
+ final String firstName = iterator.next();
+ if (iterator.hasNext())
+ {
+ buffer.append(" NAME ( '");
+ buffer.append(firstName);
+
+ while (iterator.hasNext())
+ {
+ buffer.append("' '");
+ buffer.append(iterator.next());
+ }
+
+ buffer.append("' )");
+ }
+ else
+ {
+ buffer.append(" NAME '");
+ buffer.append(firstName);
+ buffer.append("'");
+ }
+ }
+
+ if (description != null && description.length() > 0)
+ {
+ buffer.append(" DESC '");
+ buffer.append(description);
+ buffer.append("'");
+ }
+
+ if (isObsolete)
+ {
+ buffer.append(" OBSOLETE");
+ }
+
+ if (!auxiliaryClassOIDs.isEmpty())
+ {
+ final Iterator<String> iterator = auxiliaryClassOIDs.iterator();
+
+ final String firstClass = iterator.next();
+ if (iterator.hasNext())
+ {
+ buffer.append(" AUX (");
+ buffer.append(firstClass);
+
+ while (iterator.hasNext())
+ {
+ buffer.append(" $ ");
+ buffer.append(iterator.next());
+ }
+
+ buffer.append(" )");
+ }
+ else
+ {
+ buffer.append(" AUX ");
+ buffer.append(firstClass);
+ }
+ }
+
+ if (!requiredAttributeOIDs.isEmpty())
+ {
+ final Iterator<String> iterator =
+ requiredAttributeOIDs.iterator();
+
+ final String firstName = iterator.next();
+ if (iterator.hasNext())
+ {
+ buffer.append(" MUST ( ");
+ buffer.append(firstName);
+
+ while (iterator.hasNext())
+ {
+ buffer.append(" $ ");
+ buffer.append(iterator.next());
+ }
+
+ buffer.append(" )");
+ }
+ else
+ {
+ buffer.append(" MUST ");
+ buffer.append(firstName);
+ }
+ }
+
+ if (!optionalAttributeOIDs.isEmpty())
+ {
+ final Iterator<String> iterator =
+ optionalAttributeOIDs.iterator();
+
+ final String firstName = iterator.next();
+ if (iterator.hasNext())
+ {
+ buffer.append(" MAY ( ");
+ buffer.append(firstName);
+
+ while (iterator.hasNext())
+ {
+ buffer.append(" $ ");
+ buffer.append(iterator.next());
+ }
+
+ buffer.append(" )");
+ }
+ else
+ {
+ buffer.append(" MAY ");
+ buffer.append(firstName);
+ }
+ }
+
+ if (!prohibitedAttributeOIDs.isEmpty())
+ {
+ final Iterator<String> iterator =
+ prohibitedAttributeOIDs.iterator();
+
+ final String firstName = iterator.next();
+ if (iterator.hasNext())
+ {
+ buffer.append(" NOT ( ");
+ buffer.append(firstName);
+
+ while (iterator.hasNext())
+ {
+ buffer.append(" $ ");
+ buffer.append(iterator.next());
+ }
+
+ buffer.append(" )");
+ }
+ else
+ {
+ buffer.append(" NOT ");
+ buffer.append(firstName);
+ }
+ }
+ }
+
+
+
+ @Override
+ void validate(List<Message> warnings, Schema schema)
+ throws SchemaException
+ {
+ // Get the objectclass with the specified OID. If it does not exist
+ // or is not structural, then fail.
+ if (structuralClassOID != null)
+ {
+ try
+ {
+ structuralClass = schema.getObjectClass(structuralClassOID);
+ }
+ catch (final UnknownSchemaElementException e)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_DCR_UNKNOWN_STRUCTURAL_CLASS.get(
+ definition, structuralClassOID);
+ throw new SchemaException(message, e);
+ }
+ if (structuralClass.getObjectClassType() != ObjectClassType.STRUCTURAL)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_DCR_STRUCTURAL_CLASS_NOT_STRUCTURAL.get(
+ definition, structuralClass.getOID(), structuralClass
+ .getNameOrOID(), structuralClass
+ .getObjectClassType().toString());
+ warnings.add(message);
+ }
+ }
+
+ if (!auxiliaryClassOIDs.isEmpty())
+ {
+ auxiliaryClasses =
+ new HashSet<ObjectClass>(auxiliaryClassOIDs.size());
+ ObjectClass objectClass;
+ for (final String oid : auxiliaryClassOIDs)
+ {
+ try
+ {
+ objectClass = schema.getObjectClass(oid);
+ }
+ catch (final UnknownSchemaElementException e)
+ {
+ // This isn't good because it is an unknown auxiliary class.
+ final Message message =
+ ERR_ATTR_SYNTAX_DCR_UNKNOWN_AUXILIARY_CLASS.get(
+ definition, oid);
+ throw new SchemaException(message, e);
+ }
+ if (objectClass.getObjectClassType() != ObjectClassType.AUXILIARY)
+ {
+ // This isn't good because it isn't an auxiliary class.
+ final Message message =
+ ERR_ATTR_SYNTAX_DCR_AUXILIARY_CLASS_NOT_AUXILIARY.get(
+ definition, structuralClass.getOID(), structuralClass
+ .getObjectClassType().toString());
+ throw new SchemaException(message);
+ }
+ auxiliaryClasses.add(objectClass);
+ }
+ }
+
+ if (!requiredAttributeOIDs.isEmpty())
+ {
+ requiredAttributes =
+ new HashSet<AttributeType>(requiredAttributeOIDs.size());
+ AttributeType attributeType;
+ for (final String oid : requiredAttributeOIDs)
+ {
+ try
+ {
+ attributeType = schema.getAttributeType(oid);
+ }
+ catch (final UnknownSchemaElementException e)
+ {
+ // This isn't good because it means that the DIT content rule
+ // requires an attribute type that we don't know anything
+ // about.
+ final Message message =
+ ERR_ATTR_SYNTAX_DCR_UNKNOWN_REQUIRED_ATTR.get(definition,
+ oid);
+ throw new SchemaException(message, e);
+ }
+ requiredAttributes.add(attributeType);
+ }
+ }
+
+ if (!optionalAttributeOIDs.isEmpty())
+ {
+ optionalAttributes =
+ new HashSet<AttributeType>(optionalAttributeOIDs.size());
+ AttributeType attributeType;
+ for (final String oid : optionalAttributeOIDs)
+ {
+ try
+ {
+ attributeType = schema.getAttributeType(oid);
+ }
+ catch (final UnknownSchemaElementException e)
+ {
+ // This isn't good because it means that the DIT content rule
+ // requires an attribute type that we don't know anything
+ // about.
+ final Message message =
+ ERR_ATTR_SYNTAX_DCR_UNKNOWN_OPTIONAL_ATTR.get(definition,
+ oid);
+ throw new SchemaException(message, e);
+ }
+ optionalAttributes.add(attributeType);
+ }
+ }
+
+ if (!prohibitedAttributeOIDs.isEmpty())
+ {
+ prohibitedAttributes =
+ new HashSet<AttributeType>(prohibitedAttributeOIDs.size());
+ AttributeType attributeType;
+ for (final String oid : prohibitedAttributeOIDs)
+ {
+ try
+ {
+ attributeType = schema.getAttributeType(oid);
+ }
+ catch (final UnknownSchemaElementException e)
+ {
+ // This isn't good because it means that the DIT content rule
+ // requires an attribute type that we don't know anything
+ // about.
+ final Message message =
+ ERR_ATTR_SYNTAX_DCR_UNKNOWN_PROHIBITED_ATTR.get(
+ definition, oid);
+ throw new SchemaException(message, e);
+ }
+ prohibitedAttributes.add(attributeType);
+ }
+ }
+
+ // Make sure that none of the prohibited attributes is required by
+ // the structural or any of the auxiliary classes.
+ for (final AttributeType t : prohibitedAttributes)
+ {
+ if (structuralClass.isRequired(t))
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_DCR_PROHIBITED_REQUIRED_BY_STRUCTURAL.get(
+ definition, t.getNameOrOID(), structuralClass
+ .getNameOrOID());
+ throw new SchemaException(message);
+ }
+
+ for (final ObjectClass oc : auxiliaryClasses)
+ {
+ if (oc.isRequired(t))
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_DCR_PROHIBITED_REQUIRED_BY_AUXILIARY.get(
+ definition, t.getNameOrOID(), oc.getNameOrOID());
+ throw new SchemaException(message);
+ }
+ }
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/DITContentRuleSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/DITContentRuleSyntaxImpl.java
new file mode 100644
index 0000000..db62d3e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/DITContentRuleSyntaxImpl.java
@@ -0,0 +1,203 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_DCR_EMPTY_VALUE;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_DCR_EXPECTED_OPEN_PARENTHESIS;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_ILLEGAL_TOKEN;
+import static org.opends.sdk.schema.SchemaConstants.EMR_OID_FIRST_COMPONENT_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_DIT_CONTENT_RULE_NAME;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class implements the DIT content rule description syntax, which
+ * is used to hold DIT content rule definitions in the server schema.
+ * The format of this syntax is defined in RFC 2252.
+ */
+final class DITContentRuleSyntaxImpl extends AbstractSyntaxImpl
+{
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_OID_FIRST_COMPONENT_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_DIT_CONTENT_RULE_NAME;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // We'll use the decodeDITContentRule method to determine if the
+ // value is acceptable.
+ try
+ {
+ final String definition = value.toString();
+ final SubstringReader reader = new SubstringReader(definition);
+
+ // We'll do this a character at a time. First, skip over any
+ // leading whitespace.
+ reader.skipWhitespaces();
+
+ if (reader.remaining() <= 0)
+ {
+ // This means that the value was empty or contained only
+ // whitespace. That is illegal.
+ final Message message = ERR_ATTR_SYNTAX_DCR_EMPTY_VALUE.get();
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("DITConentRuleSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ // The next character must be an open parenthesis. If it is not,
+ // then that is an error.
+ final char c = reader.read();
+ if (c != '(')
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_DCR_EXPECTED_OPEN_PARENTHESIS.get(
+ definition, (reader.pos() - 1), String.valueOf(c));
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("DITContentRuleSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ // Skip over any spaces immediately following the opening
+ // parenthesis.
+ reader.skipWhitespaces();
+
+ // The next set of characters must be the OID.
+ SchemaUtils.readOID(reader);
+
+ // At this point, we should have a pretty specific syntax that
+ // describes what may come next, but some of the components are
+ // optional and it would be pretty easy to put something in the
+ // wrong order, so we will be very flexible about what we can
+ // accept. Just look at the next token, figure out what it is and
+ // how to treat what comes after it, then repeat until we get to
+ // the end of the value. But before we start, set default values
+ // for everything else we might need to know.
+ while (true)
+ {
+ final String tokenName = SchemaUtils.readTokenName(reader);
+
+ if (tokenName == null)
+ {
+ // No more tokens.
+ break;
+ }
+ else if (tokenName.equalsIgnoreCase("name"))
+ {
+ SchemaUtils.readNameDescriptors(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("desc"))
+ {
+ // This specifies the description for the attribute type. It
+ // is an arbitrary string of characters enclosed in single
+ // quotes.
+ SchemaUtils.readQuotedString(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("obsolete"))
+ {
+ // This indicates whether the attribute type should be
+ // considered obsolete. We do not need to do any more parsing
+ // for this token.
+ }
+ else if (tokenName.equalsIgnoreCase("aux"))
+ {
+ SchemaUtils.readOIDs(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("must"))
+ {
+ SchemaUtils.readOIDs(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("may"))
+ {
+ SchemaUtils.readOIDs(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("not"))
+ {
+ SchemaUtils.readOIDs(reader);
+ }
+ else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+ {
+ // This must be a non-standard property and it must be
+ // followed by either a single definition in single quotes or
+ // an open parenthesis followed by one or more values in
+ // single quotes separated by spaces followed by a close
+ // parenthesis.
+ SchemaUtils.readExtensions(reader);
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("DITContentRuleSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+ }
+ return true;
+ }
+ catch (final DecodeException de)
+ {
+ invalidReason.append(de.getMessageObject());
+ return false;
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/DITStructureRule.java b/sdk/src/org/opends/sdk/schema/DITStructureRule.java
new file mode 100644
index 0000000..851c3f3
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/DITStructureRule.java
@@ -0,0 +1,342 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_DSR_UNKNOWN_NAME_FORM;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_DSR_UNKNOWN_RULE_ID;
+
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class defines a DIT structure rule, which is used to indicate
+ * the types of children that entries may have.
+ */
+public final class DITStructureRule extends SchemaElement
+{
+ // The rule ID for this DIT structure rule.
+ private final Integer ruleID;
+
+ // The set of user defined names for this definition.
+ private final List<String> names;
+
+ // Indicates whether this definition is declared "obsolete".
+ private final boolean isObsolete;
+
+ // The name form for this DIT structure rule.
+ private final String nameFormOID;
+
+ // The set of superior DIT structure rules.
+ private final Set<Integer> superiorRuleIDs;
+
+ // The definition string used to create this objectclass.
+ private final String definition;
+
+ private NameForm nameForm;
+ private Set<DITStructureRule> superiorRules = Collections.emptySet();
+
+
+
+ DITStructureRule(Integer ruleID, List<String> names,
+ String description, boolean obsolete, String nameFormOID,
+ Set<Integer> superiorRuleIDs,
+ Map<String, List<String>> extraProperties, String definition)
+ {
+ super(description, extraProperties);
+
+ Validator.ensureNotNull(ruleID, nameFormOID, superiorRuleIDs);
+ this.ruleID = ruleID;
+ this.names = names;
+ this.isObsolete = obsolete;
+ this.nameFormOID = nameFormOID;
+ this.superiorRuleIDs = superiorRuleIDs;
+
+ if (definition != null)
+ {
+ this.definition = definition;
+ }
+ else
+ {
+ this.definition = buildDefinition();
+ }
+ }
+
+
+
+ /**
+ * Retrieves the name form for this DIT structure rule.
+ *
+ * @return The name form for this DIT structure rule.
+ */
+ public NameForm getNameForm()
+ {
+ return nameForm;
+ }
+
+
+
+ /**
+ * Retrieves the name or rule ID for this schema definition. If it has
+ * one or more names, then the primary name will be returned. If it
+ * does not have any names, then the OID will be returned.
+ *
+ * @return The name or OID for this schema definition.
+ */
+ public String getNameOrRuleID()
+ {
+ if (names.isEmpty())
+ {
+ return ruleID.toString();
+ }
+ return names.get(0);
+ }
+
+
+
+ /**
+ * Retrieves an iterable over the set of user-defined names that may
+ * be used to reference this schema definition.
+ *
+ * @return Returns an iterable over the set of user-defined names that
+ * may be used to reference this schema definition.
+ */
+ public Iterable<String> getNames()
+ {
+ return names;
+ }
+
+
+
+ /**
+ * Retrieves the rule ID for this DIT structure rule.
+ *
+ * @return The rule ID for this DIT structure rule.
+ */
+ public Integer getRuleID()
+ {
+ return ruleID;
+ }
+
+
+
+ /**
+ * Retrieves the set of superior rules for this DIT structure rule.
+ *
+ * @return The set of superior rules for this DIT structure rule.
+ */
+ public Iterable<DITStructureRule> getSuperiorRules()
+ {
+ return superiorRules;
+ }
+
+
+
+ @Override
+ public int hashCode()
+ {
+ return ruleID.hashCode();
+ }
+
+
+
+ /**
+ * Indicates whether this schema definition has the specified name.
+ *
+ * @param name
+ * The name for which to make the determination.
+ * @return <code>true</code> if the specified name is assigned to this
+ * schema definition, or <code>false</code> if not.
+ */
+ public boolean hasName(String name)
+ {
+ for (final String n : names)
+ {
+ if (n.equalsIgnoreCase(name))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether this schema definition is declared "obsolete".
+ *
+ * @return <code>true</code> if this schema definition is declared
+ * "obsolete", or <code>false</code> if not.
+ */
+ public boolean isObsolete()
+ {
+ return isObsolete;
+ }
+
+
+
+ /**
+ * Retrieves the string representation of this schema definition in
+ * the form specified in RFC 2252.
+ *
+ * @return The string representation of this schema definition in the
+ * form specified in RFC 2252.
+ */
+ @Override
+ public String toString()
+ {
+ return definition;
+ }
+
+
+
+ DITStructureRule duplicate()
+ {
+ return new DITStructureRule(ruleID, names, description, isObsolete,
+ nameFormOID, superiorRuleIDs, extraProperties, definition);
+ }
+
+
+
+ @Override
+ void toStringContent(StringBuilder buffer)
+ {
+ buffer.append(ruleID);
+
+ if (!names.isEmpty())
+ {
+ final Iterator<String> iterator = names.iterator();
+
+ final String firstName = iterator.next();
+ if (iterator.hasNext())
+ {
+ buffer.append(" NAME ( '");
+ buffer.append(firstName);
+
+ while (iterator.hasNext())
+ {
+ buffer.append("' '");
+ buffer.append(iterator.next());
+ }
+
+ buffer.append("' )");
+ }
+ else
+ {
+ buffer.append(" NAME '");
+ buffer.append(firstName);
+ buffer.append("'");
+ }
+ }
+
+ if (description != null && description.length() > 0)
+ {
+ buffer.append(" DESC '");
+ buffer.append(description);
+ buffer.append("'");
+ }
+
+ if (isObsolete)
+ {
+ buffer.append(" OBSOLETE");
+ }
+
+ buffer.append(" FORM ");
+ buffer.append(nameFormOID);
+
+ if (superiorRuleIDs != null && !superiorRuleIDs.isEmpty())
+ {
+ final Iterator<Integer> iterator = superiorRuleIDs.iterator();
+
+ final Integer firstRule = iterator.next();
+ if (iterator.hasNext())
+ {
+ buffer.append(" SUP ( ");
+ buffer.append(firstRule);
+
+ while (iterator.hasNext())
+ {
+ buffer.append(" ");
+ buffer.append(iterator.next());
+ }
+
+ buffer.append(" )");
+ }
+ else
+ {
+ buffer.append(" SUP ");
+ buffer.append(firstRule);
+ }
+ }
+ }
+
+
+
+ @Override
+ void validate(List<Message> warnings, Schema schema)
+ throws SchemaException
+ {
+ try
+ {
+ nameForm = schema.getNameForm(nameFormOID);
+ }
+ catch (final UnknownSchemaElementException e)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_DSR_UNKNOWN_NAME_FORM.get(definition,
+ nameFormOID);
+ throw new SchemaException(message, e);
+ }
+
+ if (!superiorRuleIDs.isEmpty())
+ {
+ superiorRules =
+ new HashSet<DITStructureRule>(superiorRuleIDs.size());
+ DITStructureRule rule;
+ for (final Integer id : superiorRuleIDs)
+ {
+ try
+ {
+ rule = schema.getDITStructureRule(id);
+ }
+ catch (final UnknownSchemaElementException e)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_DSR_UNKNOWN_RULE_ID.get(definition, id);
+ throw new SchemaException(message, e);
+ }
+ superiorRules.add(rule);
+ }
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/DITStructureRuleSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/DITStructureRuleSyntaxImpl.java
new file mode 100644
index 0000000..699fb59
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/DITStructureRuleSyntaxImpl.java
@@ -0,0 +1,205 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_DSR_EMPTY_VALUE;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_DSR_EXPECTED_OPEN_PARENTHESIS;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_DSR_NO_NAME_FORM;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_ILLEGAL_TOKEN;
+import static org.opends.sdk.schema.SchemaConstants.EMR_INTEGER_FIRST_COMPONENT_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_DIT_STRUCTURE_RULE_NAME;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class implements the DIT structure rule description syntax,
+ * which is used to hold DIT structure rule definitions in the server
+ * schema. The format of this syntax is defined in RFC 2252.
+ */
+final class DITStructureRuleSyntaxImpl extends AbstractSyntaxImpl
+{
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_INTEGER_FIRST_COMPONENT_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_DIT_STRUCTURE_RULE_NAME;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // We'll use the decodeDITStructureRule method to determine if the
+ // value is acceptable.
+ try
+ {
+ final String definition = value.toString();
+ final SubstringReader reader = new SubstringReader(definition);
+
+ // We'll do this a character at a time. First, skip over any
+ // leading whitespace.
+ reader.skipWhitespaces();
+
+ if (reader.remaining() <= 0)
+ {
+ // This means that the value was empty or contained only
+ // whitespace. That is illegal.
+ final Message message = ERR_ATTR_SYNTAX_DSR_EMPTY_VALUE.get();
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("DITStructureRuleSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ // The next character must be an open parenthesis. If it is not,
+ // then that is an error.
+ final char c = reader.read();
+ if (c != '(')
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_DSR_EXPECTED_OPEN_PARENTHESIS.get(
+ definition, (reader.pos() - 1), String.valueOf(c));
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("DITStructureRuleSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ // Skip over any spaces immediately following the opening
+ // parenthesis.
+ reader.skipWhitespaces();
+
+ // The next set of characters must be the OID.
+ SchemaUtils.readRuleID(reader);
+
+ String nameForm = null;
+
+ // At this point, we should have a pretty specific syntax that
+ // describes what may come next, but some of the components are
+ // optional and it would be pretty easy to put something in the
+ // wrong order, so we will be very flexible about what we can
+ // accept. Just look at the next token, figure out what it is and
+ // how to treat what comes after it, then repeat until we get to
+ // the end of the value. But before we start, set default values
+ // for everything else we might need to know.
+ while (true)
+ {
+ final String tokenName = SchemaUtils.readTokenName(reader);
+
+ if (tokenName == null)
+ {
+ // No more tokens.
+ break;
+ }
+ else if (tokenName.equalsIgnoreCase("name"))
+ {
+ SchemaUtils.readNameDescriptors(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("desc"))
+ {
+ // This specifies the description for the attribute type. It
+ // is an arbitrary string of characters enclosed in single
+ // quotes.
+ SchemaUtils.readQuotedString(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("obsolete"))
+ {
+ // This indicates whether the attribute type should be
+ // considered obsolete. We do not need to do any more parsing
+ // for this token.
+ }
+ else if (tokenName.equalsIgnoreCase("form"))
+ {
+ nameForm = SchemaUtils.readOID(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("sup"))
+ {
+ SchemaUtils.readRuleIDs(reader);
+ }
+ else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+ {
+ // This must be a non-standard property and it must be
+ // followed by either a single definition in single quotes or
+ // an open parenthesis followed by one or more values in
+ // single quotes separated by spaces followed by a close
+ // parenthesis.
+ SchemaUtils.readExtensions(reader);
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("DITStructureRuleSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+ }
+
+ if (nameForm == null)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_DSR_NO_NAME_FORM.get(definition);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("DITStructureRuleSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+ return true;
+ }
+ catch (final DecodeException de)
+ {
+ invalidReason.append(de.getMessageObject());
+ return false;
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/DeliveryMethodSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/DeliveryMethodSyntaxImpl.java
new file mode 100644
index 0000000..1f65218
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/DeliveryMethodSyntaxImpl.java
@@ -0,0 +1,172 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_DELIVERY_METHOD_INVALID_ELEMENT;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_DELIVERY_METHOD_NO_ELEMENTS;
+import static org.opends.sdk.schema.SchemaConstants.*;
+import static org.opends.sdk.util.StaticUtils.toLowerCase;
+
+import java.util.HashSet;
+import java.util.StringTokenizer;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class defines the delivery method attribute syntax. This
+ * contains one or more of a fixed set of values. If there are multiple
+ * values, then they are separated by spaces with a dollar sign between
+ * them. The allowed values include:
+ * <UL>
+ * <LI>any</LI>
+ * <LI>mhs</LI>
+ * <LI>physical</LI>
+ * <LI>telex</LI>
+ * <LI>teletex</LI>
+ * <LI>g3fax</LI>
+ * <LI>g4fax</LI>
+ * <LI>ia5</LI>
+ * <LI>videotex</LI>
+ * <LI>telephone</LI>
+ * </UL>
+ */
+final class DeliveryMethodSyntaxImpl extends AbstractSyntaxImpl
+{
+ /**
+ * The set of values that may be used as delivery methods.
+ */
+ private static final HashSet<String> ALLOWED_VALUES =
+ new HashSet<String>();
+ {
+ ALLOWED_VALUES.add("any");
+ ALLOWED_VALUES.add("mhs");
+ ALLOWED_VALUES.add("physical");
+ ALLOWED_VALUES.add("telex");
+ ALLOWED_VALUES.add("teletex");
+ ALLOWED_VALUES.add("g3fax");
+ ALLOWED_VALUES.add("g4fax");
+ ALLOWED_VALUES.add("ia5");
+ ALLOWED_VALUES.add("videotex");
+ ALLOWED_VALUES.add("telephone");
+ }
+
+
+
+ @Override
+ public String getApproximateMatchingRule()
+ {
+ return AMR_DOUBLE_METAPHONE_OID;
+ }
+
+
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_DELIVERY_METHOD_NAME;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_CASE_IGNORE_OID;
+ }
+
+
+
+ @Override
+ public String getSubstringMatchingRule()
+ {
+ return SMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ final String stringValue = toLowerCase(value.toString());
+ final StringTokenizer tokenizer =
+ new StringTokenizer(stringValue, " $");
+ if (!tokenizer.hasMoreTokens())
+ {
+ invalidReason.append(ERR_ATTR_SYNTAX_DELIVERY_METHOD_NO_ELEMENTS
+ .get(value.toString()));
+ return false;
+ }
+
+ while (tokenizer.hasMoreTokens())
+ {
+ final String token = tokenizer.nextToken();
+ if (!ALLOWED_VALUES.contains(token))
+ {
+ invalidReason
+ .append(ERR_ATTR_SYNTAX_DELIVERY_METHOD_INVALID_ELEMENT
+ .get(value.toString(), token));
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/DirectoryStringFirstComponentEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/DirectoryStringFirstComponentEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..1814375
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/DirectoryStringFirstComponentEqualityMatchingRuleImpl.java
@@ -0,0 +1,140 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_EMPTY_VALUE;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_EXPECTED_OPEN_PARENTHESIS;
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.messages.Message;
+import org.opends.sdk.Assertion;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class implements the directoryStringFirstComponentMatch matching
+ * rule defined in X.520 and referenced in RFC 2252. This rule is
+ * intended for use with attributes whose values contain a set of
+ * parentheses enclosing a space-delimited set of names and/or
+ * name-value pairs (like attribute type or objectclass descriptions) in
+ * which the "first component" is the first item after the opening
+ * parenthesis.
+ */
+final class DirectoryStringFirstComponentEqualityMatchingRuleImpl
+ extends AbstractMatchingRuleImpl
+{
+ @Override
+ public Assertion getAssertion(Schema schema, ByteSequence value)
+ {
+ final StringBuilder buffer = new StringBuilder();
+ prepareUnicode(buffer, value, TRIM, CASE_FOLD);
+
+ final int bufferLength = buffer.length();
+ if (bufferLength == 0)
+ {
+ if (value.length() > 0)
+ {
+ // This should only happen if the value is composed entirely of
+ // spaces. In that case, the normalized value is a single space.
+ return new DefaultEqualityAssertion(
+ SchemaConstants.SINGLE_SPACE_VALUE);
+ }
+ else
+ {
+ // The value is empty, so it is already normalized.
+ return new DefaultEqualityAssertion(ByteString.empty());
+ }
+ }
+
+ // Replace any consecutive spaces with a single space.
+ for (int pos = bufferLength - 1; pos > 0; pos--)
+ {
+ if (buffer.charAt(pos) == ' ')
+ {
+ if (buffer.charAt(pos - 1) == ' ')
+ {
+ buffer.delete(pos, pos + 1);
+ }
+ }
+ }
+
+ return new DefaultEqualityAssertion(ByteString.valueOf(buffer
+ .toString()));
+ }
+
+
+
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value) throws DecodeException
+ {
+ final String definition = value.toString();
+ final SubstringReader reader = new SubstringReader(definition);
+
+ // We'll do this a character at a time. First, skip over any leading
+ // whitespace.
+ reader.skipWhitespaces();
+
+ if (reader.remaining() <= 0)
+ {
+ // This means that the value was empty or contained only
+ // whitespace.
+ // That is illegal.
+ final Message message = ERR_ATTR_SYNTAX_EMPTY_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ // The next character must be an open parenthesis. If it is not,
+ // then
+ // that is an error.
+ final char c = reader.read();
+ if (c != '(')
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_EXPECTED_OPEN_PARENTHESIS.get(definition,
+ (reader.pos() - 1), String.valueOf(c));
+ throw DecodeException.error(message);
+ }
+
+ // Skip over any spaces immediately following the opening
+ // parenthesis.
+ reader.skipWhitespaces();
+
+ // The next set of characters must be the OID.
+ final String string = SchemaUtils.readQuotedString(reader);
+
+ // Grab the substring between the start pos and the current pos
+ return ByteString.valueOf(string);
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/DirectoryStringSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/DirectoryStringSyntaxImpl.java
new file mode 100644
index 0000000..f53b391
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/DirectoryStringSyntaxImpl.java
@@ -0,0 +1,125 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_DIRECTORYSTRING_INVALID_ZEROLENGTH_VALUE;
+import static org.opends.sdk.schema.SchemaConstants.*;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class defines the directory string attribute syntax, which is
+ * simply a set of UTF-8 characters. By default, they will be treated in
+ * a case-insensitive manner, and equality, ordering, substring, and
+ * approximate matching will be allowed.
+ */
+final class DirectoryStringSyntaxImpl extends AbstractSyntaxImpl
+{
+ @Override
+ public String getApproximateMatchingRule()
+ {
+ return AMR_DOUBLE_METAPHONE_OID;
+ }
+
+
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_DIRECTORY_STRING_NAME;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_CASE_IGNORE_OID;
+ }
+
+
+
+ @Override
+ public String getSubstringMatchingRule()
+ {
+ return SMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ if (value.length() > 0
+ || schema.getSchemaCompatOptions()
+ .isZeroLengthDirectoryStringsAllowed())
+ {
+ return true;
+ }
+ else
+ {
+ invalidReason
+ .append(ERR_ATTR_SYNTAX_DIRECTORYSTRING_INVALID_ZEROLENGTH_VALUE
+ .get());
+ return false;
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/DistinguishedNameEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/DistinguishedNameEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..3a3d64f
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/DistinguishedNameEqualityMatchingRuleImpl.java
@@ -0,0 +1,213 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+import org.opends.sdk.*;
+import org.opends.sdk.RDN.AVA;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * This class defines the distinguishedNameMatch matching rule defined
+ * in X.520 and referenced in RFC 2252.
+ */
+final class DistinguishedNameEqualityMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+ private static final Comparator<AVA> ATV_COMPARATOR = new Comparator<AVA>()
+ {
+ public int compare(AVA o1, AVA o2)
+ {
+ return o1.getAttributeType().compareTo(o2.getAttributeType());
+ }
+ };
+
+
+
+ @Override
+ public Assertion getAssertion(final Schema schema, ByteSequence value)
+ throws DecodeException
+ {
+ DN assertion;
+ try
+ {
+ assertion = DN.valueOf(value.toString(), schema);
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ throw DecodeException.error(e.getMessageObject());
+ }
+
+ final DN finalAssertion = assertion;
+ return new Assertion()
+ {
+ public ConditionResult matches(ByteSequence attributeValue)
+ {
+ try
+ {
+ final DN attribute = DN.valueOf(attributeValue.toString(),
+ schema);
+ return matchDNs(finalAssertion, attribute);
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ return ConditionResult.UNDEFINED;
+ }
+ }
+ };
+ }
+
+
+
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value) throws DecodeException
+ {
+ try
+ {
+ return ByteString.valueOf(DN.valueOf(value.toString(), schema)
+ .toNormalizedString());
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ throw DecodeException.error(e.getMessageObject());
+ }
+ }
+
+
+
+ private ConditionResult matchAVAs(AVA ava1, AVA ava2)
+ {
+ final AttributeType type = ava1.getAttributeType();
+
+ if (!type.equals(ava2.getAttributeType()))
+ {
+ return ConditionResult.FALSE;
+ }
+
+ final MatchingRule matchingRule = type.getEqualityMatchingRule();
+ if (matchingRule != null)
+ {
+ try
+ {
+ final ByteString nv1 = matchingRule
+ .normalizeAttributeValue(ava1.getAttributeValue());
+ final ByteString nv2 = matchingRule
+ .normalizeAttributeValue(ava2.getAttributeValue());
+ return nv1.equals(nv2) ? ConditionResult.TRUE
+ : ConditionResult.FALSE;
+ }
+ catch (final DecodeException de)
+ {
+ return ConditionResult.UNDEFINED;
+ }
+ }
+
+ return ConditionResult.UNDEFINED;
+ }
+
+
+
+ private ConditionResult matchDNs(DN dn1, DN dn2)
+ {
+ final int sz1 = dn1.size();
+ final int sz2 = dn2.size();
+
+ if (sz1 != sz2)
+ {
+ return ConditionResult.FALSE;
+ }
+ else
+ {
+ final RDN rdn1 = dn1.rdn();
+ final RDN rdn2 = dn2.rdn();
+ while (rdn1 != null)
+ {
+ final ConditionResult result = matchRDNs(rdn1, rdn2);
+ if (result != ConditionResult.TRUE)
+ {
+ return result;
+ }
+ }
+ return ConditionResult.TRUE;
+ }
+ }
+
+
+
+ private ConditionResult matchRDNs(RDN rdn1, RDN rdn2)
+ {
+ final int sz1 = rdn1.size();
+ final int sz2 = rdn2.size();
+
+ if (sz1 != sz2)
+ {
+ return ConditionResult.FALSE;
+ }
+ else if (sz1 == 1)
+ {
+ return matchAVAs(rdn1.getFirstAVA(), rdn2.getFirstAVA());
+ }
+ else
+ {
+ // Need to sort the AVAs before comparing.
+ final AVA[] a1 = new AVA[sz1];
+ int i = 0;
+ for (final AVA ava : rdn1)
+ {
+ a1[i++] = ava;
+ }
+ Arrays.sort(a1, ATV_COMPARATOR);
+
+ final AVA[] a2 = new AVA[sz1];
+ i = 0;
+ for (final AVA ava : rdn2)
+ {
+ a2[i++] = ava;
+ }
+ Arrays.sort(a2, ATV_COMPARATOR);
+
+ for (i = 0; i < sz1; i++)
+ {
+ final ConditionResult result = matchAVAs(a1[i], a2[i]);
+ if (result != ConditionResult.TRUE)
+ {
+ return result;
+ }
+ }
+
+ return ConditionResult.TRUE;
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/DistinguishedNameSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/DistinguishedNameSyntaxImpl.java
new file mode 100644
index 0000000..f068290
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/DistinguishedNameSyntaxImpl.java
@@ -0,0 +1,95 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.EMR_DN_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_DN_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DN;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * This class defines the distinguished name attribute syntax, which is
+ * used for attributes that hold distinguished names (DNs). Equality and
+ * substring matching will be allowed by default.
+ */
+final class DistinguishedNameSyntaxImpl extends AbstractSyntaxImpl
+{
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_DN_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_DN_NAME;
+ }
+
+
+
+ @Override
+ public String getSubstringMatchingRule()
+ {
+ return SMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ try
+ {
+ DN.valueOf(value.toString(), schema);
+ }
+ catch (final LocalizedIllegalArgumentException de)
+ {
+ invalidReason.append(de.getMessageObject());
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/DoubleMetaphoneApproximateMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/DoubleMetaphoneApproximateMatchingRuleImpl.java
new file mode 100644
index 0000000..f365576
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/DoubleMetaphoneApproximateMatchingRuleImpl.java
@@ -0,0 +1,1117 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * This class defines an approximate matching rule based on the Double
+ * Metaphone algorithm. The Metaphone and Double Metaphone algorithms
+ * were originally devised by Lawrence Philips (published in the
+ * December 1990 issue of <I>Computer Language</I> and the <A
+ * HREF="http://www.cuj.com/documents/s=8038/cuj0006philips/">June 2000
+ * issue of <I>C/C++ Users Journal</I></A>, respectively), and this
+ * version of the algorithm is based on a version modified by Kevin
+ * Atkinson to include bugfixes and additional functionality (source is
+ * available <A HREF="http://aspell.net/metaphone/dmetaph.cpp">here</A>
+ * and additional Metaphone and Double Metaphone information is
+ * available at <A
+ * HREF="http://aspell.net/metaphone/">http://aspell.net/
+ * metaphone/</A>). This implementation is largely the same as the one
+ * provided by Kevin Atkinson, but it has been re-written for better
+ * readability, for more efficiency, to get rid of checks for conditions
+ * that can't possibly happen, and to get rid of redundant checks that
+ * aren't needed. It has also been updated to always only generate a
+ * single value rather than one or possibly two values.
+ */
+final class DoubleMetaphoneApproximateMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value)
+ {
+ String valueString = value.toString();
+ final int length = valueString.length();
+ if (length == 0)
+ {
+ // The value is empty, so it is already normalized.
+ return ByteString.empty();
+ }
+
+ final int last = length - 1;
+
+ // Pad the value to allow for checks to go past the end of the
+ // value.
+ valueString = valueString.toUpperCase() + " ";
+
+ // The metaphone value that is being constructed.
+ final StringBuilder metaphone = new StringBuilder(4);
+
+ // Skip over GN, KN, PN, WR, and PS at the beginning of a word.
+ int pos = 0;
+ String substring = valueString.substring(0, 2);
+ if (substring.equals("GN") || substring.equals("KN")
+ || substring.equals("PN") || substring.equals("WR")
+ || substring.equals("PS"))
+ {
+ pos++;
+ }
+
+ // 'X' at the beginning of a word will sound like Z, but Z will
+ // always be mapped to S.
+ else if (valueString.charAt(0) == 'X')
+ {
+ metaphone.append("S");
+ pos++;
+ }
+
+ // Loop until we have at least four metaphone characters or have
+ // reached the end of the string.
+ while (metaphone.length() < 4 && pos < length)
+ {
+ // Check the character at the current position against various
+ // targets.
+ char posMinusFour;
+ char posMinusThree;
+ char posMinusTwo;
+ char posMinusOne;
+ char posPlusOne;
+ char posPlusTwo;
+ switch (valueString.charAt(pos))
+ {
+ case 'A':
+ case 'E':
+ case 'I':
+ case 'O':
+ case 'U':
+ case 'Y':
+ // All initial vowels map to 'A'. All others will be ignored.
+ if (pos == 0)
+ {
+ metaphone.append("A");
+ }
+
+ pos++;
+ break;
+
+ case 'B':
+ // B and BB will be mapped to P, with the exception of "MB" as
+ // in "crumb", but that will be handled elsewhere.
+ metaphone.append("P");
+
+ if (valueString.charAt(++pos) == 'B')
+ {
+ pos++;
+ }
+
+ break;
+
+ case 'C':
+ // Check for various Germanic sequences, which will be mapped to
+ // 'K'. This basically includes all occurrences of "ACH" where
+ // the preceding character is not a vowel and the following
+ // character is neither an 'E' nor an 'I' except in "BACHER" and
+ // "MACHER".
+ if (pos > 1
+ && !isVowel(posMinusTwo = valueString.charAt(pos - 2))
+ && hasSubstring(valueString, pos - 1, "ACH")
+ && (posPlusTwo = valueString.charAt(pos + 2)) != 'I'
+ && (posPlusTwo != 'E' || valueString.charAt(pos + 3) == 'R'
+ && (posMinusTwo == 'B' || posMinusTwo == 'M')))
+ {
+ metaphone.append("K");
+ pos += 2;
+ break;
+ }
+
+ // Check for a special case of "caesar", which will be maped to
+ // 'S'.
+ if (pos == 0 && hasSubstring(valueString, pos + 1, "AESAR"))
+ {
+ metaphone.append("S");
+ pos += 2;
+ break;
+ }
+
+ // CH can be treated in lots of different ways.
+ if ((posPlusOne = valueString.charAt(pos + 1)) == 'H')
+ {
+ // Check for "chia" as in "chianti" and map to 'K'.
+ if (hasSubstring(valueString, pos + 2, "IA"))
+ {
+ metaphone.append("K");
+ pos += 2;
+ break;
+ }
+
+ // Check for "chae" as in "michael" and map to 'K'.
+ if (hasSubstring(valueString, pos + 2, "AE"))
+ {
+ metaphone.append("K");
+ pos += 2;
+ break;
+ }
+
+ // Check for a Greek root at the beginning of the value like
+ // chemistry or chorus and map to 'K'.
+ if (pos == 0
+ && !hasSubstring(valueString, 2, "ORE")
+ && (hasSubstring(valueString, 2, "ARAC")
+ || hasSubstring(valueString, 2, "ARIS")
+ || hasSubstring(valueString, 2, "OR")
+ || hasSubstring(valueString, 2, "YM")
+ || hasSubstring(valueString, 2, "IA") || hasSubstring(
+ valueString, 2, "EM")))
+ {
+ metaphone.append("K");
+ pos += 2;
+ break;
+ }
+
+ // Check for "CH" values that produce a "KH" sound that will
+ // be mapped to 'K'.
+ if (isGermanic(valueString)
+ || hasSubstring(valueString, pos - 2, "ORCHES")
+ || hasSubstring(valueString, pos - 2, "ARCHIT")
+ || hasSubstring(valueString, pos - 2, "ORCHID")
+ || (posPlusTwo = valueString.charAt(pos + 2)) == 'T'
+ || posPlusTwo == 'S'
+ || (pos == 0
+ || (posMinusOne = valueString.charAt(pos - 1)) == 'A'
+ || posMinusOne == 'O' || posMinusOne == 'U' || posMinusOne == 'E')
+ && (posPlusTwo == 'L' || posPlusTwo == 'R'
+ || posPlusTwo == 'N' || posPlusTwo == 'M'
+ || posPlusTwo == 'B' || posPlusTwo == 'H'
+ || posPlusTwo == 'F' || posPlusTwo == 'V' || posPlusTwo == 'W'))
+ {
+ metaphone.append("K");
+ pos += 2;
+ break;
+ }
+
+ // All other "CH" values.
+ if (pos > 0)
+ {
+ if (hasSubstring(valueString, 0, "MC"))
+ {
+ metaphone.append("K");
+ }
+ else
+ {
+ metaphone.append("X");
+ }
+ }
+ else
+ {
+ metaphone.append("X");
+ }
+
+ pos += 2;
+ break;
+ }
+
+ // Check for "CZ" as in "czerny" but not "wicz" and map to 'S'.
+ if (posPlusOne == 'Z'
+ && !hasSubstring(valueString, pos - 2, "WI"))
+ {
+ metaphone.append("S");
+ pos += 2;
+ break;
+ }
+
+ // Check for "CIA" as in "focaccia" and map to 'X'.
+ if (posPlusOne == 'I' && valueString.charAt(pos + 2) == 'A')
+ {
+ metaphone.append("X");
+ pos += 3;
+ break;
+ }
+
+ // Check for a double C but not in values that start with "McC"
+ if (posPlusOne == 'C'
+ && !(pos == 1 && valueString.charAt(0) == 'M'))
+ {
+ if (((posPlusTwo = valueString.charAt(pos + 2)) == 'I'
+ || posPlusTwo == 'E' || posPlusTwo == 'H')
+ && !(posPlusTwo == 'H' && valueString.charAt(pos + 3) == 'U'))
+ {
+ if (pos == 1 && valueString.charAt(pos - 1) == 'A'
+ || hasSubstring(valueString, pos - 1, "UCCEE")
+ || hasSubstring(valueString, pos - 1, "UCCES"))
+ {
+ // Values like "accident", "accede", and "succeed".
+ metaphone.append("K");
+ pos += 2;
+ break;
+ }
+ else
+ {
+ // Values like "bacci" or "bertucci".
+ metaphone.append("X");
+ pos += 3;
+ break;
+ }
+ }
+ else
+ {
+ // This is Pierce's Rule, whatever that means.
+ metaphone.append("K");
+ pos += 2;
+ break;
+ }
+ }
+
+ // Check for CK, CG, or CQ and map to 'K'. Check for CI, CE, and
+ // CY and map to "S".
+ if ((posPlusOne = valueString.charAt(pos + 1)) == 'K'
+ || posPlusOne == 'G' || posPlusOne == 'Q')
+ {
+ metaphone.append("K");
+ pos += 2;
+ break;
+ }
+
+ // Check for CI, CE, or CY and map to 'S'.
+ if (posPlusOne == 'I' || posPlusOne == 'E' || posPlusOne == 'Y')
+ {
+ metaphone.append("S");
+ pos += 2;
+ break;
+ }
+
+ // All other cases of "C" will be mapped to 'K'. However, the
+ // number of positions that we skip ahead may vary. If there is
+ // a value that consists of two words like "mac caffrey", then
+ // skip ahead three. For the character combinations of "CK" and
+ // "CQ", then skip ahead two. For the character combinations of
+ // "CC" except "CCE" and "CCI", then skip ahead two. For all
+ // other cases, skip ahead one.
+ metaphone.append("K");
+ switch (valueString.charAt(pos + 1))
+ {
+ case ' ':
+ switch (valueString.charAt(pos + 2))
+ {
+ case 'C':
+ case 'Q':
+ case 'G':
+ pos += 3;
+ break;
+ default:
+ pos++;
+ break;
+ }
+ break;
+
+ case 'K':
+ case 'Q':
+ pos += 2;
+ break;
+
+ case 'C':
+ switch (valueString.charAt(pos + 2))
+ {
+ case 'E':
+ case 'I':
+ pos++;
+ break;
+ default:
+ pos += 2;
+ break;
+ }
+ break;
+ default:
+ pos++;
+ }
+ break;
+
+ case 'D':
+ // DG will be mapped to either 'J' (in cases like edge) or 'TK'
+ // (in cases like Edgar).
+ if ((posPlusOne = valueString.charAt(pos + 1)) == 'G')
+ {
+ if ((posPlusTwo = valueString.charAt(pos + 2)) == 'I'
+ || posPlusTwo == 'E' || posPlusTwo == 'Y')
+ {
+ metaphone.append("J");
+ pos += 3;
+ break;
+ }
+ else
+ {
+ metaphone.append("TK");
+ pos += 2;
+ break;
+ }
+ }
+
+ // DT and DD will be mapped to 'T'.
+ if (posPlusOne == 'T' || posPlusOne == 'D')
+ {
+ metaphone.append("T");
+ pos += 2;
+ break;
+ }
+
+ // All other cases will be mapped to 'T'.
+ metaphone.append("T");
+ pos++;
+ break;
+
+ case 'F':
+ // F always maps to F. If there is a double F, then skip the
+ // second one.
+ metaphone.append("F");
+ pos++;
+ if (valueString.charAt(pos) == 'F')
+ {
+ pos++;
+ }
+ break;
+
+ case 'G':
+ if ((posPlusOne = valueString.charAt(pos + 1)) == 'H')
+ {
+ // A "GH" that is not preceded by a vowel will be mapped to
+ // 'K'.
+ if (pos > 0 && !isVowel(valueString.charAt(pos - 1)))
+ {
+ metaphone.append("K");
+ pos += 2;
+ break;
+ }
+
+ if (pos == 0)
+ {
+ if (valueString.charAt(pos + 2) == 'I')
+ {
+ // Words like ghislane or ghiradelli
+ metaphone.append("J");
+ }
+ else
+ {
+ metaphone.append("K");
+ }
+
+ pos += 2;
+ break;
+ }
+
+ // A refined version of Parker's Rule.
+ if (pos > 1
+ && ((posMinusTwo = valueString.charAt(pos - 2)) == 'B'
+ || posMinusTwo == 'H' || posMinusTwo == 'D')
+ || pos > 2
+ && ((posMinusThree = valueString.charAt(pos - 3)) == 'B'
+ || posMinusThree == 'H' || posMinusThree == 'D')
+ || pos > 3
+ && ((posMinusFour = valueString.charAt(pos - 4)) == 'B' || posMinusFour == 'H'))
+ {
+ pos += 2;
+ break;
+ }
+ else
+ {
+ if (pos > 2
+ && valueString.charAt(pos - 1) == 'U'
+ && ((posMinusThree = valueString.charAt(pos - 3)) == 'C'
+ || posMinusThree == 'G'
+ || posMinusThree == 'L'
+ || posMinusThree == 'R' || posMinusThree == 'T'))
+ {
+ // Words like laugh, McLaughlin, cough, rough are mapped
+ // to 'F'.
+ metaphone.append("F");
+ }
+ else if (pos > 0 && valueString.charAt(pos - 1) != 'I')
+ {
+ metaphone.append("K");
+ }
+
+ pos += 2;
+ break;
+ }
+ }
+
+ if (posPlusOne == 'N')
+ {
+ if (pos == 1 && isVowel(valueString.charAt(0))
+ && !isSlavoGermanic(valueString))
+ {
+ metaphone.append("KN");
+ pos += 2;
+ break;
+ }
+ else
+ {
+ if (!hasSubstring(valueString, pos + 2, "EY")
+ && !isSlavoGermanic(valueString))
+ {
+ metaphone.append("N");
+ }
+ else
+ {
+ metaphone.append("KN");
+ }
+
+ pos += 2;
+ break;
+ }
+ }
+
+ // GLI as in tagliaro will be mapped to "KL".
+ if (posPlusOne == 'L' && valueString.charAt(pos + 2) == 'I')
+ {
+ metaphone.append("KL");
+ pos += 2;
+ break;
+ }
+
+ // Forms of GY, GE, and GI at the beginning of a word will map
+ // to 'K'.
+ if (pos == 0
+ && (posPlusOne == 'Y'
+ || (substring = valueString.substring(pos + 1, pos + 3))
+ .equals("ES") || substring.equals("EP")
+ || substring.equals("EB") || substring.equals("EL")
+ || substring.equals("EY") || substring.equals("IB")
+ || substring.equals("IL") || substring.equals("IN")
+ || substring.equals("IE") || substring.equals("EI") || substring
+ .equals("ER")))
+ {
+ metaphone.append("K");
+ pos += 2;
+ break;
+ }
+
+ // Some occurrences of GER and GY in a word will be mapped to
+ // 'K'.
+ posPlusTwo = valueString.charAt(pos + 2);
+ if ((posPlusOne == 'E' && posPlusTwo == 'R' || posPlusOne == 'Y')
+ && (posMinusOne = valueString.charAt(pos - 1)) != 'E'
+ && posMinusOne != 'I'
+ && !hasSubstring(valueString, 0, "DANGER")
+ && !hasSubstring(valueString, 0, "RANGER")
+ && !hasSubstring(valueString, 0, "MANGER")
+ && !hasSubstring(valueString, pos - 1, "RGY")
+ && !hasSubstring(valueString, pos - 1, "OGY"))
+ {
+ metaphone.append("K");
+ pos += 2;
+ break;
+ }
+
+ // Check for Italian uses like 'biaggi" and map to 'J'.
+ if (posPlusOne == 'E' || posPlusOne == 'I' || posPlusOne == 'Y'
+ || hasSubstring(valueString, pos - 1, "AGGI")
+ || hasSubstring(valueString, pos - 1, "OGGI"))
+ {
+ // Germanic uses will be mapped to 'K'.
+ if (isGermanic(valueString)
+ || hasSubstring(valueString, pos + 1, "ET"))
+ {
+ metaphone.append("K");
+ }
+ else
+ {
+ metaphone.append("J");
+ }
+
+ pos += 2;
+ break;
+ }
+
+ // All other cases will be mapped to 'K'. If there is a double
+ // G, then skip two. Otherwise, just skip one.
+ metaphone.append("K");
+ pos++;
+
+ if (posPlusOne == 'G')
+ {
+ pos++;
+ }
+
+ break;
+
+ case 'H':
+ // The letter 'H' will only be processed if it is immediately
+ // followed by a vowel and is either the start of the word or
+ // preceded by a vowel.
+ if (isVowel(valueString.charAt(pos + 1)))
+ {
+ if (pos == 0 || isVowel(valueString.charAt(pos - 1)))
+ {
+ metaphone.append("H");
+ pos++;
+ }
+ }
+
+ pos++;
+ break;
+
+ case 'J':
+ // Take care of obvious Spanish uses that should map to 'H'.
+ if (hasSubstring(valueString, 0, "SAN "))
+ {
+ metaphone.append("H");
+ pos++;
+ break;
+ }
+
+ if (hasSubstring(valueString, pos, "JOSE"))
+ {
+ if (pos == 0 && valueString.charAt(pos + 4) == ' ')
+ {
+ metaphone.append("H");
+ }
+ else
+ {
+ metaphone.append("J");
+ }
+
+ pos++;
+ break;
+ }
+
+ // All other cases will be mapped to 'J'.
+ metaphone.append("J");
+
+ if (valueString.charAt(pos + 1) == 'J')
+ {
+ pos++;
+ }
+
+ pos++;
+ break;
+
+ case 'K':
+ // 'K' will always be mapped to 'K'. KK will be treated like K.
+ metaphone.append("K");
+
+ if (valueString.charAt(pos + 1) == 'K')
+ {
+ pos++;
+ }
+
+ pos++;
+ break;
+
+ case 'L':
+ // 'L' will always be mapped to 'L'. LL will be treated like L,
+ // even for potential Spanish uses.
+ metaphone.append("L");
+
+ if (valueString.charAt(pos + 1) == 'L')
+ {
+ pos++;
+ }
+
+ pos++;
+ break;
+
+ case 'M':
+ // 'M' will always be mapped to 'M'. MM will be treated like M.
+ // UMB in cases like "dumb" and "thumb" will be treated like M.
+ metaphone.append("M");
+
+ if (valueString.charAt(pos + 1) == 'M')
+ {
+ pos++;
+ }
+ else if (hasSubstring(valueString, pos - 1, "UMB"))
+ {
+ if (pos + 1 == last
+ || hasSubstring(valueString, pos + 2, "ER"))
+ {
+ pos++;
+ }
+ }
+
+ pos++;
+ break;
+
+ case 'N':
+ // 'N' will always be mapped to 'N'. NN will be treated like N.
+ metaphone.append("N");
+
+ if (valueString.charAt(pos + 1) == 'N')
+ {
+ pos++;
+ }
+
+ pos++;
+ break;
+
+ case 'P':
+ // PH will be mapped to 'F'.
+ if ((posPlusOne = valueString.charAt(pos + 1)) == 'H')
+ {
+ metaphone.append("F");
+ pos += 2;
+ break;
+ }
+
+ // All other cases will be mapped to 'P', with PP and PB being
+ // treated like P.
+ metaphone.append("P");
+
+ if (posPlusOne == 'P' || posPlusOne == 'B')
+ {
+ pos++;
+ }
+
+ pos++;
+ break;
+
+ case 'Q':
+ // 'Q' will always be mapped to 'K'. QQ will be treated like Q.
+ metaphone.append("K");
+
+ if (valueString.charAt(pos + 1) == 'Q')
+ {
+ pos++;
+ }
+
+ pos++;
+ break;
+
+ case 'R':
+ // Ignore R at the end of French words.
+ if (pos == last && !isSlavoGermanic(valueString)
+ && hasSubstring(valueString, pos - 2, "IE")
+ && !hasSubstring(valueString, pos - 4, "ME")
+ && !hasSubstring(valueString, pos - 4, "MA"))
+ {
+ pos++;
+ break;
+ }
+
+ // All other cases will be mapped to 'R', with RR treated like
+ // R.
+ metaphone.append("R");
+
+ if (valueString.charAt(pos + 1) == 'R')
+ {
+ pos++;
+ }
+
+ pos++;
+ break;
+
+ case 'S':
+ // Special cases like isle and carlysle will be silent.
+ if (hasSubstring(valueString, pos - 1, "ISL")
+ || hasSubstring(valueString, pos - 1, "YSL"))
+ {
+ pos++;
+ break;
+ }
+
+ // Special case of sugar mapped to 'X'.
+ if (hasSubstring(valueString, pos + 1, "UGAR"))
+ {
+ metaphone.append("X");
+ pos++;
+ break;
+ }
+
+ // SH is generally mapped to 'X', but not in Germanic cases.
+ if ((posPlusOne = valueString.charAt(pos + 1)) == 'H')
+ {
+ if (hasSubstring(valueString, pos + 1, "HEIM")
+ || hasSubstring(valueString, pos + 1, "HOEK")
+ || hasSubstring(valueString, pos + 1, "HOLM")
+ || hasSubstring(valueString, pos + 1, "HOLZ"))
+ {
+ metaphone.append("S");
+ }
+ else
+ {
+ metaphone.append("X");
+ }
+
+ pos += 2;
+ break;
+ }
+
+ // Italian and Armenian cases will map to "S".
+ if (hasSubstring(valueString, pos + 1, "IO")
+ || hasSubstring(valueString, pos + 1, "IA"))
+ {
+ metaphone.append("S");
+ pos += 3;
+ break;
+ }
+
+ // SZ should be mapped to 'S'.
+ if (posPlusOne == 'Z')
+ {
+ metaphone.append("S");
+ pos += 2;
+ break;
+ }
+
+ // Various combinations at the beginning of words will be mapped
+ // to 'S'.
+ if (pos == 0
+ && (posPlusOne == 'M' || posPlusOne == 'N'
+ || posPlusOne == 'L' || posPlusOne == 'W'))
+ {
+ metaphone.append("S");
+ pos++;
+ break;
+ }
+
+ // SC should be mapped to either SK, X, or S.
+ if (posPlusOne == 'C')
+ {
+ if ((posPlusTwo = valueString.charAt(pos + 2)) == 'H')
+ {
+ if (hasSubstring(valueString, pos + 3, "OO")
+ || hasSubstring(valueString, pos + 3, "UY")
+ || hasSubstring(valueString, pos + 3, "ED")
+ || hasSubstring(valueString, pos + 3, "EM"))
+ {
+ metaphone.append("SK");
+ }
+ else
+ {
+ metaphone.append("X");
+ }
+
+ pos += 3;
+ break;
+ }
+
+ if (posPlusTwo == 'I' || posPlusTwo == 'E'
+ || posPlusTwo == 'Y')
+ {
+ metaphone.append("S");
+ pos += 3;
+ break;
+ }
+
+ metaphone.append("SK");
+ pos += 3;
+ break;
+ }
+
+ // Ignore a trailing S in French words. All others will be
+ // mapped to 'S'.
+ if (!(pos == last && (hasSubstring(valueString, pos - 2, "AI") || hasSubstring(
+ valueString, pos - 2, "OI"))))
+ {
+ metaphone.append("S");
+ }
+
+ if (posPlusOne == 'S' || posPlusOne == 'Z')
+ {
+ pos++;
+ }
+
+ pos++;
+ break;
+
+ case 'T':
+ // "TION", "TIA", and "TCH" will be mapped to 'X'.
+ if (hasSubstring(valueString, pos, "TION")
+ || hasSubstring(valueString, pos, "TIA")
+ || hasSubstring(valueString, pos, "TCH"))
+ {
+ metaphone.append("X");
+ pos += 3;
+ break;
+ }
+
+ // TH or TTH will be mapped to either T (for Germanic cases) or
+ // 0 (zero) for the rest.
+ if ((posPlusOne = valueString.charAt(pos + 1)) == 'H'
+ || posPlusOne == 'T' && valueString.charAt(pos + 2) == 'H')
+ {
+ if (isGermanic(valueString)
+ || hasSubstring(valueString, pos + 2, "OM")
+ || hasSubstring(valueString, pos + 2, "AM"))
+ {
+ metaphone.append("T");
+ }
+ else
+ {
+ metaphone.append("0");
+ }
+
+ pos += 2;
+ break;
+ }
+
+ // All other cases will map to T, with TT and TD being treated
+ // like T.
+ metaphone.append("T");
+
+ if (posPlusOne == 'T' || posPlusOne == 'D')
+ {
+ pos++;
+ }
+
+ pos++;
+ break;
+
+ case 'V':
+ // 'V' will always be mapped to 'F', with VV treated like V.
+ metaphone.append("F");
+
+ if (valueString.charAt(pos + 1) == 'V')
+ {
+ pos++;
+ }
+
+ pos++;
+ break;
+
+ case 'W':
+ // WR should always map to R.
+ if ((posPlusOne = valueString.charAt(pos + 1)) == 'R')
+ {
+ metaphone.append("R");
+ pos += 2;
+ break;
+ }
+
+ // W[AEIOUYH] at the beginning of the word should be mapped to
+ // A.
+ if (pos == 0 && (isVowel(posPlusOne) || posPlusOne == 'H'))
+ {
+ metaphone.append("A");
+
+ // FIXME -- This isn't in the algorithm as written. Should it
+ // be?
+ pos += 2;
+ break;
+ }
+
+ // A Polish value like WICZ or WITZ should be mapped to TS.
+ if (hasSubstring(valueString, pos + 1, "WICZ")
+ || hasSubstring(valueString, pos + 1, "WITZ"))
+ {
+ metaphone.append("TS");
+ pos += 4;
+ break;
+ }
+
+ // Otherwise, we'll just skip it.
+ pos++;
+ break;
+
+ case 'X':
+ // X maps to KS except at the end of French words.
+ if (!(pos == last && (hasSubstring(valueString, pos - 3, "IAU")
+ || hasSubstring(valueString, pos - 3, "EAU")
+ || hasSubstring(valueString, pos - 2, "AU") || hasSubstring(
+ valueString, pos - 2, "OU"))))
+ {
+ metaphone.append("KS");
+ }
+
+ if ((posPlusOne = valueString.charAt(pos + 1)) == 'C'
+ || posPlusOne == 'X')
+ {
+ pos++;
+ }
+
+ pos++;
+ break;
+
+ case 'Z':
+ // Chinese usages like zhao will map to J.
+ if ((posPlusOne = valueString.charAt(pos + 1)) == 'H')
+ {
+ metaphone.append("J");
+ pos += 2;
+ break;
+ }
+
+ // All other cases map to "S". ZZ will be treated like Z.
+ metaphone.append("S");
+
+ if (posPlusOne == 'Z')
+ {
+ pos++;
+ }
+
+ pos++;
+ break;
+
+ case '\u00C7': // C with a cedilla
+ // This will always be mapped to 'S'.
+ metaphone.append("S");
+ pos++;
+ break;
+
+ case '\u00D1': // N with a tilde
+ // This will always be mapped to 'N'.
+ metaphone.append("N");
+ pos++;
+ break;
+
+ default:
+ // We don't have any special treatment for this character, so
+ // skip it.
+ pos++;
+ break;
+ }
+ }
+
+ return ByteString.valueOf(metaphone.toString());
+ }
+
+
+
+ /**
+ * Indicates whether the provided value has the given substring at the
+ * specified position.
+ *
+ * @param value
+ * The value containing the range for which to make the
+ * determination.
+ * @param start
+ * The position in the value at which to start the
+ * comparison.
+ * @param substring
+ * The substring to compare against the specified value
+ * range.
+ * @return <CODE>true</CODE> if the specified portion of the value
+ * matches the given substring, or <CODE>false</CODE> if it
+ * does not.
+ */
+ private boolean hasSubstring(String value, int start, String substring)
+ {
+ try
+ {
+ // This can happen since a lot of the rules "look behind" and
+ // rightfully don't check if it's the first character
+ if (start < 0)
+ {
+ return false;
+ }
+
+ final int end = start + substring.length();
+
+ // value isn't big enough to do the comparison
+ if (end > value.length())
+ {
+ return false;
+ }
+
+ for (int i = 0, pos = start; pos < end; i++, pos++)
+ {
+ if (value.charAt(pos) != substring.charAt(i))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ catch (final Exception e)
+ {
+ StaticUtils.DEBUG_LOG.throwing(
+ "DoubleMetaphoneApproximateMatchingRule", "hasSubstring", e);
+
+ return false;
+ }
+ }
+
+
+
+ /**
+ * Indicates whether the provided string appears Germanic (starts with
+ * "VAN ", "VON ", or "SCH").
+ *
+ * @param s
+ * The string for which to make the determination.
+ * @return <CODE>true</CODE> if the provided string appears Germanic,
+ * or <CODE>false</CODE> if not.
+ */
+ private boolean isGermanic(String s)
+ {
+ return s.startsWith("VAN ") || s.startsWith("VON ")
+ || s.startsWith("SCH");
+ }
+
+
+
+ /**
+ * Indicates whether the provided string appears to be Slavo-Germanic.
+ *
+ * @param s
+ * The string for which to make the determination.
+ * @return <CODE>true</CODE> if the provided string appears to be
+ * Slavo-Germanic, or <CODE>false</CODE> if not.
+ */
+ private boolean isSlavoGermanic(String s)
+ {
+ return s.contains("W") || s.contains("K") || s.contains("CZ")
+ || s.contains("WITZ");
+ }
+
+
+
+ /**
+ * Indicates whether the provided character is a vowel (including
+ * "Y").
+ *
+ * @param c
+ * The character for which to make the determination.
+ * @return <CODE>true</CODE> if the provided character is a vowel, or
+ * <CODE>false</CODE> if not.
+ */
+ private boolean isVowel(char c)
+ {
+ switch (c)
+ {
+ case 'A':
+ case 'E':
+ case 'I':
+ case 'O':
+ case 'U':
+ case 'Y':
+ return true;
+
+ default:
+ return false;
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/EnhancedGuideSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/EnhancedGuideSyntaxImpl.java
new file mode 100644
index 0000000..4790bd1
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/EnhancedGuideSyntaxImpl.java
@@ -0,0 +1,184 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.schema.SchemaConstants.EMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_ENHANCED_GUIDE_NAME;
+import static org.opends.sdk.util.StaticUtils.toLowerCase;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class implements the enhanced guide attribute syntax, which may
+ * be used to provide criteria for generating search filters for entries
+ * of a given objectclass.
+ */
+final class EnhancedGuideSyntaxImpl extends AbstractSyntaxImpl
+{
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_OCTET_STRING_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_ENHANCED_GUIDE_NAME;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_OCTET_STRING_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // Get a lowercase string version of the provided value.
+ final String valueStr = toLowerCase(value.toString());
+
+ // Find the position of the first octothorpe. It should denote the
+ // end of the objectclass.
+ final int sharpPos = valueStr.indexOf('#');
+ if (sharpPos < 0)
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_ENHANCEDGUIDE_NO_SHARP
+ .get(valueStr));
+ return false;
+ }
+
+ // Get the objectclass and see if it is a valid name or OID.
+ final String ocName = valueStr.substring(0, sharpPos).trim();
+ final int ocLength = ocName.length();
+ if (ocLength == 0)
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_ENHANCEDGUIDE_NO_OC
+ .get(valueStr));
+ return false;
+ }
+
+ try
+ {
+ SchemaUtils.readOID(new SubstringReader(ocName
+ .substring(ocLength)));
+ }
+ catch (final DecodeException de)
+ {
+ invalidReason.append(de.getMessageObject());
+ return false;
+ }
+
+ // Find the last octothorpe and make sure it is followed by a valid
+ // scope.
+ final int lastSharpPos = valueStr.lastIndexOf('#');
+ if (lastSharpPos == sharpPos)
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_ENHANCEDGUIDE_NO_FINAL_SHARP
+ .get(valueStr));
+ return false;
+ }
+
+ final String scopeStr = valueStr.substring(lastSharpPos + 1).trim();
+ if (!(scopeStr.equals("baseobject") || scopeStr.equals("onelevel")
+ || scopeStr.equals("wholesubtree") || scopeStr
+ .equals("subordinatesubtree")))
+ {
+ if (scopeStr.length() == 0)
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_ENHANCEDGUIDE_NO_SCOPE
+ .get(valueStr));
+ }
+ else
+ {
+
+ invalidReason
+ .append(ERR_ATTR_SYNTAX_ENHANCEDGUIDE_INVALID_SCOPE.get(
+ valueStr, scopeStr));
+ }
+
+ return false;
+ }
+
+ // Everything between the two octothorpes must be the criteria. Make
+ // sure it is valid.
+ final String criteria =
+ valueStr.substring(sharpPos + 1, lastSharpPos).trim();
+ final int criteriaLength = criteria.length();
+ if (criteriaLength == 0)
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_ENHANCEDGUIDE_NO_CRITERIA
+ .get(valueStr));
+ return false;
+ }
+
+ return GuideSyntaxImpl.criteriaIsValid(criteria, valueStr,
+ invalidReason);
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/EnumOrderingMatchingRule.java b/sdk/src/org/opends/sdk/schema/EnumOrderingMatchingRule.java
new file mode 100644
index 0000000..a82cd12
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/EnumOrderingMatchingRule.java
@@ -0,0 +1,73 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_LDAPSYNTAX_ENUM_INVALID_VALUE;
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class is the ordering matching rule implementation for an enum
+ * syntax implmentation. The ordering is determined by the order of the
+ * entries in the X-ENUM extension value.
+ */
+final class EnumOrderingMatchingRule extends
+ AbstractOrderingMatchingRuleImpl
+{
+ private final EnumSyntaxImpl syntax;
+
+
+
+ EnumOrderingMatchingRule(EnumSyntaxImpl syntax)
+ {
+ Validator.ensureNotNull(syntax);
+ this.syntax = syntax;
+ }
+
+
+
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value) throws DecodeException
+ {
+ final int index = syntax.indexOf(value);
+ if (index < 0)
+ {
+ throw DecodeException.error(
+ WARN_ATTR_SYNTAX_LDAPSYNTAX_ENUM_INVALID_VALUE.get(value
+ .toString(), syntax.getName()));
+ }
+ return ByteString.valueOf(index);
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/schema/EnumSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/EnumSyntaxImpl.java
new file mode 100644
index 0000000..4687fa5
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/EnumSyntaxImpl.java
@@ -0,0 +1,200 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_LDAPSYNTAX_ENUM_INVALID_VALUE;
+import static org.opends.sdk.schema.SchemaConstants.AMR_DOUBLE_METAPHONE_OID;
+import static org.opends.sdk.schema.SchemaConstants.EMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_OID_GENERIC_ENUM;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_IGNORE_OID;
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class provides an enumeration-based mechanism where a new syntax
+ * and its corresponding matching rules can be created on-the-fly. An
+ * enum syntax is an LDAPSyntaxDescriptionSyntax with X-ENUM extension.
+ */
+final class EnumSyntaxImpl extends AbstractSyntaxImpl
+{
+ private final String oid;
+ // Set of read-only enum entries.
+ private final List<String> entries;
+
+
+
+ EnumSyntaxImpl(String oid, List<String> entries)
+ {
+ Validator.ensureNotNull(oid, entries);
+ this.oid = oid;
+ final List<String> entryStrings =
+ new ArrayList<String>(entries.size());
+
+ for (final String entry : entries)
+ {
+ final String normalized = normalize(ByteString.valueOf(entry));
+ if (!entryStrings.contains(normalized))
+ {
+ entryStrings.add(normalized);
+ }
+ }
+ this.entries = Collections.unmodifiableList(entryStrings);
+ }
+
+
+
+ @Override
+ public String getApproximateMatchingRule()
+ {
+ return AMR_DOUBLE_METAPHONE_OID;
+ }
+
+
+
+ public Iterable<String> getEntries()
+ {
+ return entries;
+ }
+
+
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return oid;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_OID_GENERIC_ENUM + "." + oid;
+ }
+
+
+
+ @Override
+ public String getSubstringMatchingRule()
+ {
+ return SMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public int indexOf(ByteSequence value)
+ {
+ return entries.indexOf(normalize(value));
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // The value is acceptable if it belongs to the set.
+ final boolean isAllowed = entries.contains(normalize(value));
+
+ if (!isAllowed)
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_LDAPSYNTAX_ENUM_INVALID_VALUE.get(value
+ .toString(), oid);
+ invalidReason.append(message);
+ }
+
+ return isAllowed;
+ }
+
+
+
+ private String normalize(ByteSequence value)
+ {
+ final StringBuilder buffer = new StringBuilder();
+ prepareUnicode(buffer, value, TRIM, CASE_FOLD);
+
+ final int bufferLength = buffer.length();
+ if (bufferLength == 0)
+ {
+ if (value.length() > 0)
+ {
+ // This should only happen if the value is composed entirely of
+ // spaces. In that case, the normalized value is a single space.
+ return " ";
+ }
+ else
+ {
+ // The value is empty, so it is already normalized.
+ return "";
+ }
+ }
+
+ // Replace any consecutive spaces with a single space.
+ for (int pos = bufferLength - 1; pos > 0; pos--)
+ {
+ if (buffer.charAt(pos) == ' ')
+ {
+ if (buffer.charAt(pos - 1) == ' ')
+ {
+ buffer.delete(pos, pos + 1);
+ }
+ }
+ }
+
+ return buffer.toString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/EqualLengthApproximateMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/EqualLengthApproximateMatchingRuleImpl.java
new file mode 100644
index 0000000..469ef63
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/EqualLengthApproximateMatchingRuleImpl.java
@@ -0,0 +1,71 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.Assertion;
+import org.opends.sdk.ConditionResult;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements an extremely simple approximate matching rule
+ * that will consider two values approximately equal only if they have
+ * the same length. It is intended purely for testing purposes.
+ */
+final class EqualLengthApproximateMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+ @Override
+ public Assertion getAssertion(Schema schema, final ByteSequence value)
+ throws DecodeException
+ {
+ return new Assertion()
+ {
+ public ConditionResult matches(ByteSequence attributeValue)
+ {
+ return attributeValue.length() == value.length() ? ConditionResult.TRUE
+ : ConditionResult.FALSE;
+ }
+ };
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value)
+ {
+ return value.toByteString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/FacsimileNumberSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/FacsimileNumberSyntaxImpl.java
new file mode 100644
index 0000000..5112a04
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/FacsimileNumberSyntaxImpl.java
@@ -0,0 +1,240 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_FAXNUMBER_EMPTY;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_FAXNUMBER_END_WITH_DOLLAR;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_FAXNUMBER_ILLEGAL_PARAMETER;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_FAXNUMBER_NOT_PRINTABLE;
+import static org.opends.sdk.schema.SchemaConstants.EMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_FAXNUMBER_NAME;
+import static org.opends.sdk.util.StaticUtils.toLowerCase;
+
+import java.util.HashSet;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the facsimile telephone number attribute
+ * syntax, which contains a printable string (the number) followed by
+ * zero or more parameters. Those parameters should start with a dollar
+ * sign may be any of the following strings:
+ * <UL>
+ * <LI>twoDimensional</LI>
+ * <LI>fineResolution</LI>
+ * <LI>unlimitedLength</LI>
+ * <LI>b4Length</LI>
+ * <LI>a3Width</LI>
+ * <LI>b4Width</LI>
+ * <LI>uncompressed</LI>
+ * </UL>
+ */
+final class FacsimileNumberSyntaxImpl extends AbstractSyntaxImpl
+{
+ /**
+ * The set of allowed fax parameter values, formatted entirely in
+ * lowercase characters.
+ */
+ public static final HashSet<String> ALLOWED_FAX_PARAMETERS =
+ new HashSet<String>(7);
+
+ static
+ {
+ ALLOWED_FAX_PARAMETERS.add("twodimensional");
+ ALLOWED_FAX_PARAMETERS.add("fineresolution");
+ ALLOWED_FAX_PARAMETERS.add("unlimitedlength");
+ ALLOWED_FAX_PARAMETERS.add("b4length");
+ ALLOWED_FAX_PARAMETERS.add("a3width");
+ ALLOWED_FAX_PARAMETERS.add("b4width");
+ ALLOWED_FAX_PARAMETERS.add("uncompressed");
+ }
+
+
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_FAXNUMBER_NAME;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_CASE_IGNORE_OID;
+ }
+
+
+
+ @Override
+ public String getSubstringMatchingRule()
+ {
+ return SMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // Get a lowercase string representation of the value and find its
+ // length.
+ final String valueString = toLowerCase(value.toString());
+ final int valueLength = valueString.length();
+
+ // The value must contain at least one character.
+ if (valueLength == 0)
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_FAXNUMBER_EMPTY.get());
+ return false;
+ }
+
+ // The first character must be a printable string character.
+ char c = valueString.charAt(0);
+ if (!PrintableStringSyntaxImpl.isPrintableCharacter(c))
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_FAXNUMBER_NOT_PRINTABLE.get(
+ valueString, String.valueOf(c), 0));
+ return false;
+ }
+
+ // Continue reading until we find a dollar sign or the end of the
+ // string. Every intermediate character must be a printable string
+ // character.
+ int pos = 1;
+ for (; pos < valueLength; pos++)
+ {
+ c = valueString.charAt(pos);
+ if (c == '$')
+ {
+ pos++;
+ break;
+ }
+ else
+ {
+ if (!PrintableStringSyntaxImpl.isPrintableCharacter(c))
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_FAXNUMBER_NOT_PRINTABLE
+ .get(valueString, String.valueOf(c), pos));
+ }
+ }
+ }
+
+ if (pos >= valueLength)
+ {
+ // We're at the end of the value, so it must be valid unless the
+ // last character was a dollar sign.
+ if (c == '$')
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_FAXNUMBER_END_WITH_DOLLAR
+ .get(valueString));
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ // Continue reading until we find the end of the string. Each
+ // substring must be a valid fax parameter.
+ int paramStartPos = pos;
+ while (pos < valueLength)
+ {
+ c = valueString.charAt(pos++);
+ if (c == '$')
+ {
+ final String paramStr =
+ valueString.substring(paramStartPos, pos);
+ if (!ALLOWED_FAX_PARAMETERS.contains(paramStr))
+ {
+
+ invalidReason
+ .append(ERR_ATTR_SYNTAX_FAXNUMBER_ILLEGAL_PARAMETER.get(
+ valueString, paramStr, paramStartPos, (pos - 1)));
+ return false;
+ }
+
+ paramStartPos = pos;
+ }
+ }
+
+ // We must be at the end of the value. Read the last parameter and
+ // make sure it is valid.
+ final String paramStr = valueString.substring(paramStartPos);
+ if (!ALLOWED_FAX_PARAMETERS.contains(paramStr))
+ {
+ invalidReason.append(ERR_ATTR_SYNTAX_FAXNUMBER_ILLEGAL_PARAMETER
+ .get(valueString, paramStr, paramStartPos, (pos - 1)));
+ return false;
+ }
+
+ // If we've gotten here, then the value must be valid.
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/FaxSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/FaxSyntaxImpl.java
new file mode 100644
index 0000000..eff93f0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/FaxSyntaxImpl.java
@@ -0,0 +1,100 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.EMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_FAX_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the fax attribute syntax. This should be
+ * restricted to holding only fax message contents, but we will accept
+ * any set of bytes. It will be treated much like the octet string
+ * attribute syntax.
+ */
+final class FaxSyntaxImpl extends AbstractSyntaxImpl
+{
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_OCTET_STRING_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_FAX_NAME;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_OCTET_STRING_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // All values will be acceptable for the fax syntax.
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/GeneralizedTimeEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/GeneralizedTimeEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..a153074
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/GeneralizedTimeEqualityMatchingRuleImpl.java
@@ -0,0 +1,50 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the generalizedTimeMatch matching rule defined in
+ * X.520 and referenced in RFC 2252.
+ */
+final class GeneralizedTimeEqualityMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value) throws DecodeException
+ {
+ return ByteString.valueOf(GeneralizedTimeSyntaxImpl
+ .decodeGeneralizedTimeValue(value));
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/GeneralizedTimeOrderingMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/GeneralizedTimeOrderingMatchingRuleImpl.java
new file mode 100644
index 0000000..dc7c429
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/GeneralizedTimeOrderingMatchingRuleImpl.java
@@ -0,0 +1,50 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the generalizedTimeOrderingMatch matching rule
+ * defined in X.520 and referenced in RFC 2252.
+ */
+final class GeneralizedTimeOrderingMatchingRuleImpl extends
+ AbstractOrderingMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value) throws DecodeException
+ {
+ return ByteString.valueOf(GeneralizedTimeSyntaxImpl
+ .decodeGeneralizedTimeValue(value));
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/GeneralizedTimeSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/GeneralizedTimeSyntaxImpl.java
new file mode 100644
index 0000000..9df5dde
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/GeneralizedTimeSyntaxImpl.java
@@ -0,0 +1,1463 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.schema.SchemaConstants.*;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * This class implements the fax attribute syntax. This should be
+ * restricted to holding only fax message contents, but we will accept
+ * any set of bytes. It will be treated much like the octet string
+ * attribute syntax.
+ */
+final class GeneralizedTimeSyntaxImpl extends AbstractSyntaxImpl
+{
+
+ // UTC TimeZone is assumed to never change over JVM lifetime
+ private static final TimeZone TIME_ZONE_UTC_OBJ =
+ TimeZone.getTimeZone(TIME_ZONE_UTC);
+
+
+
+ /**
+ * Decodes the provided normalized value as a generalized time value
+ * and retrieves a timestamp containing its representation.
+ *
+ * @param value
+ * The normalized value to decode using the generalized time
+ * syntax.
+ * @return The timestamp created from the provided generalized time
+ * value.
+ * @throws DecodeException
+ * If the provided value cannot be parsed as a valid
+ * generalized time string.
+ */
+ static long decodeGeneralizedTimeValue(ByteSequence value)
+ throws DecodeException
+ {
+ int year = 0;
+ int month = 0;
+ int day = 0;
+ int hour = 0;
+ int minute = 0;
+ int second = 0;
+
+ // Get the value as a string and verify that it is at least long
+ // enough for "YYYYMMDDhhZ", which is the shortest allowed value.
+ final String valueString = value.toString().toUpperCase();
+ final int length = valueString.length();
+ if (length < 11)
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_TOO_SHORT.get(valueString);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ // The first four characters are the century and year, and they must
+ // be numeric digits between 0 and 9.
+ for (int i = 0; i < 4; i++)
+ {
+ switch (valueString.charAt(i))
+ {
+ case '0':
+ year = year * 10;
+ break;
+
+ case '1':
+ year = year * 10 + 1;
+ break;
+
+ case '2':
+ year = year * 10 + 2;
+ break;
+
+ case '3':
+ year = year * 10 + 3;
+ break;
+
+ case '4':
+ year = year * 10 + 4;
+ break;
+
+ case '5':
+ year = year * 10 + 5;
+ break;
+
+ case '6':
+ year = year * 10 + 6;
+ break;
+
+ case '7':
+ year = year * 10 + 7;
+ break;
+
+ case '8':
+ year = year * 10 + 8;
+ break;
+
+ case '9':
+ year = year * 10 + 9;
+ break;
+
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_YEAR.get(
+ valueString, String.valueOf(valueString.charAt(i)));
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+ }
+
+ // The next two characters are the month, and they must form the
+ // string representation of an integer between 01 and 12.
+ char m1 = valueString.charAt(4);
+ final char m2 = valueString.charAt(5);
+ switch (m1)
+ {
+ case '0':
+ // m2 must be a digit between 1 and 9.
+ switch (m2)
+ {
+ case '1':
+ month = Calendar.JANUARY;
+ break;
+
+ case '2':
+ month = Calendar.FEBRUARY;
+ break;
+
+ case '3':
+ month = Calendar.MARCH;
+ break;
+
+ case '4':
+ month = Calendar.APRIL;
+ break;
+
+ case '5':
+ month = Calendar.MAY;
+ break;
+
+ case '6':
+ month = Calendar.JUNE;
+ break;
+
+ case '7':
+ month = Calendar.JULY;
+ break;
+
+ case '8':
+ month = Calendar.AUGUST;
+ break;
+
+ case '9':
+ month = Calendar.SEPTEMBER;
+ break;
+
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MONTH.get(
+ valueString, valueString.substring(4, 6));
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+ break;
+ case '1':
+ // m2 must be a digit between 0 and 2.
+ switch (m2)
+ {
+ case '0':
+ month = Calendar.OCTOBER;
+ break;
+
+ case '1':
+ month = Calendar.NOVEMBER;
+ break;
+
+ case '2':
+ month = Calendar.DECEMBER;
+ break;
+
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MONTH.get(
+ valueString, valueString.substring(4, 6));
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+ break;
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MONTH.get(
+ valueString, valueString.substring(4, 6));
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ // The next two characters should be the day of the month, and they
+ // must form the string representation of an integer between 01 and
+ // 31. This doesn't do any validation against the year or month, so
+ // it will allow dates like April 31, or February 29 in a non-leap
+ // year, but we'll let those slide.
+ final char d1 = valueString.charAt(6);
+ final char d2 = valueString.charAt(7);
+ switch (d1)
+ {
+ case '0':
+ // d2 must be a digit between 1 and 9.
+ switch (d2)
+ {
+ case '1':
+ day = 1;
+ break;
+
+ case '2':
+ day = 2;
+ break;
+
+ case '3':
+ day = 3;
+ break;
+
+ case '4':
+ day = 4;
+ break;
+
+ case '5':
+ day = 5;
+ break;
+
+ case '6':
+ day = 6;
+ break;
+
+ case '7':
+ day = 7;
+ break;
+
+ case '8':
+ day = 8;
+ break;
+
+ case '9':
+ day = 9;
+ break;
+
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY.get(
+ valueString, valueString.substring(6, 8));
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+ break;
+
+ case '1':
+ // d2 must be a digit between 0 and 9.
+ switch (d2)
+ {
+ case '0':
+ day = 10;
+ break;
+
+ case '1':
+ day = 11;
+ break;
+
+ case '2':
+ day = 12;
+ break;
+
+ case '3':
+ day = 13;
+ break;
+
+ case '4':
+ day = 14;
+ break;
+
+ case '5':
+ day = 15;
+ break;
+
+ case '6':
+ day = 16;
+ break;
+
+ case '7':
+ day = 17;
+ break;
+
+ case '8':
+ day = 18;
+ break;
+
+ case '9':
+ day = 19;
+ break;
+
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY.get(
+ valueString, valueString.substring(6, 8));
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+ break;
+
+ case '2':
+ // d2 must be a digit between 0 and 9.
+ switch (d2)
+ {
+ case '0':
+ day = 20;
+ break;
+
+ case '1':
+ day = 21;
+ break;
+
+ case '2':
+ day = 22;
+ break;
+
+ case '3':
+ day = 23;
+ break;
+
+ case '4':
+ day = 24;
+ break;
+
+ case '5':
+ day = 25;
+ break;
+
+ case '6':
+ day = 26;
+ break;
+
+ case '7':
+ day = 27;
+ break;
+
+ case '8':
+ day = 28;
+ break;
+
+ case '9':
+ day = 29;
+ break;
+
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY.get(
+ valueString, valueString.substring(6, 8));
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+ break;
+
+ case '3':
+ // d2 must be either 0 or 1.
+ switch (d2)
+ {
+ case '0':
+ day = 30;
+ break;
+
+ case '1':
+ day = 31;
+ break;
+
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY.get(
+ valueString, valueString.substring(6, 8));
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+ break;
+
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY.get(
+ valueString, valueString.substring(6, 8));
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ // The next two characters must be the hour, and they must form the
+ // string representation of an integer between 00 and 23.
+ final char h1 = valueString.charAt(8);
+ final char h2 = valueString.charAt(9);
+ switch (h1)
+ {
+ case '0':
+ switch (h2)
+ {
+ case '0':
+ hour = 0;
+ break;
+
+ case '1':
+ hour = 1;
+ break;
+
+ case '2':
+ hour = 2;
+ break;
+
+ case '3':
+ hour = 3;
+ break;
+
+ case '4':
+ hour = 4;
+ break;
+
+ case '5':
+ hour = 5;
+ break;
+
+ case '6':
+ hour = 6;
+ break;
+
+ case '7':
+ hour = 7;
+ break;
+
+ case '8':
+ hour = 8;
+ break;
+
+ case '9':
+ hour = 9;
+ break;
+
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR.get(
+ valueString, valueString.substring(8, 10));
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+ break;
+
+ case '1':
+ switch (h2)
+ {
+ case '0':
+ hour = 10;
+ break;
+
+ case '1':
+ hour = 11;
+ break;
+
+ case '2':
+ hour = 12;
+ break;
+
+ case '3':
+ hour = 13;
+ break;
+
+ case '4':
+ hour = 14;
+ break;
+
+ case '5':
+ hour = 15;
+ break;
+
+ case '6':
+ hour = 16;
+ break;
+
+ case '7':
+ hour = 17;
+ break;
+
+ case '8':
+ hour = 18;
+ break;
+
+ case '9':
+ hour = 19;
+ break;
+
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR.get(
+ valueString, valueString.substring(8, 10));
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+ break;
+
+ case '2':
+ switch (h2)
+ {
+ case '0':
+ hour = 20;
+ break;
+
+ case '1':
+ hour = 21;
+ break;
+
+ case '2':
+ hour = 22;
+ break;
+
+ case '3':
+ hour = 23;
+ break;
+
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR.get(
+ valueString, valueString.substring(8, 10));
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+ break;
+
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR.get(
+ valueString, valueString.substring(8, 10));
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ // Next, there should be either two digits comprising an integer
+ // between 00 and 59 (for the minute), a letter 'Z' (for the UTC
+ // specifier), a plus or minus sign followed by two or four digits
+ // (for the UTC offset), or a period or comma representing the
+ // fraction.
+ m1 = valueString.charAt(10);
+ switch (m1)
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ // There must be at least two more characters, and the next one
+ // must be a digit between 0 and 9.
+ if (length < 13)
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get(
+ valueString, String.valueOf(m1), 10);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ minute = 10 * (m1 - '0');
+
+ switch (valueString.charAt(11))
+ {
+ case '0':
+ break;
+
+ case '1':
+ minute += 1;
+ break;
+
+ case '2':
+ minute += 2;
+ break;
+
+ case '3':
+ minute += 3;
+ break;
+
+ case '4':
+ minute += 4;
+ break;
+
+ case '5':
+ minute += 5;
+ break;
+
+ case '6':
+ minute += 6;
+ break;
+
+ case '7':
+ minute += 7;
+ break;
+
+ case '8':
+ minute += 8;
+ break;
+
+ case '9':
+ minute += 9;
+ break;
+
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MINUTE.get(
+ valueString, valueString.substring(10, 12));
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ break;
+
+ case 'Z':
+ // This is fine only if we are at the end of the value.
+ if (length == 11)
+ {
+ try
+ {
+ final GregorianCalendar calendar = new GregorianCalendar();
+ calendar.setLenient(false);
+ calendar.setTimeZone(TIME_ZONE_UTC_OBJ);
+ calendar.set(year, month, day, hour, minute, second);
+ calendar.set(Calendar.MILLISECOND, 0);
+ return calendar.getTimeInMillis();
+ }
+ catch (final Exception e)
+ {
+ // This should only happen if the provided date wasn't legal
+ // (e.g., September 31).
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME.get(
+ valueString, String.valueOf(e));
+ final DecodeException de = DecodeException.error(message, e);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", de);
+ throw de;
+ }
+ }
+ else
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get(
+ valueString, String.valueOf(m1), 10);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ case '+':
+ case '-':
+ // These are fine only if there are exactly two or four more
+ // digits that specify a valid offset.
+ if (length == 13 || length == 15)
+ {
+ try
+ {
+ final GregorianCalendar calendar = new GregorianCalendar();
+ calendar.setLenient(false);
+ calendar.setTimeZone(getTimeZoneForOffset(valueString, 10));
+ calendar.set(year, month, day, hour, minute, second);
+ calendar.set(Calendar.MILLISECOND, 0);
+ return calendar.getTimeInMillis();
+ }
+ catch (final Exception e)
+ {
+
+ // This should only happen if the provided date wasn't legal
+ // (e.g., September 31).
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME.get(
+ valueString, String.valueOf(e));
+ final DecodeException de = DecodeException.error(message, e);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", de);
+ throw de;
+ }
+ }
+ else
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get(
+ valueString, String.valueOf(m1), 10);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ case '.':
+ case ',':
+ return finishDecodingFraction(valueString, 11, year, month, day,
+ hour, minute, second, 3600000);
+
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get(
+ valueString, String.valueOf(m1), 10);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ // Next, there should be either two digits comprising an integer
+ // between 00 and 60 (for the second, including a possible leap
+ // second), a letter 'Z' (for the UTC specifier), a plus or minus
+ // sign followed by two or four digits (for the UTC offset), or a
+ // period or comma to start the fraction.
+ final char s1 = valueString.charAt(12);
+ switch (s1)
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ // There must be at least two more characters, and the next one
+ // must be a digit between 0 and 9.
+ if (length < 15)
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get(
+ valueString, String.valueOf(s1), 12);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ second = 10 * (s1 - '0');
+
+ switch (valueString.charAt(13))
+ {
+ case '0':
+ break;
+
+ case '1':
+ second += 1;
+ break;
+
+ case '2':
+ second += 2;
+ break;
+
+ case '3':
+ second += 3;
+ break;
+
+ case '4':
+ second += 4;
+ break;
+
+ case '5':
+ second += 5;
+ break;
+
+ case '6':
+ second += 6;
+ break;
+
+ case '7':
+ second += 7;
+ break;
+
+ case '8':
+ second += 8;
+ break;
+
+ case '9':
+ second += 9;
+ break;
+
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MINUTE.get(
+ valueString, valueString.substring(12, 14));
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ break;
+
+ case '6':
+ // There must be at least two more characters and the next one
+ // must be a 0.
+ if (length < 15)
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get(
+ valueString, String.valueOf(s1), 12);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ if (valueString.charAt(13) != '0')
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_SECOND.get(
+ valueString, valueString.substring(12, 14));
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ second = 60;
+ break;
+
+ case 'Z':
+ // This is fine only if we are at the end of the value.
+ if (length == 13)
+ {
+ try
+ {
+ final GregorianCalendar calendar = new GregorianCalendar();
+ calendar.setLenient(false);
+ calendar.setTimeZone(TIME_ZONE_UTC_OBJ);
+ calendar.set(year, month, day, hour, minute, second);
+ calendar.set(Calendar.MILLISECOND, 0);
+ return calendar.getTimeInMillis();
+ }
+ catch (final Exception e)
+ {
+
+ // This should only happen if the provided date wasn't legal
+ // (e.g., September 31).
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME.get(
+ valueString, String.valueOf(e));
+ final DecodeException de = DecodeException.error(message, e);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", de);
+ throw de;
+ }
+ }
+ else
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get(
+ valueString, String.valueOf(s1), 12);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ case '+':
+ case '-':
+ // These are fine only if there are exactly two or four more
+ // digits that specify a valid offset.
+ if (length == 15 || length == 17)
+ {
+ try
+ {
+ final GregorianCalendar calendar = new GregorianCalendar();
+ calendar.setLenient(false);
+ calendar.setTimeZone(getTimeZoneForOffset(valueString, 12));
+ calendar.set(year, month, day, hour, minute, second);
+ calendar.set(Calendar.MILLISECOND, 0);
+ return calendar.getTimeInMillis();
+ }
+ catch (final Exception e)
+ {
+
+ // This should only happen if the provided date wasn't legal
+ // (e.g., September 31).
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME.get(
+ valueString, String.valueOf(e));
+ final DecodeException de = DecodeException.error(message, e);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", de);
+ throw de;
+ }
+ }
+ else
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get(
+ valueString, String.valueOf(s1), 12);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ case '.':
+ case ',':
+ return finishDecodingFraction(valueString, 13, year, month, day,
+ hour, minute, second, 60000);
+
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get(
+ valueString, String.valueOf(s1), 12);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ // Next, there should be either a period or comma followed by
+ // between one and three digits (to specify the sub-second), a
+ // letter 'Z' (for the UTC specifier), or a plus or minus sign
+ // followed by two our four digits (for the UTC offset).
+ switch (valueString.charAt(14))
+ {
+ case '.':
+ case ',':
+ return finishDecodingFraction(valueString, 15, year, month, day,
+ hour, minute, second, 1000);
+
+ case 'Z':
+ // This is fine only if we are at the end of the value.
+ if (length == 15)
+ {
+ try
+ {
+ final GregorianCalendar calendar = new GregorianCalendar();
+ calendar.setLenient(false);
+ calendar.setTimeZone(TIME_ZONE_UTC_OBJ);
+ calendar.set(year, month, day, hour, minute, second);
+ calendar.set(Calendar.MILLISECOND, 0);
+ return calendar.getTimeInMillis();
+ }
+ catch (final Exception e)
+ {
+ // This should only happen if the provided date wasn't legal
+ // (e.g., September 31).
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME.get(
+ valueString, String.valueOf(e));
+ final DecodeException de = DecodeException.error(message, e);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", de);
+ throw de;
+ }
+ }
+ else
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR
+ .get(valueString, String
+ .valueOf(valueString.charAt(14)), 14);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ case '+':
+ case '-':
+ // These are fine only if there are exactly two or four more
+ // digits that specify a valid offset.
+ if (length == 17 || length == 19)
+ {
+ try
+ {
+ final GregorianCalendar calendar = new GregorianCalendar();
+ calendar.setLenient(false);
+ calendar.setTimeZone(getTimeZoneForOffset(valueString, 14));
+ calendar.set(year, month, day, hour, minute, second);
+ calendar.set(Calendar.MILLISECOND, 0);
+ return calendar.getTimeInMillis();
+ }
+ catch (final Exception e)
+ {
+ // This should only happen if the provided date wasn't legal
+ // (e.g., September 31).
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME.get(
+ valueString, String.valueOf(e));
+ final DecodeException de = DecodeException.error(message, e);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", de);
+ throw de;
+ }
+ }
+ else
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR
+ .get(valueString, String
+ .valueOf(valueString.charAt(14)), 14);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get(
+ valueString, String.valueOf(valueString.charAt(14)), 14);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+ }
+
+
+
+ /**
+ * Completes decoding the generalized time value containing a
+ * fractional component. It will also decode the trailing 'Z' or
+ * offset.
+ *
+ * @param value
+ * The whole value, including the fractional component and
+ * time zone information.
+ * @param startPos
+ * The position of the first character after the period in
+ * the value string.
+ * @param year
+ * The year decoded from the provided value.
+ * @param month
+ * The month decoded from the provided value.
+ * @param day
+ * The day decoded from the provided value.
+ * @param hour
+ * The hour decoded from the provided value.
+ * @param minute
+ * The minute decoded from the provided value.
+ * @param second
+ * The second decoded from the provided value.
+ * @param multiplier
+ * The multiplier value that should be used to scale the
+ * fraction appropriately. If it's a fraction of an hour,
+ * then it should be 3600000 (60*60*1000). If it's a fraction
+ * of a minute, then it should be 60000. If it's a fraction
+ * of a second, then it should be 1000.
+ * @return The timestamp created from the provided generalized time
+ * value including the fractional element.
+ * @throws DecodeException
+ * If the provided value cannot be parsed as a valid
+ * generalized time string.
+ */
+ private static long finishDecodingFraction(String value,
+ int startPos, int year, int month, int day, int hour, int minute,
+ int second, int multiplier) throws DecodeException
+ {
+ final int length = value.length();
+ final StringBuilder fractionBuffer =
+ new StringBuilder(2 + length - startPos);
+ fractionBuffer.append("0.");
+
+ TimeZone timeZone = null;
+
+ outerLoop: for (int i = startPos; i < length; i++)
+ {
+ final char c = value.charAt(i);
+ switch (c)
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ fractionBuffer.append(c);
+ break;
+
+ case 'Z':
+ // This is only acceptable if we're at the end of the value.
+ if (i != value.length() - 1)
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_FRACTION_CHAR
+ .get(value, String.valueOf(c));
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "finishDecodingFraction", e);
+ throw e;
+ }
+
+ timeZone = TIME_ZONE_UTC_OBJ;
+ break outerLoop;
+
+ case '+':
+ case '-':
+ timeZone = getTimeZoneForOffset(value, i);
+ break outerLoop;
+
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_FRACTION_CHAR
+ .get(value, String.valueOf(c));
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "finishDecodingFraction", e);
+ throw e;
+ }
+ }
+
+ if (fractionBuffer.length() == 2)
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_EMPTY_FRACTION.get(value);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "finishDecodingFraction", e);
+ throw e;
+ }
+
+ if (timeZone == null)
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_NO_TIME_ZONE_INFO
+ .get(value);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "finishDecodingFraction", e);
+ throw e;
+ }
+
+ final Double fractionValue =
+ Double.parseDouble(fractionBuffer.toString());
+ final long additionalMilliseconds =
+ Math.round(fractionValue * multiplier);
+
+ try
+ {
+ final GregorianCalendar calendar = new GregorianCalendar();
+ calendar.setLenient(false);
+ calendar.setTimeZone(timeZone);
+ calendar.set(year, month, day, hour, minute, second);
+ calendar.set(Calendar.MILLISECOND, 0);
+ return calendar.getTimeInMillis() + additionalMilliseconds;
+ }
+ catch (final Exception e)
+ {
+
+ // This should only happen if the provided date wasn't legal
+ // (e.g., September 31).
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME.get(value,
+ String.valueOf(e));
+ final DecodeException de = DecodeException.error(message, e);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "valueIsAcceptable", de);
+ throw de;
+ }
+ }
+
+
+
+ /**
+ * Decodes a time zone offset from the provided value.
+ *
+ * @param value
+ * The whole value, including the offset.
+ * @param startPos
+ * The position of the first character that is contained in
+ * the offset. This should be the position of the plus or
+ * minus character.
+ * @return The {@code TimeZone} object representing the decoded time
+ * zone.
+ * @throws DecodeException
+ * If the provided value does not contain a valid offset.
+ */
+ private static TimeZone getTimeZoneForOffset(String value,
+ int startPos) throws DecodeException
+ {
+ final String offSetStr = value.substring(startPos);
+ if (offSetStr.length() != 3 && offSetStr.length() != 5)
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET.get(value,
+ offSetStr);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "getTimeZoneForOffset", e);
+ throw e;
+ }
+
+ // The first character must be either a plus or minus.
+ switch (offSetStr.charAt(0))
+ {
+ case '+':
+ case '-':
+ // These are OK.
+ break;
+
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET.get(value,
+ offSetStr);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "getTimeZoneForOffset", e);
+ throw e;
+ }
+
+ // The first two characters must be an integer between 00 and 23.
+ switch (offSetStr.charAt(1))
+ {
+ case '0':
+ case '1':
+ switch (offSetStr.charAt(2))
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ // These are all fine.
+ break;
+
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET.get(value,
+ offSetStr);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "getTimeZoneForOffset", e);
+ throw e;
+ }
+ break;
+
+ case '2':
+ switch (offSetStr.charAt(2))
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ // These are all fine.
+ break;
+
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET.get(value,
+ offSetStr);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "getTimeZoneForOffset", e);
+ throw e;
+ }
+ break;
+
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET.get(value,
+ offSetStr);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "getTimeZoneForOffset", e);
+ throw e;
+ }
+
+ // If there are two more characters, then they must be an integer
+ // between 00 and 59.
+ if (offSetStr.length() == 5)
+ {
+ switch (offSetStr.charAt(3))
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ switch (offSetStr.charAt(4))
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ // These are all fine.
+ break;
+
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET.get(
+ value, offSetStr);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "getTimeZoneForOffset", e);
+ throw e;
+ }
+ break;
+
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET.get(value,
+ offSetStr);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+ "getTimeZoneForOffset", e);
+ throw e;
+ }
+ }
+
+ // If we've gotten here, then it looks like a valid offset. We can
+ // create a time zone by using "GMT" followed by the offset.
+ return TimeZone.getTimeZone("GMT" + offSetStr);
+ }
+
+
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_GENERALIZED_TIME_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_GENERALIZED_TIME_NAME;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_GENERALIZED_TIME_OID;
+ }
+
+
+
+ @Override
+ public String getSubstringMatchingRule()
+ {
+ return SMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ try
+ {
+ decodeGeneralizedTimeValue(value);
+ return true;
+ }
+ catch (final DecodeException de)
+ {
+ invalidReason.append(de.getMessageObject());
+ return false;
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/GenerateCoreSchema.java b/sdk/src/org/opends/sdk/schema/GenerateCoreSchema.java
new file mode 100644
index 0000000..2928bce
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/GenerateCoreSchema.java
@@ -0,0 +1,415 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import java.util.*;
+
+
+
+/**
+ * Tool for generating CoreSchema.java.
+ */
+final class GenerateCoreSchema
+{
+ private static boolean isOpenDSOID(String oid)
+ {
+ return oid.startsWith(SchemaConstants.OID_OPENDS_SERVER_BASE + ".");
+ }
+
+ private static final Set<String> ABBREVIATIONS =
+ new HashSet<String>(Arrays.asList("LDAP", "DN", "DIT", "RDN",
+ "JPEG", "OID", "UUID", "IA5", "UID", "UTC", "X500", "X121",
+ "C", "CN", "O", "OU", "L", "DC", "ISDN", "SN", "ST"));
+
+
+
+ private static String splitNameIntoWords(String name)
+ {
+ String splitName = name.replaceAll("([A-Z][a-z])", "_$1");
+ splitName = splitName.replaceAll("([a-z])([A-Z])", "$1_$2");
+
+ return splitName.toUpperCase(Locale.ENGLISH);
+ }
+
+
+
+ private static String toJavaName(String splitName)
+ {
+ StringBuilder builder = new StringBuilder();
+ for (String word : splitName.split("_"))
+ {
+ if (ABBREVIATIONS.contains(word))
+ {
+ builder.append(word);
+ }
+ else
+ {
+ builder.append(word.charAt(0));
+ if (word.length() > 1)
+ {
+ builder.append(word.substring(1).toLowerCase(Locale.ENGLISH));
+ }
+ }
+ }
+ return builder.toString();
+ }
+
+
+
+ private static void testSplitNameIntoWords()
+ {
+ String[][] values =
+ new String[][] { { "oneTwoThree", "ONE_TWO_THREE" },
+ { "oneTWOThree", "ONE_TWO_THREE" },
+ { "oneX500Three", "ONE_X500_THREE" },
+ { "oneTwoX500", "ONE_TWO_X500" },
+ { "oneTwoX500", "ONE_TWO_X500" },
+ { "x500TwoThree", "X500_TWO_THREE" }, };
+
+ for (String[] test : values)
+ {
+ String actual = splitNameIntoWords(test[0]);
+ String expected = test[1];
+ if (!actual.equals(expected))
+ {
+ System.out.println("Test Split Failure: " + test[0] + " -> "
+ + actual + " != " + expected);
+ }
+ }
+ }
+
+
+
+ /**
+ * Tool for generating CoreSchema.java.
+ *
+ * @param args
+ * The command line arguments (none required).
+ */
+ public static void main(String[] args)
+ {
+ testSplitNameIntoWords();
+
+ Schema schema = Schema.getCoreSchema();
+
+ SortedMap<String, Syntax> syntaxes = new TreeMap<String, Syntax>();
+ for (Syntax syntax : schema.getSyntaxes())
+ {
+ if (isOpenDSOID(syntax.getOID()))
+ {
+ continue;
+ }
+
+ String name = syntax.getDescription().replaceAll(" Syntax$", "");
+ String fieldName =
+ name.replace(" ", "_").toUpperCase(Locale.ENGLISH).concat(
+ "_SYNTAX");
+ syntaxes.put(fieldName, syntax);
+ }
+
+ SortedMap<String, MatchingRule> matchingRules =
+ new TreeMap<String, MatchingRule>();
+ for (MatchingRule matchingRule : schema.getMatchingRules())
+ {
+ if (isOpenDSOID(matchingRule.getOID()))
+ {
+ continue;
+ }
+
+ String name =
+ matchingRule.getNameOrOID().replaceAll("Match$", "");
+ String fieldName =
+ splitNameIntoWords(name).concat("_MATCHING_RULE");
+ matchingRules.put(fieldName, matchingRule);
+ }
+
+ SortedMap<String, AttributeType> attributeTypes =
+ new TreeMap<String, AttributeType>();
+ for (AttributeType attributeType : schema.getAttributeTypes())
+ {
+ if (isOpenDSOID(attributeType.getOID()))
+ {
+ continue;
+ }
+ String name = attributeType.getNameOrOID();
+ String fieldName =
+ splitNameIntoWords(name).concat("_ATTRIBUTE_TYPE");
+ attributeTypes.put(fieldName, attributeType);
+ }
+
+ SortedMap<String, ObjectClass> objectClasses =
+ new TreeMap<String, ObjectClass>();
+ for (ObjectClass objectClass : schema.getObjectClasses())
+ {
+ if (isOpenDSOID(objectClass.getOID()))
+ {
+ continue;
+ }
+ String name = objectClass.getNameOrOID();
+ String fieldName =
+ splitNameIntoWords(name).concat("_OBJECT_CLASS");
+
+ objectClasses.put(fieldName, objectClass);
+ }
+
+ System.out.println();
+ System.out.println();
+ System.out.println();
+ System.out.println("package org.opends.sdk.schema;");
+ System.out.println();
+ System.out.println();
+ System.out.println();
+ System.out.println("/**");
+ System.out
+ .println(" * The OpenDS SDK core schema contains standard LDAP RFC schema elements. These include:");
+ System.out.println(" * <ul>");
+ System.out
+ .println(" * <li><a href=\"http://tools.ietf.org/html/rfc4512\">RFC 4512 -");
+ System.out
+ .println(" * Lightweight Directory Access Protocol (LDAP): Directory Information");
+ System.out.println(" * Models </a>");
+ System.out
+ .println(" * <li><a href=\"http://tools.ietf.org/html/rfc4517\">RFC 4517 -");
+ System.out
+ .println(" * Lightweight Directory Access Protocol (LDAP): Syntaxes and Matching");
+ System.out.println(" * Rules </a>");
+ System.out
+ .println(" * <li><a href=\"http://tools.ietf.org/html/rfc4519\">RFC 4519 -");
+ System.out
+ .println(" * Lightweight Directory Access Protocol (LDAP): Schema for User");
+ System.out.println(" * Applications </a>");
+ System.out
+ .println(" * <li><a href=\"http://tools.ietf.org/html/rfc4530\">RFC 4530 -");
+ System.out
+ .println(" * Lightweight Directory Access Protocol (LDAP): entryUUID Operational");
+ System.out.println(" * Attribute </a>");
+ System.out
+ .println(" * <li><a href=\"http://tools.ietf.org/html/rfc3045\">RFC 3045 - Storing");
+ System.out
+ .println(" * Vendor Information in the LDAP Root DSE </a>");
+ System.out
+ .println(" * <li><a href=\"http://tools.ietf.org/html/rfc3112\">RFC 3112 - LDAP");
+ System.out.println(" * Authentication Password Schema </a>");
+ System.out.println(" * </ul>");
+ System.out.println(" * <p>");
+ System.out
+ .println(" * The core schema is non-strict: attempts to retrieve");
+ System.out
+ .println(" * non-existent Attribute Types will return a temporary");
+ System.out
+ .println(" * Attribute Type having the Octet String syntax.");
+ System.out.println(" */");
+ System.out.println("public final class CoreSchema");
+ System.out.println("{");
+
+ System.out.println(" // Core Syntaxes");
+ for (Map.Entry<String, Syntax> syntax : syntaxes.entrySet())
+ {
+ System.out.println(" private static final Syntax "
+ + syntax.getKey() + " =");
+ System.out
+ .println(" CoreSchemaImpl.getInstance().getSyntax(\""
+ + syntax.getValue().getOID() + "\");");
+ }
+
+ System.out.println();
+ System.out.println(" // Core Matching Rules");
+ for (Map.Entry<String, MatchingRule> matchingRule : matchingRules
+ .entrySet())
+ {
+ System.out.println(" private static final MatchingRule "
+ + matchingRule.getKey() + " =");
+ System.out
+ .println(" CoreSchemaImpl.getInstance().getMatchingRule(\""
+ + matchingRule.getValue().getOID() + "\");");
+ }
+
+ System.out.println();
+ System.out.println(" // Core Attribute Types");
+ for (Map.Entry<String, AttributeType> attributeType : attributeTypes
+ .entrySet())
+ {
+ System.out.println(" private static final AttributeType "
+ + attributeType.getKey() + " =");
+ System.out
+ .println(" CoreSchemaImpl.getInstance().getAttributeType(\""
+ + attributeType.getValue().getOID() + "\");");
+ }
+
+ System.out.println();
+ System.out.println(" // Core Object Classes");
+ for (Map.Entry<String, ObjectClass> objectClass : objectClasses
+ .entrySet())
+ {
+ System.out.println(" private static final ObjectClass "
+ + objectClass.getKey() + " =");
+ System.out
+ .println(" CoreSchemaImpl.getInstance().getObjectClass(\""
+ + objectClass.getValue().getOID() + "\");");
+ }
+
+ System.out.println();
+ System.out.println();
+ System.out.println();
+ System.out.println(" // Prevent instantiation");
+ System.out.println(" private CoreSchema()");
+ System.out.println(" {");
+ System.out.println(" // Nothing to do.");
+ System.out.println(" }");
+
+ System.out.println();
+ System.out.println();
+ System.out.println();
+ System.out.println(" /**");
+ System.out
+ .println(" * Returns a reference to the singleton core schema.");
+ System.out.println(" *");
+ System.out.println(" * @return The core schema.");
+ System.out.println(" */");
+ System.out.println(" public static Schema getInstance()");
+ System.out.println(" {");
+ System.out.println(" return CoreSchemaImpl.getInstance();");
+ System.out.println(" }");
+
+ for (Map.Entry<String, Syntax> syntax : syntaxes.entrySet())
+ {
+ System.out.println();
+ System.out.println();
+ System.out.println();
+
+ String description =
+ toCodeJavaDoc(syntax.getValue().getDescription().replaceAll(
+ " Syntax$", "")
+ + " Syntax");
+ System.out.println(" /**");
+ System.out.println(" * Returns a reference to the "
+ + description);
+ System.out.println(" * which has the OID "
+ + toCodeJavaDoc(syntax.getValue().getOID()) + ".");
+ System.out.println(" *");
+ System.out.println(" * @return A reference to the "
+ + description + ".");
+
+ System.out.println(" */");
+ System.out.println(" public static Syntax get"
+ + toJavaName(syntax.getKey()) + "()");
+ System.out.println(" {");
+ System.out.println(" return " + syntax.getKey() + ";");
+ System.out.println(" }");
+ }
+
+ for (Map.Entry<String, MatchingRule> matchingRule : matchingRules
+ .entrySet())
+ {
+ System.out.println();
+ System.out.println();
+ System.out.println();
+
+ String description =
+ toCodeJavaDoc(matchingRule.getValue().getNameOrOID());
+ System.out.println(" /**");
+ System.out.println(" * Returns a reference to the "
+ + description + " Matching Rule");
+ System.out.println(" * which has the OID "
+ + toCodeJavaDoc(matchingRule.getValue().getOID()) + ".");
+ System.out.println(" *");
+ System.out.println(" * @return A reference to the "
+ + description + " Matching Rule.");
+
+ System.out.println(" */");
+ System.out.println(" public static MatchingRule get"
+ + toJavaName(matchingRule.getKey()) + "()");
+ System.out.println(" {");
+ System.out.println(" return " + matchingRule.getKey() + ";");
+ System.out.println(" }");
+ }
+
+ for (Map.Entry<String, AttributeType> attributeType : attributeTypes
+ .entrySet())
+ {
+ System.out.println();
+ System.out.println();
+ System.out.println();
+
+ String description =
+ toCodeJavaDoc(attributeType.getValue().getNameOrOID());
+ System.out.println(" /**");
+ System.out.println(" * Returns a reference to the "
+ + description + " Attribute Type");
+ System.out.println(" * which has the OID "
+ + toCodeJavaDoc(attributeType.getValue().getOID()) + ".");
+ System.out.println(" *");
+ System.out.println(" * @return A reference to the "
+ + description + " Attribute Type.");
+
+ System.out.println(" */");
+ System.out.println(" public static AttributeType get"
+ + toJavaName(attributeType.getKey()) + "()");
+ System.out.println(" {");
+ System.out.println(" return " + attributeType.getKey() + ";");
+ System.out.println(" }");
+ }
+
+ for (Map.Entry<String, ObjectClass> objectClass : objectClasses
+ .entrySet())
+ {
+ System.out.println();
+ System.out.println();
+ System.out.println();
+
+ String description =
+ toCodeJavaDoc(objectClass.getValue().getNameOrOID());
+ System.out.println(" /**");
+ System.out.println(" * Returns a reference to the "
+ + description + " Object Class");
+ System.out.println(" * which has the OID "
+ + toCodeJavaDoc(objectClass.getValue().getOID()) + ".");
+ System.out.println(" *");
+ System.out.println(" * @return A reference to the "
+ + description + " Object Class.");
+
+ System.out.println(" */");
+ System.out.println(" public static ObjectClass get"
+ + toJavaName(objectClass.getKey()) + "()");
+ System.out.println(" {");
+ System.out.println(" return " + objectClass.getKey() + ";");
+ System.out.println(" }");
+ }
+
+ System.out.println("}");
+ }
+
+
+
+ private static String toCodeJavaDoc(String text)
+ {
+ return String.format("{@code %s}", text);
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/GuideSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/GuideSyntaxImpl.java
new file mode 100644
index 0000000..497e4a2
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/GuideSyntaxImpl.java
@@ -0,0 +1,430 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.schema.SchemaConstants.EMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_GUIDE_NAME;
+import static org.opends.sdk.util.StaticUtils.toLowerCase;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class implements the guide attribute syntax, which may be used
+ * to provide criteria for generating search filters for entries,
+ * optionally tied to a specified objectclass.
+ */
+final class GuideSyntaxImpl extends AbstractSyntaxImpl
+{
+ /**
+ * Determines whether the provided string represents a valid criteria
+ * according to the guide syntax.
+ *
+ * @param criteria
+ * The portion of the criteria for which to make the
+ * determination.
+ * @param valueStr
+ * The complete guide value provided by the client.
+ * @param invalidReason
+ * The buffer to which to append the reason that the criteria
+ * is invalid if a problem is found.
+ * @return <CODE>true</CODE> if the provided string does contain a
+ * valid criteria, or <CODE>false</CODE> if not.
+ */
+ static boolean criteriaIsValid(String criteria, String valueStr,
+ MessageBuilder invalidReason)
+ {
+ // See if the criteria starts with a '!'. If so, then just evaluate
+ // everything after that as a criteria.
+ char c = criteria.charAt(0);
+ if (c == '!')
+ {
+ return criteriaIsValid(criteria.substring(1), valueStr,
+ invalidReason);
+ }
+
+ // See if the criteria starts with a '('. If so, then find the
+ // corresponding ')' and parse what's in between as a criteria.
+ if (c == '(')
+ {
+ final int length = criteria.length();
+ int depth = 1;
+
+ for (int i = 1; i < length; i++)
+ {
+ c = criteria.charAt(i);
+ if (c == ')')
+ {
+ depth--;
+ if (depth == 0)
+ {
+ final String subCriteria = criteria.substring(1, i);
+ if (!criteriaIsValid(subCriteria, valueStr, invalidReason))
+ {
+ return false;
+ }
+
+ // If we are at the end of the value, then it was valid.
+ // Otherwise, the next character must be a pipe or an
+ // ampersand followed by another set of criteria.
+ if (i == length - 1)
+ {
+ return true;
+ }
+ else
+ {
+ c = criteria.charAt(i + 1);
+ if (c == '|' || c == '&')
+ {
+ return criteriaIsValid(criteria.substring(i + 2),
+ valueStr, invalidReason);
+ }
+ else
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_ILLEGAL_CHAR
+ .get(valueStr, criteria, c, (i + 1)));
+ return false;
+ }
+ }
+ }
+ }
+ else if (c == '(')
+ {
+ depth++;
+ }
+ }
+
+ // If we've gotten here, then we went through the entire value
+ // without finding the appropriate closing parenthesis.
+
+ invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_MISSING_CLOSE_PAREN
+ .get(valueStr, criteria));
+ return false;
+ }
+
+ // See if the criteria starts with a '?'. If so, then it must be
+ // either "?true" or "?false".
+ if (c == '?')
+ {
+ if (criteria.startsWith("?true"))
+ {
+ if (criteria.length() == 5)
+ {
+ return true;
+ }
+ else
+ {
+ // The only characters allowed next are a pipe or an
+ // ampersand.
+ c = criteria.charAt(5);
+ if (c == '|' || c == '&')
+ {
+ return criteriaIsValid(criteria.substring(6), valueStr,
+ invalidReason);
+ }
+ else
+ {
+ invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_ILLEGAL_CHAR
+ .get(valueStr, criteria, c, 5));
+ return false;
+ }
+ }
+ }
+ else if (criteria.startsWith("?false"))
+ {
+ if (criteria.length() == 6)
+ {
+ return true;
+ }
+ else
+ {
+ // The only characters allowed next are a pipe or an
+ // ampersand.
+ c = criteria.charAt(6);
+ if (c == '|' || c == '&')
+ {
+ return criteriaIsValid(criteria.substring(7), valueStr,
+ invalidReason);
+ }
+ else
+ {
+ invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_ILLEGAL_CHAR
+ .get(valueStr, criteria, c, 6));
+ return false;
+ }
+ }
+ }
+ else
+ {
+ invalidReason
+ .append(ERR_ATTR_SYNTAX_GUIDE_INVALID_QUESTION_MARK.get(
+ valueStr, criteria));
+ return false;
+ }
+ }
+
+ // See if the criteria is either "true" or "false". If so, then it
+ // is valid.
+ if (criteria.equals("true") || criteria.equals("false"))
+ {
+ return true;
+ }
+
+ // The only thing that will be allowed is an attribute type name or
+ // OID followed by a dollar sign and a match type. Find the dollar
+ // sign and verify whether the value before it is a valid attribute
+ // type name or OID.
+ final int dollarPos = criteria.indexOf('$');
+ if (dollarPos < 0)
+ {
+ invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_NO_DOLLAR.get(
+ valueStr, criteria));
+ return false;
+ }
+ else if (dollarPos == 0)
+ {
+ invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_NO_ATTR.get(valueStr,
+ criteria));
+ return false;
+ }
+ else if (dollarPos == criteria.length() - 1)
+ {
+ invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_NO_MATCH_TYPE.get(
+ valueStr, criteria));
+ return false;
+ }
+ else
+ {
+ try
+ {
+ SchemaUtils.readOID(new SubstringReader(criteria.substring(0,
+ dollarPos)));
+ }
+ catch (final DecodeException de)
+ {
+ invalidReason.append(de.getMessageObject());
+ return false;
+ }
+ }
+
+ // The substring immediately after the dollar sign must be one of
+ // "eq", "substr", "ge", "le", or "approx". It may be followed by
+ // the end of the value, a pipe, or an ampersand.
+ int endPos;
+ c = criteria.charAt(dollarPos + 1);
+ switch (c)
+ {
+ case 'e':
+ if (criteria.startsWith("eq", dollarPos + 1))
+ {
+ endPos = dollarPos + 3;
+ break;
+ }
+ else
+ {
+ invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE
+ .get(valueStr, criteria, dollarPos + 1));
+ return false;
+ }
+
+ case 's':
+ if (criteria.startsWith("substr", dollarPos + 1))
+ {
+ endPos = dollarPos + 7;
+ break;
+ }
+ else
+ {
+ invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE
+ .get(valueStr, criteria, dollarPos + 1));
+ return false;
+ }
+
+ case 'g':
+ if (criteria.startsWith("ge", dollarPos + 1))
+ {
+ endPos = dollarPos + 3;
+ break;
+ }
+ else
+ {
+ invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE
+ .get(valueStr, criteria, dollarPos + 1));
+ return false;
+ }
+
+ case 'l':
+ if (criteria.startsWith("le", dollarPos + 1))
+ {
+ endPos = dollarPos + 3;
+ break;
+ }
+ else
+ {
+ invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE
+ .get(valueStr, criteria, dollarPos + 1));
+ return false;
+ }
+
+ case 'a':
+ if (criteria.startsWith("approx", dollarPos + 1))
+ {
+ endPos = dollarPos + 7;
+ break;
+ }
+ else
+ {
+ invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE
+ .get(valueStr, criteria, dollarPos + 1));
+ return false;
+ }
+
+ default:
+ invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE
+ .get(valueStr, criteria, dollarPos + 1));
+ return false;
+ }
+
+ // See if we are at the end of the value. If so, then it is valid.
+ // Otherwise, the next character must be a pipe or an ampersand.
+ if (endPos >= criteria.length())
+ {
+ return true;
+ }
+ else
+ {
+ c = criteria.charAt(endPos);
+ if (c == '|' || c == '&')
+ {
+ return criteriaIsValid(criteria.substring(endPos + 1),
+ valueStr, invalidReason);
+ }
+ else
+ {
+ invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_ILLEGAL_CHAR.get(
+ valueStr, criteria, c, endPos));
+ return false;
+ }
+ }
+ }
+
+
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_OCTET_STRING_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_GUIDE_NAME;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_OCTET_STRING_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // Get a lowercase string version of the provided value.
+ final String valueStr = toLowerCase(value.toString());
+
+ // Find the position of the octothorpe. If there isn't one, then the
+ // entire value should be the criteria.
+ final int sharpPos = valueStr.indexOf('#');
+ if (sharpPos < 0)
+ {
+ return criteriaIsValid(valueStr, valueStr, invalidReason);
+ }
+
+ // Get the objectclass and see if it is a valid name or OID.
+ final String ocName = valueStr.substring(0, sharpPos).trim();
+ final int ocLength = ocName.length();
+ if (ocLength == 0)
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_NO_OC.get(valueStr));
+ return false;
+ }
+
+ try
+ {
+ SchemaUtils.readOID(new SubstringReader(ocName.substring(0,
+ ocLength)));
+ }
+ catch (final DecodeException de)
+ {
+ invalidReason.append(de.getMessageObject());
+ return false;
+ }
+
+ // The rest of the value must be the criteria.
+ return criteriaIsValid(valueStr.substring(sharpPos + 1), valueStr,
+ invalidReason);
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/IA5StringSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/IA5StringSyntaxImpl.java
new file mode 100644
index 0000000..e0086f0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/IA5StringSyntaxImpl.java
@@ -0,0 +1,131 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_IA5_ILLEGAL_CHARACTER;
+import static org.opends.sdk.schema.SchemaConstants.*;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the IA5 string attribute syntax, which is
+ * simply a set of ASCII characters. By default, they will be treated in
+ * a case-insensitive manner, and equality, ordering, substring, and
+ * approximate matching will be allowed.
+ */
+final class IA5StringSyntaxImpl extends AbstractSyntaxImpl
+{
+ @Override
+ public String getApproximateMatchingRule()
+ {
+ return AMR_DOUBLE_METAPHONE_OID;
+ }
+
+
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_IA5_STRING_NAME;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_CASE_IGNORE_OID;
+ }
+
+
+
+ @Override
+ public String getSubstringMatchingRule()
+ {
+ return SMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // We will allow any value that does not contain any non-ASCII
+ // characters. Empty values are acceptable as well.
+ byte b;
+ for (int i = 0; i < value.length(); i++)
+ {
+ b = value.byteAt(i);
+ if ((b & 0x7F) != b)
+ {
+
+ final Message message =
+ WARN_ATTR_SYNTAX_IA5_ILLEGAL_CHARACTER.get(
+ value.toString(), String.valueOf(b));
+ invalidReason.append(message);
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/IntegerEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/IntegerEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..f408ddc
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/IntegerEqualityMatchingRuleImpl.java
@@ -0,0 +1,66 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_ILLEGAL_INTEGER;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * This class defines the integerMatch matching rule defined in X.520
+ * and referenced in RFC 2252.
+ */
+final class IntegerEqualityMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value) throws DecodeException
+ {
+ try
+ {
+ return ByteString.valueOf(Integer.parseInt(value.toString()));
+ }
+ catch (final Exception e)
+ {
+ StaticUtils.DEBUG_LOG.throwing("IntegerEqualityMatchingRule",
+ "normalizeAttributeValue", e);
+
+ final Message message =
+ WARN_ATTR_SYNTAX_ILLEGAL_INTEGER.get(value.toString());
+ throw DecodeException.error(message);
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/IntegerFirstComponentEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/IntegerFirstComponentEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..de646b0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/IntegerFirstComponentEqualityMatchingRuleImpl.java
@@ -0,0 +1,131 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_EMPTY_VALUE;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_EXPECTED_OPEN_PARENTHESIS;
+import static org.opends.messages.SchemaMessages.ERR_EMR_INTFIRSTCOMP_FIRST_COMPONENT_NOT_INT;
+
+import org.opends.messages.Message;
+import org.opends.sdk.Assertion;
+import org.opends.sdk.ConditionResult;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class implements the integerFirstComponentMatch matching rule
+ * defined in X.520 and referenced in RFC 2252. This rule is intended
+ * for use with attributes whose values contain a set of parentheses
+ * enclosing a space-delimited set of names and/or name-value pairs
+ * (like attribute type or objectclass descriptions) in which the
+ * "first component" is the first item after the opening parenthesis.
+ */
+final class IntegerFirstComponentEqualityMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+
+ @Override
+ public Assertion getAssertion(Schema schema, ByteSequence value)
+ throws DecodeException
+ {
+ try
+ {
+ final String definition = value.toString();
+ final SubstringReader reader = new SubstringReader(definition);
+ final int intValue = SchemaUtils.readRuleID(reader);
+
+ return new Assertion()
+ {
+ public ConditionResult matches(ByteSequence attributeValue)
+ {
+ final int actualIntValue =
+ attributeValue.toByteString().toInt();
+ return intValue == actualIntValue ? ConditionResult.TRUE
+ : ConditionResult.FALSE;
+ }
+ };
+ }
+ catch (final Exception e)
+ {
+ StaticUtils.DEBUG_LOG.throwing(
+ "IntegerFirstComponentEqualityMatchingRule", "getAssertion",
+ e);
+
+ final Message message =
+ ERR_EMR_INTFIRSTCOMP_FIRST_COMPONENT_NOT_INT.get(value
+ .toString());
+ throw DecodeException.error(message);
+ }
+
+ }
+
+
+
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value) throws DecodeException
+ {
+ final String definition = value.toString();
+ final SubstringReader reader = new SubstringReader(definition);
+
+ // We'll do this a character at a time. First, skip over any leading
+ // whitespace.
+ reader.skipWhitespaces();
+
+ if (reader.remaining() <= 0)
+ {
+ // This means that the value was empty or contained only
+ // whitespace. That is illegal.
+ final Message message = ERR_ATTR_SYNTAX_EMPTY_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ // The next character must be an open parenthesis. If it is not,
+ // then that is an error.
+ final char c = reader.read();
+ if (c != '(')
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_EXPECTED_OPEN_PARENTHESIS.get(definition,
+ (reader.pos() - 1), String.valueOf(c));
+ throw DecodeException.error(message);
+ }
+
+ // Skip over any spaces immediately following the opening
+ // parenthesis.
+ reader.skipWhitespaces();
+
+ // The next set of characters must be the OID.
+ return ByteString.valueOf(SchemaUtils.readRuleID(reader));
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/IntegerOrderingMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/IntegerOrderingMatchingRuleImpl.java
new file mode 100644
index 0000000..9f630f0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/IntegerOrderingMatchingRuleImpl.java
@@ -0,0 +1,66 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_ILLEGAL_INTEGER;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * This class defines the integerOrderingMatch matching rule defined in
+ * X.520 and referenced in RFC 4519.
+ */
+final class IntegerOrderingMatchingRuleImpl extends
+ AbstractOrderingMatchingRuleImpl
+{
+
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value) throws DecodeException
+ {
+ try
+ {
+ return ByteString.valueOf(Integer.parseInt(value.toString()));
+ }
+ catch (final Exception e)
+ {
+ StaticUtils.DEBUG_LOG.throwing("IntegerOrderingMatchingRule",
+ "normalizeAttributeValue", e);
+
+ final Message message =
+ WARN_ATTR_SYNTAX_ILLEGAL_INTEGER.get(value.toString());
+ throw DecodeException.error(message);
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/IntegerSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/IntegerSyntaxImpl.java
new file mode 100644
index 0000000..e95441d
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/IntegerSyntaxImpl.java
@@ -0,0 +1,228 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_INTEGER_DASH_NEEDS_VALUE;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_INTEGER_EMPTY_VALUE;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_INTEGER_INITIAL_ZERO;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_INTEGER_INVALID_CHARACTER;
+import static org.opends.sdk.schema.SchemaConstants.EMR_INTEGER_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_INTEGER_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_EXACT_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_INTEGER_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class defines the integer attribute syntax, which holds an
+ * arbitrarily-long integer value. Equality, ordering, and substring
+ * matching will be allowed by default.
+ */
+final class IntegerSyntaxImpl extends AbstractSyntaxImpl
+{
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_INTEGER_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_INTEGER_NAME;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_INTEGER_OID;
+ }
+
+
+
+ @Override
+ public String getSubstringMatchingRule()
+ {
+ return SMR_CASE_EXACT_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ final String valueString = value.toString();
+ final int length = valueString.length();
+
+ if (length == 0)
+ {
+ invalidReason.append(WARN_ATTR_SYNTAX_INTEGER_EMPTY_VALUE
+ .get(valueString));
+ return false;
+ }
+ else if (length == 1)
+ {
+ switch (valueString.charAt(0))
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ return true;
+ case '-':
+ invalidReason.append(WARN_ATTR_SYNTAX_INTEGER_DASH_NEEDS_VALUE
+ .get(valueString));
+ return false;
+ default:
+ invalidReason.append(WARN_ATTR_SYNTAX_INTEGER_INVALID_CHARACTER
+ .get(valueString, valueString.charAt(0), 0));
+ return false;
+ }
+ }
+ else
+ {
+ boolean negative = false;
+
+ switch (valueString.charAt(0))
+ {
+ case '0':
+ invalidReason.append(WARN_ATTR_SYNTAX_INTEGER_INITIAL_ZERO
+ .get(valueString));
+ return false;
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ // These are all fine.
+ break;
+ case '-':
+ // This is fine too.
+ negative = true;
+ break;
+ default:
+ invalidReason.append(WARN_ATTR_SYNTAX_INTEGER_INVALID_CHARACTER
+ .get(valueString, valueString.charAt(0), 0));
+ return false;
+ }
+
+ switch (valueString.charAt(1))
+ {
+ case '0':
+ // This is fine as long as the value isn't negative.
+ if (negative)
+ {
+ invalidReason.append(WARN_ATTR_SYNTAX_INTEGER_INITIAL_ZERO
+ .get(valueString));
+ return false;
+ }
+ break;
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ // These are all fine.
+ break;
+ default:
+ invalidReason.append(WARN_ATTR_SYNTAX_INTEGER_INVALID_CHARACTER
+ .get(valueString, valueString.charAt(0), 0));
+ return false;
+ }
+
+ for (int i = 2; i < length; i++)
+ {
+ switch (valueString.charAt(i))
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ // These are all fine.
+ break;
+ default:
+ invalidReason
+ .append(WARN_ATTR_SYNTAX_INTEGER_INVALID_CHARACTER.get(
+ valueString, valueString.charAt(0), 0));
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/JPEGSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/JPEGSyntaxImpl.java
new file mode 100644
index 0000000..3b07283
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/JPEGSyntaxImpl.java
@@ -0,0 +1,99 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.EMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_JPEG_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the JPEG attribute syntax. This should be
+ * restricted to holding only JPEG image contents, but we will accept
+ * any set of bytes. It will be treated much like the octet string
+ * attribute syntax.
+ */
+final class JPEGSyntaxImpl extends AbstractSyntaxImpl
+{
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_OCTET_STRING_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_JPEG_NAME;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_OCTET_STRING_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // All values will be acceptable for the fax syntax.
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/KeywordEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/KeywordEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..1b4b4e3
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/KeywordEqualityMatchingRuleImpl.java
@@ -0,0 +1,184 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.Assertion;
+import org.opends.sdk.ConditionResult;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the keywordMatch matching rule defined in
+ * X.520. That document defines "keyword" as implementation-specific,
+ * but in this case we will consider it a match if the assertion value
+ * is contained within the attribute value and is bounded by the edge of
+ * the value or any of the following characters: <BR>
+ * <UL>
+ * <LI>A space</LI>
+ * <LI>A period</LI>
+ * <LI>A comma</LI>
+ * <LI>A slash</LI>
+ * <LI>A dollar sign</LI>
+ * <LI>A plus sign</LI>
+ * <LI>A dash</LI>
+ * <LI>An underscore</LI>
+ * <LI>An octothorpe</LI>
+ * <LI>An equal sign</LI>
+ * </UL>
+ */
+final class KeywordEqualityMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+ @Override
+ public Assertion getAssertion(Schema schema, ByteSequence value)
+ throws DecodeException
+ {
+ final String normalStr = normalize(value);
+
+ return new Assertion()
+ {
+ public ConditionResult matches(ByteSequence attributeValue)
+ {
+ // See if the assertion value is contained in the attribute
+ // value. If not, then it isn't a match.
+ final String valueStr1 = attributeValue.toString();
+
+ final int pos = valueStr1.indexOf(normalStr);
+ if (pos < 0)
+ {
+ return ConditionResult.FALSE;
+ }
+
+ if (pos > 0)
+ {
+ final char c = valueStr1.charAt(pos - 1);
+ switch (c)
+ {
+ case ' ':
+ case '.':
+ case ',':
+ case '/':
+ case '$':
+ case '+':
+ case '-':
+ case '_':
+ case '#':
+ case '=':
+ // These are all acceptable.
+ break;
+
+ default:
+ // Anything else is not.
+ return ConditionResult.FALSE;
+ }
+ }
+
+ if (valueStr1.length() > pos + normalStr.length())
+ {
+ final char c = valueStr1.charAt(pos + normalStr.length());
+ switch (c)
+ {
+ case ' ':
+ case '.':
+ case ',':
+ case '/':
+ case '$':
+ case '+':
+ case '-':
+ case '_':
+ case '#':
+ case '=':
+ // These are all acceptable.
+ break;
+
+ default:
+ // Anything else is not.
+ return ConditionResult.FALSE;
+ }
+ }
+
+ // If we've gotten here, then we can assume it is a match.
+ return ConditionResult.TRUE;
+ }
+ };
+ }
+
+
+
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value)
+ {
+ return ByteString.valueOf(normalize(value));
+ }
+
+
+
+ private String normalize(ByteSequence value)
+ {
+ final StringBuilder buffer = new StringBuilder();
+ prepareUnicode(buffer, value, TRIM, CASE_FOLD);
+
+ final int bufferLength = buffer.length();
+ if (bufferLength == 0)
+ {
+ if (value.length() > 0)
+ {
+ // This should only happen if the value is composed entirely of
+ // spaces. In that case, the normalized value is a single space.
+ return " ".intern();
+ }
+ else
+ {
+ // The value is empty, so it is already normalized.
+ return "".intern();
+ }
+ }
+
+ // Replace any consecutive spaces with a single space.
+ for (int pos = bufferLength - 1; pos > 0; pos--)
+ {
+ if (buffer.charAt(pos) == ' ')
+ {
+ if (buffer.charAt(pos - 1) == ' ')
+ {
+ buffer.delete(pos, pos + 1);
+ }
+ }
+ }
+
+ return buffer.toString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/LDAPSyntaxDescriptionSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/LDAPSyntaxDescriptionSyntaxImpl.java
new file mode 100644
index 0000000..bfe652c
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/LDAPSyntaxDescriptionSyntaxImpl.java
@@ -0,0 +1,236 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.schema.SchemaConstants.EMR_OID_FIRST_COMPONENT_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_LDAP_SYNTAX_NAME;
+
+import java.util.*;
+import java.util.regex.Pattern;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class defines the LDAP syntax description syntax, which is used
+ * to hold attribute syntax definitions in the schema. The format of
+ * this syntax is defined in RFC 2252.
+ */
+final class LDAPSyntaxDescriptionSyntaxImpl extends AbstractSyntaxImpl
+{
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_OID_FIRST_COMPONENT_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_LDAP_SYNTAX_NAME;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // We'll use the decodeNameForm method to determine if the value is
+ // acceptable.
+ try
+ {
+ final String definition = value.toString();
+ final SubstringReader reader = new SubstringReader(definition);
+
+ // We'll do this a character at a time. First, skip over any
+ // leading whitespace.
+ reader.skipWhitespaces();
+
+ if (reader.remaining() <= 0)
+ {
+ // This means that the value was empty or contained only
+ // whitespace. That is illegal.
+ final Message message =
+ ERR_ATTR_SYNTAX_ATTRSYNTAX_EMPTY_VALUE.get();
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("LDAPSyntaxDescriptionSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ // The next character must be an open parenthesis. If it is not,
+ // then that is an error.
+ final char c = reader.read();
+ if (c != '(')
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_ATTRSYNTAX_EXPECTED_OPEN_PARENTHESIS.get(
+ definition, (reader.pos() - 1), String.valueOf(c));
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("LDAPSyntaxDescriptionSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ // Skip over any spaces immediately following the opening
+ // parenthesis.
+ reader.skipWhitespaces();
+
+ // The next set of characters must be the OID.
+ final String oid = SchemaUtils.readOID(reader);
+
+ Map<String, List<String>> extraProperties =
+ Collections.emptyMap();
+ // At this point, we should have a pretty specific syntax that
+ // describes what may come next, but some of the components are
+ // optional and it would be pretty easy to put something in the
+ // wrong order, so we will be very flexible about what we can
+ // accept. Just look at the next token, figure out what it is and
+ // how to treat what comes after it, then repeat until we get to
+ // the end of the value. But before we start, set default values
+ // for everything else we might need to know.
+ while (true)
+ {
+ final String tokenName = SchemaUtils.readTokenName(reader);
+
+ if (tokenName == null)
+ {
+ // No more tokens.
+ break;
+ }
+ else if (tokenName.equalsIgnoreCase("desc"))
+ {
+ // This specifies the description for the syntax. It is an
+ // arbitrary string of characters enclosed in single quotes.
+ SchemaUtils.readQuotedString(reader);
+ }
+ else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+ {
+ // This must be a non-standard property and it must be
+ // followed by either a single definition in single quotes or
+ // an open parenthesis followed by one or more values in
+ // single quotes separated by spaces followed by a close
+ // parenthesis.
+ if (extraProperties.isEmpty())
+ {
+ extraProperties = new HashMap<String, List<String>>();
+ }
+ extraProperties.put(tokenName, SchemaUtils
+ .readExtensions(reader));
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("LDAPSyntaxDescriptionSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+ }
+
+ for (final Map.Entry<String, List<String>> property : extraProperties
+ .entrySet())
+ {
+ if (property.getKey().equalsIgnoreCase("x-pattern"))
+ {
+ final Iterator<String> values =
+ property.getValue().iterator();
+ if (values.hasNext())
+ {
+ final String pattern = values.next();
+ try
+ {
+ Pattern.compile(values.next());
+ }
+ catch (final Exception e)
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_LDAPSYNTAX_REGEX_INVALID_PATTERN
+ .get(oid, pattern);
+ final DecodeException de =
+ DecodeException.error(message, e);
+ StaticUtils.DEBUG_LOG.throwing(
+ "LDAPSyntaxDescriptionSyntax", "valueIsAcceptable",
+ de);
+ throw de;
+ }
+ break;
+ }
+ }
+ else if (property.getKey().equalsIgnoreCase("x-enum"))
+ {
+ final List<String> values = property.getValue();
+ for (int i = 0; i < values.size() - 1; i++)
+ {
+ final String entry = values.get(i);
+ for (int j = i + 1; j < values.size(); j++)
+ {
+ if (entry.equals(values.get(j)))
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_LDAPSYNTAX_ENUM_DUPLICATE_VALUE
+ .get(oid, entry, j);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing(
+ "LDAPSyntaxDescriptionSyntax", "valueIsAcceptable",
+ e);
+ throw e;
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+ catch (final DecodeException de)
+ {
+ invalidReason.append(de.getMessageObject());
+ return false;
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/MatchingRule.java b/sdk/src/org/opends/sdk/schema/MatchingRule.java
new file mode 100644
index 0000000..1cedd1c
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/MatchingRule.java
@@ -0,0 +1,457 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_MR_UNKNOWN_SYNTAX;
+import static org.opends.messages.SchemaMessages.WARN_MATCHING_RULE_NOT_IMPLEMENTED;
+
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.opends.messages.Message;
+import org.opends.sdk.Assertion;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class defines a data structure for storing and interacting with
+ * matching rules, which are used by servers to compare attribute values
+ * against assertion values when performing Search and Compare
+ * operations. They are also used to identify the value to be added or
+ * deleted when modifying entries, and are used when comparing a
+ * purported distinguished name with the name of an entry.
+ * <p>
+ * Matching rule implementations must extend the
+ * <code>MatchingRuleImplementation</code> class so they can be used by
+ * OpenDS.
+ * <p>
+ * Where ordered sets of names, or extra properties are provided, the
+ * ordering will be preserved when the associated fields are accessed
+ * via their getters or via the {@link #toString()} methods.
+ */
+public final class MatchingRule extends SchemaElement
+{
+ private final String oid;
+ private final List<String> names;
+ private final boolean isObsolete;
+ private final String syntaxOID;
+ private final String definition;
+ private MatchingRuleImpl impl;
+ private Syntax syntax;
+ private Schema schema;
+
+
+
+ MatchingRule(String oid, List<String> names, String description,
+ boolean obsolete, String syntax,
+ Map<String, List<String>> extraProperties, String definition,
+ MatchingRuleImpl implementation)
+ {
+ super(description, extraProperties);
+
+ Validator.ensureNotNull(oid, names, description, syntax);
+ Validator.ensureNotNull(extraProperties);
+ this.oid = oid;
+ this.names = names;
+ this.isObsolete = obsolete;
+ this.syntaxOID = syntax;
+
+ if (definition != null)
+ {
+ this.definition = definition;
+ }
+ else
+ {
+ this.definition = buildDefinition();
+ }
+ this.impl = implementation;
+ }
+
+
+
+ /**
+ * Get a comparator that can be used to compare the attribute values
+ * normalized by this matching rule.
+ *
+ * @return A comparator that can be used to compare the attribute
+ * values normalized by this matching rule.
+ */
+ public Comparator<ByteSequence> comparator()
+ {
+ return impl.comparator(schema);
+ }
+
+
+
+ /**
+ * Retrieves the normalized form of the provided assertion value,
+ * which is best suite for efficiently performing matching operations
+ * on that value. The assertion value is guarenteed to be valid
+ * against this matching rule's assertion syntax.
+ *
+ * @param value
+ * The syntax checked assertion value to be normalized.
+ * @return The normalized version of the provided assertion value.
+ * @throws DecodeException
+ * if the syntax of the value is not valid.
+ */
+ public Assertion getAssertion(ByteSequence value)
+ throws DecodeException
+ {
+ return impl.getAssertion(schema, value);
+ }
+
+
+
+ /**
+ * Retrieves the normalized form of the provided assertion substring
+ * values, which is best suite for efficiently performing matching
+ * operations on that value.
+ *
+ * @param subInitial
+ * The normalized substring value fragment that should appear
+ * at the beginning of the target value.
+ * @param subAnyElements
+ * The normalized substring value fragments that should
+ * appear in the middle of the target value.
+ * @param subFinal
+ * The normalized substring value fragment that should appear
+ * at the end of the target value.
+ * @return The normalized version of the provided assertion value.
+ * @throws DecodeException
+ * if the syntax of the value is not valid.
+ */
+ public Assertion getAssertion(ByteSequence subInitial,
+ List<ByteSequence> subAnyElements, ByteSequence subFinal)
+ throws DecodeException
+ {
+ return impl.getAssertion(schema, subInitial, subAnyElements,
+ subFinal);
+ }
+
+
+
+ /**
+ * Retrieves the normalized form of the provided assertion value,
+ * which is best suite for efficiently performing greater than or
+ * equal ordering matching operations on that value. The assertion
+ * value is guarenteed to be valid against this matching rule's
+ * assertion syntax.
+ *
+ * @param value
+ * The syntax checked assertion value to be normalized.
+ * @return The normalized version of the provided assertion value.
+ * @throws DecodeException
+ * if the syntax of the value is not valid.
+ */
+ public Assertion getGreaterOrEqualAssertion(ByteSequence value)
+ throws DecodeException
+ {
+ return impl.getGreaterOrEqualAssertion(schema, value);
+ }
+
+
+
+ /**
+ * Retrieves the normalized form of the provided assertion value,
+ * which is best suite for efficiently performing greater than or
+ * equal ordering matching operations on that value. The assertion
+ * value is guarenteed to be valid against this matching rule's
+ * assertion syntax.
+ *
+ * @param value
+ * The syntax checked assertion value to be normalized.
+ * @return The normalized version of the provided assertion value.
+ * @throws DecodeException
+ * if the syntax of the value is not valid.
+ */
+ public Assertion getLessOrEqualAssertion(ByteSequence value)
+ throws DecodeException
+ {
+ return impl.getLessOrEqualAssertion(schema, value);
+ }
+
+
+
+ /**
+ * Retrieves the name or OID for this schema definition. If it has one
+ * or more names, then the primary name will be returned. If it does
+ * not have any names, then the OID will be returned.
+ *
+ * @return The name or OID for this schema definition.
+ */
+ public String getNameOrOID()
+ {
+ if (names.isEmpty())
+ {
+ return oid;
+ }
+ return names.get(0);
+ }
+
+
+
+ /**
+ * Retrieves an iterable over the set of user-defined names that may
+ * be used to reference this schema definition.
+ *
+ * @return Returns an iterable over the set of user-defined names that
+ * may be used to reference this schema definition.
+ */
+ public Iterable<String> getNames()
+ {
+ return names;
+ }
+
+
+
+ /**
+ * Retrieves the OID for this schema definition.
+ *
+ * @return The OID for this schema definition.
+ */
+ public String getOID()
+ {
+
+ return oid;
+ }
+
+
+
+ /**
+ * Retrieves the OID of the assertion value syntax with which this
+ * matching rule is associated.
+ *
+ * @return The OID of the assertion value syntax with which this
+ * matching rule is associated.
+ */
+ public Syntax getSyntax()
+ {
+ return syntax;
+ }
+
+
+
+ @Override
+ public int hashCode()
+ {
+ return oid.hashCode();
+ }
+
+
+
+ /**
+ * Indicates whether this schema definition has the specified name.
+ *
+ * @param name
+ * The name for which to make the determination.
+ * @return <code>true</code> if the specified name is assigned to this
+ * schema definition, or <code>false</code> if not.
+ */
+ public boolean hasName(String name)
+ {
+ for (final String n : names)
+ {
+ if (n.equalsIgnoreCase(name))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether this schema definition has the specified name or
+ * OID.
+ *
+ * @param value
+ * The value for which to make the determination.
+ * @return <code>true</code> if the provided value matches the OID or
+ * one of the names assigned to this schema definition, or
+ * <code>false</code> if not.
+ */
+ public boolean hasNameOrOID(String value)
+ {
+ return hasName(value) || getOID().equals(value);
+ }
+
+
+
+ /**
+ * Indicates whether this schema definition is declared "obsolete".
+ *
+ * @return <code>true</code> if this schema definition is declared
+ * "obsolete", or <code>false</code> if not.
+ */
+ public boolean isObsolete()
+ {
+ return isObsolete;
+ }
+
+
+
+ /**
+ * Retrieves the normalized form of the provided attribute value,
+ * which is best suite for efficiently performing matching operations
+ * on that value.
+ *
+ * @param value
+ * The attribute value to be normalized.
+ * @return The normalized version of the provided attribute value.
+ * @throws DecodeException
+ * if the syntax of the value is not valid.
+ */
+ public ByteString normalizeAttributeValue(ByteSequence value)
+ throws DecodeException
+ {
+ return impl.normalizeAttributeValue(schema, value);
+ }
+
+
+
+ /**
+ * Retrieves the string representation of this schema definition in
+ * the form specified in RFC 2252.
+ *
+ * @return The string representation of this schema definition in the
+ * form specified in RFC 2252.
+ */
+ @Override
+ public String toString()
+ {
+ return definition;
+ }
+
+
+
+ MatchingRule duplicate()
+ {
+ return new MatchingRule(oid, names, description, isObsolete,
+ syntaxOID, extraProperties, definition, impl);
+ }
+
+
+
+ @Override
+ void toStringContent(StringBuilder buffer)
+ {
+ buffer.append(oid);
+
+ if (!names.isEmpty())
+ {
+ final Iterator<String> iterator = names.iterator();
+
+ final String firstName = iterator.next();
+ if (iterator.hasNext())
+ {
+ buffer.append(" NAME ( '");
+ buffer.append(firstName);
+
+ while (iterator.hasNext())
+ {
+ buffer.append("' '");
+ buffer.append(iterator.next());
+ }
+
+ buffer.append("' )");
+ }
+ else
+ {
+ buffer.append(" NAME '");
+ buffer.append(firstName);
+ buffer.append("'");
+ }
+ }
+
+ if (description != null && description.length() > 0)
+ {
+ buffer.append(" DESC '");
+ buffer.append(description);
+ buffer.append("'");
+ }
+
+ if (isObsolete)
+ {
+ buffer.append(" OBSOLETE");
+ }
+
+ buffer.append(" SYNTAX ");
+ buffer.append(syntaxOID);
+ }
+
+
+
+ @Override
+ void validate(List<Message> warnings, Schema schema)
+ throws SchemaException
+ {
+ // Try finding an implementation in the core schema
+ if (impl == null && Schema.getDefaultSchema().hasMatchingRule(oid))
+ {
+ impl = Schema.getDefaultSchema().getMatchingRule(oid).impl;
+ }
+ if (impl == null && Schema.getCoreSchema().hasMatchingRule(oid))
+ {
+ impl = Schema.getCoreSchema().getMatchingRule(oid).impl;
+ }
+
+ if (impl == null)
+ {
+ impl = Schema.getDefaultMatchingRule().impl;
+ final Message message =
+ WARN_MATCHING_RULE_NOT_IMPLEMENTED.get(oid, Schema
+ .getDefaultMatchingRule().getOID());
+ warnings.add(message);
+ }
+
+ try
+ {
+ // Make sure the specifiec syntax is defined in this schema.
+ syntax = schema.getSyntax(syntaxOID);
+ }
+ catch (final UnknownSchemaElementException e)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_MR_UNKNOWN_SYNTAX.get(getNameOrOID(),
+ syntaxOID);
+ throw new SchemaException(message, e);
+ }
+
+ this.schema = schema;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/MatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/MatchingRuleImpl.java
new file mode 100644
index 0000000..04e6e12
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/MatchingRuleImpl.java
@@ -0,0 +1,160 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import java.util.Comparator;
+import java.util.List;
+
+import org.opends.sdk.Assertion;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This interface defines the set of methods that must be implemented to
+ * define a new matching rule.
+ */
+public interface MatchingRuleImpl
+{
+ /**
+ * Get a comparator that can be used to compare the attribute values
+ * normalized by this matching rule.
+ *
+ * @param schema
+ * The schema in which this matching rule is defined.
+ * @return A comparator that can be used to compare the attribute
+ * values normalized by this matching rule.
+ */
+ public Comparator<ByteSequence> comparator(Schema schema);
+
+
+
+ /**
+ * Retrieves the normalized form of the provided assertion value,
+ * which is best suite for efficiently performing matching operations
+ * on that value. The assertion value is guarenteed to be valid
+ * against this matching rule's assertion syntax.
+ *
+ * @param schema
+ * The schema in which this matching rule is defined.
+ * @param value
+ * The syntax checked assertion value to be normalized.
+ * @return The normalized version of the provided assertion value.
+ * @throws DecodeException
+ * if an syntax error occured while parsing the value.
+ */
+ public Assertion getAssertion(Schema schema, ByteSequence value)
+ throws DecodeException;
+
+
+
+ /**
+ * Retrieves the normalized form of the provided assertion substring
+ * values, which is best suite for efficiently performing matching
+ * operations on that value.
+ *
+ * @param schema
+ * The schema in which this matching rule is defined.
+ * @param subInitial
+ * The normalized substring value fragment that should appear
+ * at the beginning of the target value.
+ * @param subAnyElements
+ * The normalized substring value fragments that should
+ * appear in the middle of the target value.
+ * @param subFinal
+ * The normalized substring value fragment that should appear
+ * at the end of the target value.
+ * @return The normalized version of the provided assertion value.
+ * @throws DecodeException
+ * if an syntax error occured while parsing the value.
+ */
+ public Assertion getAssertion(Schema schema, ByteSequence subInitial,
+ List<ByteSequence> subAnyElements, ByteSequence subFinal)
+ throws DecodeException;
+
+
+
+ /**
+ * Retrieves the normalized form of the provided assertion value,
+ * which is best suite for efficiently performing greater than or
+ * equal matching operations on that value. The assertion value is
+ * guarenteed to be valid against this matching rule's assertion
+ * syntax.
+ *
+ * @param schema
+ * The schema in which this matching rule is defined.
+ * @param value
+ * The syntax checked assertion value to be normalized.
+ * @return The normalized version of the provided assertion value.
+ * @throws DecodeException
+ * if an syntax error occured while parsing the value.
+ */
+ public Assertion getGreaterOrEqualAssertion(Schema schema,
+ ByteSequence value) throws DecodeException;
+
+
+
+ /**
+ * Retrieves the normalized form of the provided assertion value,
+ * which is best suite for efficiently performing greater than or
+ * equal matching operations on that value. The assertion value is
+ * guarenteed to be valid against this matching rule's assertion
+ * syntax.
+ *
+ * @param schema
+ * The schema in which this matching rule is defined.
+ * @param value
+ * The syntax checked assertion value to be normalized.
+ * @return The normalized version of the provided assertion value.
+ * @throws DecodeException
+ * if an syntax error occured while parsing the value.
+ */
+ public Assertion getLessOrEqualAssertion(Schema schema,
+ ByteSequence value) throws DecodeException;
+
+
+
+ /**
+ * Retrieves the normalized form of the provided attribute value,
+ * which is best suite for efficiently performing matching operations
+ * on that value.
+ *
+ * @param schema
+ * The schema in which this matching rule is defined.
+ * @param value
+ * The attribute value to be normalized.
+ * @return The normalized version of the provided attribute value.
+ * @throws DecodeException
+ * if an syntax error occured while parsing the value.
+ */
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value) throws DecodeException;
+}
diff --git a/sdk/src/org/opends/sdk/schema/MatchingRuleSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/MatchingRuleSyntaxImpl.java
new file mode 100644
index 0000000..9657d53
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/MatchingRuleSyntaxImpl.java
@@ -0,0 +1,215 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_ILLEGAL_TOKEN;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_MR_EMPTY_VALUE;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_MR_EXPECTED_OPEN_PARENTHESIS;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_MR_NO_SYNTAX;
+import static org.opends.sdk.schema.SchemaConstants.EMR_OID_FIRST_COMPONENT_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_MATCHING_RULE_NAME;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class implements the matching rule description syntax, which is
+ * used to hold matching rule definitions in the server schema. The
+ * format of this syntax is defined in RFC 2252.
+ */
+final class MatchingRuleSyntaxImpl extends AbstractSyntaxImpl
+{
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_OID_FIRST_COMPONENT_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_MATCHING_RULE_NAME;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // We'll use the decodeMatchingRule method to determine if the value
+ // is acceptable.
+ try
+ {
+ final String definition = value.toString();
+ final SubstringReader reader = new SubstringReader(definition);
+
+ // We'll do this a character at a time. First, skip over any
+ // leading whitespace.
+ reader.skipWhitespaces();
+
+ if (reader.remaining() <= 0)
+ {
+ // This means that the value was empty or contained only
+ // whitespace. That is illegal.
+ final Message message = ERR_ATTR_SYNTAX_MR_EMPTY_VALUE.get();
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("MatchingRuleSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ // The next character must be an open parenthesis. If it is not,
+ // then that is an error.
+ final char c = reader.read();
+ if (c != '(')
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_MR_EXPECTED_OPEN_PARENTHESIS.get(
+ definition, (reader.pos() - 1), String.valueOf(c));
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("MatchingRuleSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ // Skip over any spaces immediately following the opening
+ // parenthesis.
+ reader.skipWhitespaces();
+
+ // The next set of characters must be the OID.
+ SchemaUtils.readOID(reader);
+ String syntax = null;
+
+ // At this point, we should have a pretty specific syntax that
+ // describes what may come next, but some of the components are
+ // optional and it would be pretty easy to put something in the
+ // wrong order, so we will be very flexible about what we can
+ // accept. Just look at the next token, figure out what it is and
+ // how to treat what comes after it, then repeat until we get to
+ // the end of the value. But before we start, set default values
+ // for everything else we might need to know.
+ while (true)
+ {
+ final String tokenName = SchemaUtils.readTokenName(reader);
+
+ if (tokenName == null)
+ {
+ // No more tokens.
+ break;
+ }
+ else if (tokenName.equalsIgnoreCase("name"))
+ {
+ SchemaUtils.readNameDescriptors(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("desc"))
+ {
+ // This specifies the description for the matching rule. It is
+ // an arbitrary string of characters enclosed in single
+ // quotes.
+ SchemaUtils.readQuotedString(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("obsolete"))
+ {
+ // This indicates whether the matching rule should be
+ // considered obsolete. We do not need to do any more parsing
+ // for this token.
+ }
+ else if (tokenName.equalsIgnoreCase("syntax"))
+ {
+ syntax = SchemaUtils.readOID(reader);
+ }
+ else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+ {
+ // This must be a non-standard property and it must be
+ // followed by either a single definition in single quotes or
+ // an open parenthesis followed by one or more values in
+ // single quotes separated by spaces followed by a close
+ // parenthesis.
+ SchemaUtils.readExtensions(reader);
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("MatchingRuleSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+ }
+
+ // Make sure that a syntax was specified.
+ if (syntax == null)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_MR_NO_SYNTAX.get(definition);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("MatchingRuleSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+ return true;
+ }
+ catch (final DecodeException de)
+ {
+ invalidReason.append(de.getMessageObject());
+ return false;
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/MatchingRuleUse.java b/sdk/src/org/opends/sdk/schema/MatchingRuleUse.java
new file mode 100644
index 0000000..11a6a62
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/MatchingRuleUse.java
@@ -0,0 +1,371 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_MRUSE_UNKNOWN_ATTR;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_MRUSE_UNKNOWN_MATCHING_RULE;
+
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class defines a data structure for storing and interacting with
+ * a matching rule use definition, which may be used to restrict the set
+ * of attribute types that may be used for a given matching rule.
+ */
+public final class MatchingRuleUse extends SchemaElement
+{
+ // The OID of the matching rule associated with this matching rule
+ // use definition.
+ private final String oid;
+
+ // The set of user defined names for this definition.
+ private final List<String> names;
+
+ // Indicates whether this definition is declared "obsolete".
+ private final boolean isObsolete;
+
+ // The set of attribute types with which this matching rule use is
+ // associated.
+ private final Set<String> attributeOIDs;
+
+ // The definition string used to create this objectclass.
+ private final String definition;
+
+ private MatchingRule matchingRule;
+ private Set<AttributeType> attributes = Collections.emptySet();
+
+
+
+ MatchingRuleUse(String oid, List<String> names, String description,
+ boolean obsolete, Set<String> attributeOIDs,
+ Map<String, List<String>> extraProperties, String definition)
+ {
+ super(description, extraProperties);
+
+ Validator.ensureNotNull(oid, names, attributeOIDs);
+ this.oid = oid;
+ this.names = names;
+ this.isObsolete = obsolete;
+ this.attributeOIDs = attributeOIDs;
+
+ if (definition != null)
+ {
+ this.definition = definition;
+ }
+ else
+ {
+ this.definition = buildDefinition();
+ }
+ }
+
+
+
+ /**
+ * Retrieves the set of attributes associated with this matching rule
+ * use.
+ *
+ * @return The set of attributes associated with this matching rule
+ * use.
+ */
+ public Iterable<AttributeType> getAttributes()
+ {
+ return attributes;
+ }
+
+
+
+ /**
+ * Retrieves the matching rule for this matching rule use.
+ *
+ * @return The matching rule for this matching rule use.
+ */
+ public MatchingRule getMatchingRule()
+ {
+ return matchingRule;
+ }
+
+
+
+ /**
+ * Retrieves the matching rule OID for this schema definition.
+ *
+ * @return The OID for this schema definition.
+ */
+ public String getMatchingRuleOID()
+ {
+ return oid;
+ }
+
+
+
+ /**
+ * Retrieves the name or matching rule OID for this schema definition.
+ * If it has one or more names, then the primary name will be
+ * returned. If it does not have any names, then the OID will be
+ * returned.
+ *
+ * @return The name or OID for this schema definition.
+ */
+ public String getNameOrOID()
+ {
+ if (names.isEmpty())
+ {
+ return oid;
+ }
+ return names.get(0);
+ }
+
+
+
+ /**
+ * Retrieves an iterable over the set of user-defined names that may
+ * be used to reference this schema definition.
+ *
+ * @return Returns an iterable over the set of user-defined names that
+ * may be used to reference this schema definition.
+ */
+ public Iterable<String> getNames()
+ {
+ return names;
+ }
+
+
+
+ /**
+ * Indicates whether the provided attribute type is referenced by this
+ * matching rule use.
+ *
+ * @param attributeType
+ * The attribute type for which to make the determination.
+ * @return {@code true} if the provided attribute type is referenced
+ * by this matching rule use, or {@code false} if it is not.
+ */
+ public boolean hasAttribute(AttributeType attributeType)
+ {
+ return attributes.contains(attributeType);
+ }
+
+
+
+ @Override
+ public int hashCode()
+ {
+ return oid.hashCode();
+ }
+
+
+
+ /**
+ * Indicates whether this schema definition has the specified name.
+ *
+ * @param name
+ * The name for which to make the determination.
+ * @return <code>true</code> if the specified name is assigned to this
+ * schema definition, or <code>false</code> if not.
+ */
+ public boolean hasName(String name)
+ {
+ for (final String n : names)
+ {
+ if (n.equalsIgnoreCase(name))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether this schema definition has the specified name or
+ * matching rule OID.
+ *
+ * @param value
+ * The value for which to make the determination.
+ * @return <code>true</code> if the provided value matches the OID or
+ * one of the names assigned to this schema definition, or
+ * <code>false</code> if not.
+ */
+ public boolean hasNameOrOID(String value)
+ {
+ return hasName(value) || oid.equals(value);
+ }
+
+
+
+ /**
+ * Indicates whether this schema definition is declared "obsolete".
+ *
+ * @return <code>true</code> if this schema definition is declared
+ * "obsolete", or <code>false</code> if not.
+ */
+ public boolean isObsolete()
+ {
+ return isObsolete;
+ }
+
+
+
+ /**
+ * Retrieves the string representation of this schema definition in
+ * the form specified in RFC 2252.
+ *
+ * @return The string representation of this schema definition in the
+ * form specified in RFC 2252.
+ */
+ @Override
+ public String toString()
+ {
+ return definition;
+ }
+
+
+
+ MatchingRuleUse duplicate()
+ {
+ return new MatchingRuleUse(oid, names, description, isObsolete,
+ attributeOIDs, extraProperties, definition);
+ }
+
+
+
+ @Override
+ void toStringContent(StringBuilder buffer)
+ {
+ buffer.append(oid);
+
+ if (!names.isEmpty())
+ {
+ final Iterator<String> iterator = names.iterator();
+
+ final String firstName = iterator.next();
+ if (iterator.hasNext())
+ {
+ buffer.append(" NAME ( '");
+ buffer.append(firstName);
+
+ while (iterator.hasNext())
+ {
+ buffer.append("' '");
+ buffer.append(iterator.next());
+ }
+
+ buffer.append("' )");
+ }
+ else
+ {
+ buffer.append(" NAME '");
+ buffer.append(firstName);
+ buffer.append("'");
+ }
+ }
+
+ if (description != null && description.length() > 0)
+ {
+ buffer.append(" DESC '");
+ buffer.append(description);
+ buffer.append("'");
+ }
+
+ if (isObsolete)
+ {
+ buffer.append(" OBSOLETE");
+ }
+
+ if (!attributeOIDs.isEmpty())
+ {
+ final Iterator<String> iterator = attributeOIDs.iterator();
+
+ final String firstName = iterator.next();
+ if (iterator.hasNext())
+ {
+ buffer.append(" APPLIES ( ");
+ buffer.append(firstName);
+
+ while (iterator.hasNext())
+ {
+ buffer.append(" $ ");
+ buffer.append(iterator.next());
+ }
+
+ buffer.append(" )");
+ }
+ else
+ {
+ buffer.append(" APPLIES ");
+ buffer.append(firstName);
+ }
+ }
+ }
+
+
+
+ @Override
+ void validate(List<Message> warnings, Schema schema)
+ throws SchemaException
+ {
+ try
+ {
+ matchingRule = schema.getMatchingRule(oid);
+ }
+ catch (final UnknownSchemaElementException e)
+ {
+ // This is bad because the matching rule use is associated with a
+ // matching rule that we don't know anything about.
+ final Message message =
+ ERR_ATTR_SYNTAX_MRUSE_UNKNOWN_MATCHING_RULE.get(definition,
+ oid);
+ throw new SchemaException(message, e);
+ }
+
+ attributes = new HashSet<AttributeType>(attributeOIDs.size());
+ AttributeType attributeType;
+ for (final String attribute : attributeOIDs)
+ {
+ try
+ {
+ attributeType = schema.getAttributeType(attribute);
+ }
+ catch (final UnknownSchemaElementException e)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_MRUSE_UNKNOWN_ATTR.get(oid, attribute);
+ throw new SchemaException(message, e);
+ }
+ attributes.add(attributeType);
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/MatchingRuleUseSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/MatchingRuleUseSyntaxImpl.java
new file mode 100644
index 0000000..086055a
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/MatchingRuleUseSyntaxImpl.java
@@ -0,0 +1,217 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_ILLEGAL_TOKEN;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_MRUSE_EMPTY_VALUE;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_MRUSE_EXPECTED_OPEN_PARENTHESIS;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_MRUSE_NO_ATTR;
+import static org.opends.sdk.schema.SchemaConstants.EMR_OID_FIRST_COMPONENT_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_MATCHING_RULE_USE_NAME;
+
+import java.util.Set;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class implements the matching rule use description syntax, which
+ * is used to hold matching rule use definitions in the server schema.
+ * The format of this syntax is defined in RFC 2252.
+ */
+final class MatchingRuleUseSyntaxImpl extends AbstractSyntaxImpl
+{
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_OID_FIRST_COMPONENT_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_MATCHING_RULE_USE_NAME;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // We'll use the decodeAttributeType method to determine if the
+ // value is acceptable.
+ try
+ {
+ final String definition = value.toString();
+ final SubstringReader reader = new SubstringReader(definition);
+
+ // We'll do this a character at a time. First, skip over any
+ // leading whitespace.
+ reader.skipWhitespaces();
+
+ if (reader.remaining() <= 0)
+ {
+ // This means that the value was empty or contained only
+ // whitespace. That is illegal.
+ final Message message = ERR_ATTR_SYNTAX_MRUSE_EMPTY_VALUE.get();
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("MatchingRuleUseSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ // The next character must be an open parenthesis. If it is not,
+ // then that is an error.
+ final char c = reader.read();
+ if (c != '(')
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_MRUSE_EXPECTED_OPEN_PARENTHESIS.get(
+ definition, (reader.pos() - 1), String.valueOf(c));
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("MatchingRuleUseSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ // Skip over any spaces immediately following the opening
+ // parenthesis.
+ reader.skipWhitespaces();
+
+ // The next set of characters must be the OID.
+ SchemaUtils.readOID(reader);
+
+ // At this point, we should have a pretty specific syntax that
+ // describes what may come next, but some of the components are
+ // optional and it would be pretty easy to put something in the
+ // wrong order, so we will be very flexible about what we can
+ // accept. Just look at the next token, figure out what it is and
+ // how to treat what comes after it, then repeat until we get to
+ // the end of the value. But before we start, set default values
+ // for everything else we might need to know.
+ Set<String> attributes = null;
+ while (true)
+ {
+ final String tokenName = SchemaUtils.readTokenName(reader);
+
+ if (tokenName == null)
+ {
+ // No more tokens.
+ break;
+ }
+ else if (tokenName.equalsIgnoreCase("name"))
+ {
+ SchemaUtils.readNameDescriptors(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("desc"))
+ {
+ // This specifies the description for the attribute type. It
+ // is an arbitrary string of characters enclosed in single
+ // quotes.
+ SchemaUtils.readQuotedString(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("obsolete"))
+ {
+ // This indicates whether the attribute type should be
+ // considered obsolete. We do not need to do any more parsing
+ // for this token.
+ }
+ else if (tokenName.equalsIgnoreCase("applies"))
+ {
+ attributes = SchemaUtils.readOIDs(reader);
+ }
+ else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+ {
+ // This must be a non-standard property and it must be
+ // followed by either a single definition in single quotes or
+ // an open parenthesis followed by one or more values in
+ // single quotes separated by spaces followed by a close
+ // parenthesis.
+ SchemaUtils.readExtensions(reader);
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("MatchingRuleUseSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+ }
+
+ // Make sure that the set of attributes was defined.
+ if (attributes == null || attributes.size() == 0)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_MRUSE_NO_ATTR.get(definition);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("MatchingRuleUseSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+ return true;
+ }
+ catch (final DecodeException de)
+ {
+ invalidReason.append(de.getMessageObject());
+ return false;
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/NameAndOptionalUIDSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/NameAndOptionalUIDSyntaxImpl.java
new file mode 100644
index 0000000..acc8ad7
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/NameAndOptionalUIDSyntaxImpl.java
@@ -0,0 +1,152 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_NAMEANDUID_ILLEGAL_BINARY_DIGIT;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_NAMEANDUID_INVALID_DN;
+import static org.opends.sdk.schema.SchemaConstants.EMR_UNIQUE_MEMBER_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_NAME_AND_OPTIONAL_UID_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DN;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * This class implements the name and optional UID attribute syntax,
+ * which holds values consisting of a DN, optionally followed by an
+ * octothorpe (#) and a bit string value.
+ */
+final class NameAndOptionalUIDSyntaxImpl extends AbstractSyntaxImpl
+{
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_UNIQUE_MEMBER_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_NAME_AND_OPTIONAL_UID_NAME;
+ }
+
+
+
+ @Override
+ public String getSubstringMatchingRule()
+ {
+ return SMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ final String valueString = value.toString().trim();
+ final int valueLength = valueString.length();
+
+ // See if the value contains the "optional uid" portion. If we think
+ // it does, then mark its location.
+ int dnEndPos = valueLength;
+ int sharpPos = -1;
+ if (valueString.endsWith("'B") || valueString.endsWith("'b"))
+ {
+ sharpPos = valueString.lastIndexOf("#'");
+ if (sharpPos > 0)
+ {
+ dnEndPos = sharpPos;
+ }
+ }
+
+ // Take the DN portion of the string and try to normalize it.
+ try
+ {
+ DN.valueOf(valueString.substring(0, dnEndPos), schema);
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ // We couldn't normalize the DN for some reason. The value cannot
+ // be acceptable.
+ invalidReason.append(ERR_ATTR_SYNTAX_NAMEANDUID_INVALID_DN.get(
+ valueString, e.getMessageObject()));
+ return false;
+ }
+
+ // If there is an "optional uid", then normalize it and make sure it
+ // only contains valid binary digits.
+ if (sharpPos > 0)
+ {
+ final int endPos = valueLength - 2;
+ for (int i = sharpPos + 2; i < endPos; i++)
+ {
+ final char c = valueString.charAt(i);
+ if (!(c == '0' || c == '1'))
+ {
+
+ invalidReason
+ .append(ERR_ATTR_SYNTAX_NAMEANDUID_ILLEGAL_BINARY_DIGIT
+ .get(valueString, String.valueOf(c), i));
+ return false;
+ }
+ }
+ }
+
+ // If we've gotten here, then the value is acceptable.
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/NameForm.java b/sdk/src/org/opends/sdk/schema/NameForm.java
new file mode 100644
index 0000000..eb0b398
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/NameForm.java
@@ -0,0 +1,452 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_NAME_FORM_STRUCTURAL_CLASS_NOT_STRUCTURAL;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_OPTIONAL_ATTR;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_REQUIRED_ATTR;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_STRUCTURAL_CLASS;
+
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class defines a data structure for storing and interacting with
+ * a name form, which defines the attribute type(s) that must and/or may
+ * be used in the RDN of an entry with a given structural objectclass.
+ */
+public final class NameForm extends SchemaElement
+{
+ // The OID that may be used to reference this definition.
+ private final String oid;
+
+ // The set of user defined names for this definition.
+ private final List<String> names;
+
+ // Indicates whether this definition is declared "obsolete".
+ private final boolean isObsolete;
+
+ // The reference to the structural objectclass for this name form.
+ private final String structuralClassOID;
+
+ // The set of optional attribute types for this name form.
+ private final Set<String> optionalAttributeOIDs;
+
+ // The set of required attribute types for this name form.
+ private final Set<String> requiredAttributeOIDs;
+
+ // The definition string used to create this objectclass.
+ private final String definition;
+
+ private ObjectClass structuralClass;
+ private Set<AttributeType> optionalAttributes =
+ Collections.emptySet();
+ private Set<AttributeType> requiredAttributes =
+ Collections.emptySet();
+
+
+
+ NameForm(String oid, List<String> names, String description,
+ boolean obsolete, String structuralClassOID,
+ Set<String> requiredAttributeOIDs,
+ Set<String> optionalAttributeOIDs,
+ Map<String, List<String>> extraProperties, String definition)
+ {
+ super(description, extraProperties);
+
+ Validator.ensureNotNull(oid, names);
+ Validator.ensureNotNull(structuralClassOID, requiredAttributeOIDs,
+ optionalAttributeOIDs);
+ Validator.ensureTrue(requiredAttributeOIDs.size() > 0,
+ "required attribute is empty");
+ this.oid = oid;
+ this.names = names;
+ this.isObsolete = obsolete;
+ this.structuralClassOID = structuralClassOID;
+ this.requiredAttributeOIDs = requiredAttributeOIDs;
+ this.optionalAttributeOIDs = optionalAttributeOIDs;
+
+ if (definition != null)
+ {
+ this.definition = definition;
+ }
+ else
+ {
+ this.definition = buildDefinition();
+ }
+ }
+
+
+
+ /**
+ * Retrieves the name or OID for this schema definition. If it has one
+ * or more names, then the primary name will be returned. If it does
+ * not have any names, then the OID will be returned.
+ *
+ * @return The name or OID for this schema definition.
+ */
+ public String getNameOrOID()
+ {
+ if (names.isEmpty())
+ {
+ return oid;
+ }
+ return names.get(0);
+ }
+
+
+
+ /**
+ * Retrieves an iterable over the set of user-defined names that may
+ * be used to reference this schema definition.
+ *
+ * @return Returns an iterable over the set of user-defined names that
+ * may be used to reference this schema definition.
+ */
+ public Iterable<String> getNames()
+ {
+ return names;
+ }
+
+
+
+ /**
+ * Retrieves the OID for this schema definition.
+ *
+ * @return The OID for this schema definition.
+ */
+ public String getOID()
+ {
+
+ return oid;
+ }
+
+
+
+ /**
+ * Retrieves the set of optional attributes for this name form.
+ *
+ * @return The set of optional attributes for this name form.
+ */
+ public Iterable<AttributeType> getOptionalAttributes()
+ {
+ return optionalAttributes;
+ }
+
+
+
+ /**
+ * Retrieves the set of required attributes for this name form.
+ *
+ * @return The set of required attributes for this name form.
+ */
+ public Iterable<AttributeType> getRequiredAttributes()
+ {
+ return requiredAttributes;
+ }
+
+
+
+ /**
+ * Retrieves the reference to the structural objectclass for this name
+ * form.
+ *
+ * @return The reference to the structural objectclass for this name
+ * form.
+ */
+ public ObjectClass getStructuralClass()
+ {
+ return structuralClass;
+ }
+
+
+
+ @Override
+ public int hashCode()
+ {
+ return oid.hashCode();
+ }
+
+
+
+ /**
+ * Indicates whether this schema definition has the specified name.
+ *
+ * @param name
+ * The name for which to make the determination.
+ * @return <code>true</code> if the specified name is assigned to this
+ * schema definition, or <code>false</code> if not.
+ */
+ public boolean hasName(String name)
+ {
+ for (final String n : names)
+ {
+ if (n.equalsIgnoreCase(name))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether this schema definition has the specified name or
+ * OID.
+ *
+ * @param value
+ * The value for which to make the determination.
+ * @return <code>true</code> if the provided value matches the OID or
+ * one of the names assigned to this schema definition, or
+ * <code>false</code> if not.
+ */
+ public boolean hasNameOrOID(String value)
+ {
+ return hasName(value) || getOID().equals(value);
+ }
+
+
+
+ /**
+ * Indicates whether this schema definition is declared "obsolete".
+ *
+ * @return <code>true</code> if this schema definition is declared
+ * "obsolete", or <code>false</code> if not.
+ */
+ public boolean isObsolete()
+ {
+ return isObsolete;
+ }
+
+
+
+ /**
+ * Retrieves the string representation of this schema definition in
+ * the form specified in RFC 2252.
+ *
+ * @return The string representation of this schema definition in the
+ * form specified in RFC 2252.
+ */
+ @Override
+ public String toString()
+ {
+ return definition;
+ }
+
+
+
+ NameForm duplicate()
+ {
+ return new NameForm(oid, names, description, isObsolete,
+ structuralClassOID, requiredAttributeOIDs,
+ optionalAttributeOIDs, extraProperties, definition);
+ }
+
+
+
+ @Override
+ void toStringContent(StringBuilder buffer)
+ {
+ buffer.append(oid);
+
+ if (!names.isEmpty())
+ {
+ final Iterator<String> iterator = names.iterator();
+
+ final String firstName = iterator.next();
+ if (iterator.hasNext())
+ {
+ buffer.append(" NAME ( '");
+ buffer.append(firstName);
+
+ while (iterator.hasNext())
+ {
+ buffer.append("' '");
+ buffer.append(iterator.next());
+ }
+
+ buffer.append("' )");
+ }
+ else
+ {
+ buffer.append(" NAME '");
+ buffer.append(firstName);
+ buffer.append("'");
+ }
+ }
+
+ if (description != null && description.length() > 0)
+ {
+ buffer.append(" DESC '");
+ buffer.append(description);
+ buffer.append("'");
+ }
+
+ if (isObsolete)
+ {
+ buffer.append(" OBSOLETE");
+ }
+
+ buffer.append(" OC ");
+ buffer.append(structuralClassOID);
+
+ if (!requiredAttributeOIDs.isEmpty())
+ {
+ final Iterator<String> iterator =
+ requiredAttributeOIDs.iterator();
+
+ final String firstName = iterator.next();
+ if (iterator.hasNext())
+ {
+ buffer.append(" MUST ( ");
+ buffer.append(firstName);
+
+ while (iterator.hasNext())
+ {
+ buffer.append(" $ ");
+ buffer.append(iterator.next());
+ }
+
+ buffer.append(" )");
+ }
+ else
+ {
+ buffer.append(" MUST ");
+ buffer.append(firstName);
+ }
+ }
+
+ if (!optionalAttributeOIDs.isEmpty())
+ {
+ final Iterator<String> iterator =
+ optionalAttributeOIDs.iterator();
+
+ final String firstName = iterator.next();
+ if (iterator.hasNext())
+ {
+ buffer.append(" MAY ( ");
+ buffer.append(firstName);
+
+ while (iterator.hasNext())
+ {
+ buffer.append(" $ ");
+ buffer.append(iterator.next());
+ }
+
+ buffer.append(" )");
+ }
+ else
+ {
+ buffer.append(" MAY ");
+ buffer.append(firstName);
+ }
+ }
+ }
+
+
+
+ @Override
+ void validate(List<Message> warnings, Schema schema)
+ throws SchemaException
+ {
+ try
+ {
+ structuralClass = schema.getObjectClass(structuralClassOID);
+ }
+ catch (final UnknownSchemaElementException e)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_STRUCTURAL_CLASS.get(oid,
+ structuralClassOID);
+ throw new SchemaException(message, e);
+ }
+ if (structuralClass.getObjectClassType() != ObjectClassType.STRUCTURAL)
+ {
+ // This is bad because the associated structural class type is not
+ // structural.
+ final Message message =
+ ERR_ATTR_SYNTAX_NAME_FORM_STRUCTURAL_CLASS_NOT_STRUCTURAL
+ .get(oid, structuralClass.getOID(), structuralClass
+ .getNameOrOID(), String.valueOf(structuralClass
+ .getObjectClassType()));
+ throw new SchemaException(message);
+ }
+
+ requiredAttributes =
+ new HashSet<AttributeType>(requiredAttributeOIDs.size());
+ AttributeType attributeType;
+ for (final String oid : requiredAttributeOIDs)
+ {
+ try
+ {
+ attributeType = schema.getAttributeType(oid);
+ }
+ catch (final UnknownSchemaElementException e)
+ {
+ // This isn't good because it means that the name form requires
+ // an attribute type that we don't know anything about.
+ final Message message =
+ ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_REQUIRED_ATTR.get(
+ this.oid, oid);
+ throw new SchemaException(message, e);
+ }
+ requiredAttributes.add(attributeType);
+ }
+
+ if (!optionalAttributeOIDs.isEmpty())
+ {
+ optionalAttributes =
+ new HashSet<AttributeType>(optionalAttributeOIDs.size());
+ for (final String oid : optionalAttributeOIDs)
+ {
+ try
+ {
+ attributeType = schema.getAttributeType(oid);
+ }
+ catch (final UnknownSchemaElementException e)
+ {
+ // This isn't good because it means that the name form
+ // requires an attribute type that we don't know anything
+ // about.
+ final Message message =
+ ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_OPTIONAL_ATTR.get(
+ this.oid, oid);
+ throw new SchemaException(message, e);
+ }
+ optionalAttributes.add(attributeType);
+ }
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/NameFormSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/NameFormSyntaxImpl.java
new file mode 100644
index 0000000..e03bbc0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/NameFormSyntaxImpl.java
@@ -0,0 +1,223 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.schema.SchemaConstants.EMR_OID_FIRST_COMPONENT_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_NAME_FORM_NAME;
+
+import java.util.Set;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class implements the name form description syntax, which is used
+ * to hold name form definitions in the server schema. The format of
+ * this syntax is defined in RFC 2252.
+ */
+final class NameFormSyntaxImpl extends AbstractSyntaxImpl
+{
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_OID_FIRST_COMPONENT_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_NAME_FORM_NAME;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // We'll use the decodeNameForm method to determine if the value is
+ // acceptable.
+ try
+ {
+ final String definition = value.toString();
+ final SubstringReader reader = new SubstringReader(definition);
+
+ // We'll do this a character at a time. First, skip over any
+ // leading whitespace.
+ reader.skipWhitespaces();
+
+ if (reader.remaining() <= 0)
+ {
+ // This means that the value was empty or contained only
+ // whitespace. That is illegal.
+ final Message message =
+ ERR_ATTR_SYNTAX_NAME_FORM_EMPTY_VALUE.get();
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("NameFormSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ // The next character must be an open parenthesis. If it is not,
+ // then that is an error.
+ final char c = reader.read();
+ if (c != '(')
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_NAME_FORM_EXPECTED_OPEN_PARENTHESIS.get(
+ definition, (reader.pos() - 1), c);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("NameFormSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ // Skip over any spaces immediately following the opening
+ // parenthesis.
+ reader.skipWhitespaces();
+
+ // The next set of characters must be the OID.
+ SchemaUtils.readOID(reader);
+
+ String structuralClass = null;
+ Set<String> requiredAttributes = null;
+
+ // At this point, we should have a pretty specific syntax that
+ // describes what may come next, but some of the components are
+ // optional and it would be pretty easy to put something in the
+ // wrong order, so we will be very flexible about what we can
+ // accept. Just look at the next token, figure out what it is and
+ // how to treat what comes after it, then repeat until we get to
+ // the end of the value. But before we start, set default values
+ // for everything else we might need to know.
+ while (true)
+ {
+ final String tokenName = SchemaUtils.readTokenName(reader);
+
+ if (tokenName == null)
+ {
+ // No more tokens.
+ break;
+ }
+ else if (tokenName.equalsIgnoreCase("name"))
+ {
+ SchemaUtils.readNameDescriptors(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("desc"))
+ {
+ // This specifies the description for the attribute type. It
+ // is an arbitrary string of characters enclosed in single
+ // quotes.
+ SchemaUtils.readQuotedString(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("obsolete"))
+ {
+ // This indicates whether the attribute type should be
+ // considered obsolete. We do not need to do any more parsing
+ // for this token.
+ }
+ else if (tokenName.equalsIgnoreCase("oc"))
+ {
+ structuralClass = SchemaUtils.readOID(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("must"))
+ {
+ requiredAttributes = SchemaUtils.readOIDs(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("may"))
+ {
+ SchemaUtils.readOIDs(reader);
+ }
+ else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+ {
+ // This must be a non-standard property and it must be
+ // followed by either a single definition in single quotes or
+ // an open parenthesis followed by one or more values in
+ // single quotes separated by spaces followed by a close
+ // parenthesis.
+ SchemaUtils.readExtensions(reader);
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("NameFormSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+ }
+
+ // Make sure that a structural class was specified. If not, then
+ // it cannot be valid.
+ if (structuralClass == null)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_NAME_FORM_NO_STRUCTURAL_CLASS
+ .get(definition);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("NameFormSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ if (requiredAttributes == null || requiredAttributes.size() == 0)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_NAME_FORM_NO_REQUIRED_ATTR.get(definition);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("NameFormSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+ return true;
+ }
+ catch (final DecodeException de)
+ {
+ invalidReason.append(de.getMessageObject());
+ return false;
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/NumericStringEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/NumericStringEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..e803010
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/NumericStringEqualityMatchingRuleImpl.java
@@ -0,0 +1,60 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.NO_CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the numericStringMatch matching rule defined in
+ * X.520 and referenced in RFC 2252. It allows for values with numeric
+ * digits and spaces, but ignores spaces when performing matching.
+ */
+final class NumericStringEqualityMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value)
+ {
+ final StringBuilder buffer = new StringBuilder();
+ prepareUnicode(buffer, value, TRIM, NO_CASE_FOLD);
+
+ if (buffer.length() == 0)
+ {
+ return ByteString.empty();
+ }
+ return ByteString.valueOf(buffer.toString());
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/NumericStringOrderingMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/NumericStringOrderingMatchingRuleImpl.java
new file mode 100644
index 0000000..0b342fc
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/NumericStringOrderingMatchingRuleImpl.java
@@ -0,0 +1,59 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.NO_CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This implements defines the numericStringOrderingMatch matching rule
+ * defined in X.520 and referenced in RFC 2252.
+ */
+final class NumericStringOrderingMatchingRuleImpl extends
+ AbstractOrderingMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value)
+ {
+ final StringBuilder buffer = new StringBuilder();
+ prepareUnicode(buffer, value, TRIM, NO_CASE_FOLD);
+
+ if (buffer.length() == 0)
+ {
+ return ByteString.empty();
+ }
+ return ByteString.valueOf(buffer.toString());
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/NumericStringSubstringMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/NumericStringSubstringMatchingRuleImpl.java
new file mode 100644
index 0000000..216aaef
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/NumericStringSubstringMatchingRuleImpl.java
@@ -0,0 +1,59 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.NO_CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the numericStringSubstringsMatch matching rule
+ * defined in X.520 and referenced in RFC 2252.
+ */
+final class NumericStringSubstringMatchingRuleImpl extends
+ AbstractSubstringMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value)
+ {
+ final StringBuilder buffer = new StringBuilder();
+ prepareUnicode(buffer, value, TRIM, NO_CASE_FOLD);
+
+ if (buffer.length() == 0)
+ {
+ return ByteString.empty();
+ }
+ return ByteString.valueOf(buffer.toString());
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/NumericStringSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/NumericStringSyntaxImpl.java
new file mode 100644
index 0000000..960ead7
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/NumericStringSyntaxImpl.java
@@ -0,0 +1,137 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_NUMERIC_STRING_EMPTY_VALUE;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_NUMERIC_STRING_ILLEGAL_CHAR;
+import static org.opends.sdk.schema.SchemaConstants.EMR_NUMERIC_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_NUMERIC_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_EXACT_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_NUMERIC_STRING_NAME;
+import static org.opends.sdk.util.StaticUtils.isDigit;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the numeric string attribute syntax, which may
+ * be hold one or more numeric digits and/or spaces. Equality, ordering,
+ * and substring matching will be allowed by default.
+ */
+final class NumericStringSyntaxImpl extends AbstractSyntaxImpl
+{
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_NUMERIC_STRING_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_NUMERIC_STRING_NAME;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_NUMERIC_STRING_OID;
+ }
+
+
+
+ @Override
+ public String getSubstringMatchingRule()
+ {
+ return SMR_CASE_EXACT_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ final String valueString = value.toString();
+ final int length = valueString.length();
+
+ // It must have at least one digit or space.
+ if (length == 0)
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_NUMERIC_STRING_EMPTY_VALUE
+ .get());
+ return false;
+ }
+
+ // Iterate through the characters and make sure they are all digits
+ // or spaces.
+ for (int i = 0; i < length; i++)
+ {
+ final char c = valueString.charAt(i);
+ if (!(isDigit(c) || c == ' '))
+ {
+
+ invalidReason
+ .append(WARN_ATTR_SYNTAX_NUMERIC_STRING_ILLEGAL_CHAR.get(
+ valueString, String.valueOf(c), i));
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/OIDSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/OIDSyntaxImpl.java
new file mode 100644
index 0000000..3ca71ae
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/OIDSyntaxImpl.java
@@ -0,0 +1,108 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.EMR_OID_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_OID_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class defines the OID syntax, which holds either an identifier
+ * name or a numeric OID. Equality and substring matching will be
+ * allowed by default.
+ */
+final class OIDSyntaxImpl extends AbstractSyntaxImpl
+{
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_OID_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_OID_NAME;
+ }
+
+
+
+ @Override
+ public String getSubstringMatchingRule()
+ {
+ return SMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ try
+ {
+ SchemaUtils.readOID(new SubstringReader(value.toString()));
+ return true;
+ }
+ catch (final DecodeException de)
+ {
+ invalidReason.append(de.getMessageObject());
+ return false;
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/ObjectClass.java b/sdk/src/org/opends/sdk/schema/ObjectClass.java
new file mode 100644
index 0000000..81844ee
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/ObjectClass.java
@@ -0,0 +1,810 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.schema.SchemaConstants.EXTENSIBLE_OBJECT_OBJECTCLASS_NAME;
+import static org.opends.sdk.schema.SchemaConstants.EXTENSIBLE_OBJECT_OBJECTCLASS_OID;
+import static org.opends.sdk.schema.SchemaConstants.TOP_OBJECTCLASS_NAME;
+
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class defines a data structure for storing and interacting with
+ * an objectclass, which contains a collection of attributes that must
+ * and/or may be present in an entry with that objectclass.
+ * <p>
+ * Where ordered sets of names, attribute types, or extra properties are
+ * provided, the ordering will be preserved when the associated fields
+ * are accessed via their getters or via the {@link #toString()}
+ * methods.
+ */
+public final class ObjectClass extends SchemaElement
+{
+ // The OID that may be used to reference this definition.
+ private final String oid;
+
+ // The set of user defined names for this definition.
+ private final List<String> names;
+
+ // Indicates whether this definition is declared "obsolete".
+ private final boolean isObsolete;
+
+ // The reference to the superior objectclasses.
+ private final Set<String> superiorClassOIDs;
+
+ // The objectclass type for this objectclass.
+ private final ObjectClassType objectClassType;
+
+ // The set of required attribute types for this objectclass.
+ private final Set<String> requiredAttributeOIDs;
+
+ // The set of optional attribute types for this objectclass.
+ private final Set<String> optionalAttributeOIDs;
+
+ // The definition string used to create this objectclass.
+ private final String definition;
+
+ private Set<ObjectClass> superiorClasses = Collections.emptySet();
+ private Set<AttributeType> declaredRequiredAttributes =
+ Collections.emptySet();
+ private Set<AttributeType> requiredAttributes =
+ Collections.emptySet();
+ private Set<AttributeType> declaredOptionalAttributes =
+ Collections.emptySet();
+ private Set<AttributeType> optionalAttributes =
+ Collections.emptySet();
+ private boolean validated = false;
+
+
+
+ ObjectClass(String oid, List<String> names, String description,
+ boolean obsolete, Set<String> superiorClassOIDs,
+ Set<String> requiredAttributeOIDs,
+ Set<String> optionalAttributeOIDs,
+ ObjectClassType objectClassType,
+ Map<String, List<String>> extraProperties, String definition)
+ {
+ super(description, extraProperties);
+
+ Validator.ensureNotNull(oid, names);
+ Validator.ensureNotNull(superiorClassOIDs, requiredAttributeOIDs,
+ optionalAttributeOIDs, objectClassType);
+ this.oid = oid;
+ this.names = names;
+ this.isObsolete = obsolete;
+ this.superiorClassOIDs = superiorClassOIDs;
+ this.objectClassType = objectClassType;
+ this.requiredAttributeOIDs = requiredAttributeOIDs;
+ this.optionalAttributeOIDs = optionalAttributeOIDs;
+
+ if (definition != null)
+ {
+ this.definition = definition;
+ }
+ else
+ {
+ this.definition = buildDefinition();
+ }
+ }
+
+
+
+ /**
+ * Construct a extensibleObject object class where the set of allowed
+ * attribute types of this object class is implicitly the set of all
+ * attribute types of userApplications usage.
+ *
+ * @param description
+ * The description for this schema definition
+ * @param extraProperties
+ * The map of "extra" properties for this schema definition
+ */
+ ObjectClass(String description,
+ Map<String, List<String>> extraProperties)
+ {
+ super(description, extraProperties);
+ this.oid = EXTENSIBLE_OBJECT_OBJECTCLASS_OID;
+ this.names =
+ Collections.singletonList(EXTENSIBLE_OBJECT_OBJECTCLASS_NAME);
+ this.isObsolete = false;
+ this.superiorClassOIDs =
+ Collections.singleton(TOP_OBJECTCLASS_NAME);
+ this.objectClassType = ObjectClassType.AUXILIARY;
+ this.requiredAttributeOIDs = Collections.emptySet();
+ this.optionalAttributeOIDs = Collections.emptySet();
+
+ this.definition = buildDefinition();
+ }
+
+
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ {
+ return true;
+ }
+
+ if (o instanceof ObjectClass)
+ {
+ final ObjectClass other = (ObjectClass) o;
+ return oid.equals(other.oid);
+ }
+
+ return false;
+ }
+
+
+
+ /**
+ * Retrieves the list of optional attributes for this objectclass.
+ * Note that this set will not automatically include any optional
+ * attributes for superior objectclasses.
+ *
+ * @return Returns the list of optional attributes for this
+ * objectclass.
+ */
+ public Iterable<AttributeType> getDeclaredOptionalAttributes()
+ {
+ return declaredOptionalAttributes;
+ }
+
+
+
+ /**
+ * Retrieves the list of required attributes for this objectclass.
+ * Note that this set will not automatically include any required
+ * attributes for superior objectclasses.
+ *
+ * @return Returns the list of required attributes for this
+ * objectclass.
+ */
+ public Iterable<AttributeType> getDeclaredRequiredAttributes()
+ {
+ return declaredRequiredAttributes;
+ }
+
+
+
+ /**
+ * Retrieves the name or OID for this schema definition. If it has one
+ * or more names, then the primary name will be returned. If it does
+ * not have any names, then the OID will be returned.
+ *
+ * @return The name or OID for this schema definition.
+ */
+ public String getNameOrOID()
+ {
+ if (names.isEmpty())
+ {
+ return oid;
+ }
+ return names.get(0);
+ }
+
+
+
+ /**
+ * Retrieves an iterable over the set of user-defined names that may
+ * be used to reference this schema definition.
+ *
+ * @return Returns an iterable over the set of user-defined names that
+ * may be used to reference this schema definition.
+ */
+ public Iterable<String> getNames()
+ {
+ return names;
+ }
+
+
+
+ /**
+ * Retrieves the objectclass type for this objectclass.
+ *
+ * @return The objectclass type for this objectclass.
+ */
+ public ObjectClassType getObjectClassType()
+ {
+
+ return objectClassType;
+ }
+
+
+
+ /**
+ * Retrieves the OID for this schema definition.
+ *
+ * @return The OID for this schema definition.
+ */
+ public String getOID()
+ {
+
+ return oid;
+ }
+
+
+
+ /**
+ * Retrieves the list of all optional attributes for this objectclass
+ * and any superior objectclasses that it might have.
+ *
+ * @return Returns the list of all optional attributes for this
+ * objectclass and any superior objectclasses that it might
+ * have.
+ */
+ public Iterable<AttributeType> getOptionalAttributes()
+ {
+ return optionalAttributes;
+ }
+
+
+
+ /**
+ * Retrieves the list of all required attributes for this objectclass
+ * and any superior objectclasses that it might have.
+ *
+ * @return Returns the list of all required attributes for this
+ * objectclass and any superior objectclasses that it might
+ * have.
+ */
+ public Iterable<AttributeType> getRequiredAttributes()
+ {
+ return requiredAttributes;
+ }
+
+
+
+ /**
+ * Retrieves the reference to the superior classes for this
+ * objectclass.
+ *
+ * @return The list of superior classes for this objectlass.
+ */
+ public Iterable<ObjectClass> getSuperiorClasses()
+ {
+ return superiorClasses;
+ }
+
+
+
+ @Override
+ public int hashCode()
+ {
+ return oid.hashCode();
+ }
+
+
+
+ /**
+ * Indicates whether this schema definition has the specified name.
+ *
+ * @param name
+ * The name for which to make the determination.
+ * @return <code>true</code> if the specified name is assigned to this
+ * schema definition, or <code>false</code> if not.
+ */
+ public boolean hasName(String name)
+ {
+ for (final String n : names)
+ {
+ if (n.equalsIgnoreCase(name))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether this schema definition has the specified name or
+ * OID.
+ *
+ * @param value
+ * The value for which to make the determination.
+ * @return <code>true</code> if the provided value matches the OID or
+ * one of the names assigned to this schema definition, or
+ * <code>false</code> if not.
+ */
+ public boolean hasNameOrOID(String value)
+ {
+ return hasName(value) || getOID().equals(value);
+ }
+
+
+
+ /**
+ * Indicates whether this objectclass is a descendant of the provided
+ * class.
+ *
+ * @param objectClass
+ * The objectClass for which to make the determination.
+ * @return <code>true</code> if this objectclass is a descendant of
+ * the provided class, or <code>false</code> if not.
+ */
+ public boolean isDescendantOf(ObjectClass objectClass)
+ {
+ for (final ObjectClass sup : superiorClasses)
+ {
+ if (sup.equals(objectClass) || sup.isDescendantOf(objectClass))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether this schema definition is declared "obsolete".
+ *
+ * @return <code>true</code> if this schema definition is declared
+ * "obsolete", or <code>false</code> if not.
+ */
+ public boolean isObsolete()
+ {
+ return isObsolete;
+ }
+
+
+
+ /**
+ * Indicates whether the provided attribute type is included in the
+ * optional attribute list for this or any of its superior
+ * objectclasses.
+ *
+ * @param attributeType
+ * The attribute type for which to make the determination.
+ * @return <code>true</code> if the provided attribute type is
+ * optional for this objectclass or any of its superior
+ * classes, or <code>false</code> if not.
+ */
+ public boolean isOptional(AttributeType attributeType)
+ {
+ return optionalAttributes.contains(attributeType);
+ }
+
+
+
+ /**
+ * Indicates whether the provided attribute type is included in the
+ * required attribute list for this or any of its superior
+ * objectclasses.
+ *
+ * @param attributeType
+ * The attribute type for which to make the determination.
+ * @return <code>true</code> if the provided attribute type is
+ * required by this objectclass or any of its superior
+ * classes, or <code>false</code> if not.
+ */
+ public boolean isRequired(AttributeType attributeType)
+ {
+ return requiredAttributes.contains(attributeType);
+ }
+
+
+
+ /**
+ * Indicates whether the provided attribute type is in the list of
+ * required or optional attributes for this objectclass or any of its
+ * superior classes.
+ *
+ * @param attributeType
+ * The attribute type for which to make the determination.
+ * @return <code>true</code> if the provided attribute type is
+ * required or allowed for this objectclass or any of its
+ * superior classes, or <code>false</code> if it is not.
+ */
+ public boolean isRequiredOrOptional(AttributeType attributeType)
+ {
+ return isRequired(attributeType) || isOptional(attributeType);
+ }
+
+
+
+ /**
+ * Retrieves the string representation of this schema definition in
+ * the form specified in RFC 2252.
+ *
+ * @return The string representation of this schema definition in the
+ * form specified in RFC 2252.
+ */
+ @Override
+ public String toString()
+ {
+ return definition;
+ }
+
+
+
+ ObjectClass duplicate()
+ {
+ return new ObjectClass(oid, names, description, isObsolete,
+ superiorClassOIDs, requiredAttributeOIDs,
+ optionalAttributeOIDs, objectClassType, extraProperties,
+ definition);
+ }
+
+
+
+ @Override
+ void toStringContent(StringBuilder buffer)
+ {
+ buffer.append(oid);
+
+ if (!names.isEmpty())
+ {
+ final Iterator<String> iterator = names.iterator();
+
+ final String firstName = iterator.next();
+ if (iterator.hasNext())
+ {
+ buffer.append(" NAME ( '");
+ buffer.append(firstName);
+
+ while (iterator.hasNext())
+ {
+ buffer.append("' '");
+ buffer.append(iterator.next());
+ }
+
+ buffer.append("' )");
+ }
+ else
+ {
+ buffer.append(" NAME '");
+ buffer.append(firstName);
+ buffer.append("'");
+ }
+ }
+
+ if (description != null && description.length() > 0)
+ {
+ buffer.append(" DESC '");
+ buffer.append(description);
+ buffer.append("'");
+ }
+
+ if (isObsolete)
+ {
+ buffer.append(" OBSOLETE");
+ }
+
+ if (!superiorClassOIDs.isEmpty())
+ {
+ final Iterator<String> iterator = superiorClassOIDs.iterator();
+
+ final String firstName = iterator.next();
+ if (iterator.hasNext())
+ {
+ buffer.append(" SUP ( ");
+ buffer.append(firstName);
+
+ while (iterator.hasNext())
+ {
+ buffer.append(" $ ");
+ buffer.append(iterator.next());
+ }
+
+ buffer.append(" )");
+ }
+ else
+ {
+ buffer.append(" SUP ");
+ buffer.append(firstName);
+ }
+ }
+
+ if (objectClassType != null)
+ {
+ buffer.append(" ");
+ buffer.append(objectClassType.toString());
+ }
+
+ if (!requiredAttributeOIDs.isEmpty())
+ {
+ final Iterator<String> iterator =
+ requiredAttributeOIDs.iterator();
+
+ final String firstName = iterator.next();
+ if (iterator.hasNext())
+ {
+ buffer.append(" MUST ( ");
+ buffer.append(firstName);
+
+ while (iterator.hasNext())
+ {
+ buffer.append(" $ ");
+ buffer.append(iterator.next());
+ }
+
+ buffer.append(" )");
+ }
+ else
+ {
+ buffer.append(" MUST ");
+ buffer.append(firstName);
+ }
+ }
+
+ if (!optionalAttributeOIDs.isEmpty())
+ {
+ final Iterator<String> iterator =
+ optionalAttributeOIDs.iterator();
+
+ final String firstName = iterator.next();
+ if (iterator.hasNext())
+ {
+ buffer.append(" MAY ( ");
+ buffer.append(firstName);
+
+ while (iterator.hasNext())
+ {
+ buffer.append(" $ ");
+ buffer.append(iterator.next());
+ }
+
+ buffer.append(" )");
+ }
+ else
+ {
+ buffer.append(" MAY ");
+ buffer.append(firstName);
+ }
+ }
+ }
+
+
+
+ @Override
+ void validate(List<Message> warnings, Schema schema)
+ throws SchemaException
+ {
+ if (validated)
+ {
+ return;
+ }
+ validated = true;
+
+ // Init a flag to check to inheritance from top (only needed for
+ // structural object classes) per RFC 4512
+ boolean derivesTop = objectClassType != ObjectClassType.STRUCTURAL;
+
+ if (!superiorClassOIDs.isEmpty())
+ {
+ superiorClasses =
+ new HashSet<ObjectClass>(superiorClassOIDs.size());
+ ObjectClass superiorClass;
+ for (final String superClassOid : superiorClassOIDs)
+ {
+ try
+ {
+ superiorClass = schema.getObjectClass(superClassOid);
+ }
+ catch (final UnknownSchemaElementException e)
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_OBJECTCLASS_UNKNOWN_SUPERIOR_CLASS.get(
+ oid, superClassOid);
+ throw new SchemaException(message, e);
+ }
+
+ // Make sure that the inheritance configuration is acceptable.
+ final ObjectClassType superiorType =
+ superiorClass.getObjectClassType();
+ switch (objectClassType)
+ {
+ case ABSTRACT:
+ // Abstract classes may only inherit from other abstract
+ // classes.
+ if (superiorType != ObjectClassType.ABSTRACT)
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_OBJECTCLASS_INVALID_SUPERIOR_TYPE.get(
+ oid, objectClassType.toString(), superiorType
+ .toString(), superiorClass.getNameOrOID());
+ throw new SchemaException(message);
+ }
+ break;
+
+ case AUXILIARY:
+ // Auxiliary classes may only inherit from abstract classes or
+ // other auxiliary classes.
+ if (superiorType != ObjectClassType.ABSTRACT
+ && superiorType != ObjectClassType.AUXILIARY)
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_OBJECTCLASS_INVALID_SUPERIOR_TYPE.get(
+ oid, objectClassType.toString(), superiorType
+ .toString(), superiorClass.getNameOrOID());
+ throw new SchemaException(message);
+ }
+ break;
+
+ case STRUCTURAL:
+ // Structural classes may only inherit from abstract classes
+ // or other structural classes.
+ if (superiorType != ObjectClassType.ABSTRACT
+ && superiorType != ObjectClassType.STRUCTURAL)
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_OBJECTCLASS_INVALID_SUPERIOR_TYPE.get(
+ oid, objectClassType.toString(), superiorType
+ .toString(), superiorClass.getNameOrOID());
+ throw new SchemaException(message);
+ }
+ break;
+ }
+
+ // All existing structural object classes defined in this schema
+ // are implicitly guaranteed to inherit from top
+ if (!derivesTop && superiorType == ObjectClassType.STRUCTURAL)
+ {
+ derivesTop = true;
+ }
+
+ // Validate superior object class so we can inherit its
+ // attributes.
+ superiorClass.validate(warnings, schema);
+
+ // Inherit all required attributes from superior class.
+ Iterator<AttributeType> i =
+ superiorClass.getRequiredAttributes().iterator();
+ if (i.hasNext() && requiredAttributes == Collections.EMPTY_SET)
+ {
+ requiredAttributes = new HashSet<AttributeType>();
+ }
+ while (i.hasNext())
+ {
+ requiredAttributes.add(i.next());
+ }
+
+ // Inherit all optional attributes from superior class.
+ i = superiorClass.getRequiredAttributes().iterator();
+ if (i.hasNext() && requiredAttributes == Collections.EMPTY_SET)
+ {
+ requiredAttributes = new HashSet<AttributeType>();
+ }
+ while (i.hasNext())
+ {
+ requiredAttributes.add(i.next());
+ }
+
+ superiorClasses.add(superiorClass);
+ }
+ }
+
+ if (!derivesTop)
+ {
+ derivesTop = isDescendantOf(schema.getObjectClass("2.5.6.0"));
+ }
+
+ // Structural classes must have the "top" objectclass somewhere
+ // in the superior chain.
+ if (!derivesTop)
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_OBJECTCLASS_STRUCTURAL_SUPERIOR_NOT_TOP
+ .get(oid);
+ throw new SchemaException(message);
+ }
+
+ if (oid.equals(EXTENSIBLE_OBJECT_OBJECTCLASS_OID))
+ {
+ declaredOptionalAttributes =
+ new HashSet<AttributeType>(requiredAttributeOIDs.size());
+ for (final AttributeType attributeType : schema
+ .getAttributeTypes())
+ {
+ if (attributeType.getUsage() == AttributeUsage.USER_APPLICATIONS)
+ {
+ declaredOptionalAttributes.add(attributeType);
+ }
+ }
+ optionalAttributes = declaredRequiredAttributes;
+ return;
+ }
+
+ if (!requiredAttributeOIDs.isEmpty())
+ {
+ declaredRequiredAttributes =
+ new HashSet<AttributeType>(requiredAttributeOIDs.size());
+ AttributeType attributeType;
+ for (final String requiredAttribute : requiredAttributeOIDs)
+ {
+ try
+ {
+ attributeType = schema.getAttributeType(requiredAttribute);
+ }
+ catch (final UnknownSchemaElementException e)
+ {
+ // This isn't good because it means that the objectclass
+ // requires an attribute type that we don't know anything
+ // about.
+ final Message message =
+ WARN_ATTR_SYNTAX_OBJECTCLASS_UNKNOWN_REQUIRED_ATTR.get(
+ oid, requiredAttribute);
+ throw new SchemaException(message, e);
+ }
+ declaredRequiredAttributes.add(attributeType);
+ }
+ if (requiredAttributes == Collections.EMPTY_SET)
+ {
+ requiredAttributes = declaredRequiredAttributes;
+ }
+ else
+ {
+ requiredAttributes.addAll(declaredRequiredAttributes);
+ }
+ }
+
+ if (!optionalAttributeOIDs.isEmpty())
+ {
+ declaredOptionalAttributes =
+ new HashSet<AttributeType>(optionalAttributeOIDs.size());
+ AttributeType attributeType;
+ for (final String optionalAttribute : optionalAttributeOIDs)
+ {
+ try
+ {
+ attributeType = schema.getAttributeType(optionalAttribute);
+ }
+ catch (final UnknownSchemaElementException e)
+ {
+ // This isn't good because it means that the objectclass
+ // requires an attribute type that we don't know anything
+ // about.
+ final Message message =
+ WARN_ATTR_SYNTAX_OBJECTCLASS_UNKNOWN_OPTIONAL_ATTR.get(
+ oid, optionalAttribute);
+ throw new SchemaException(message, e);
+ }
+ declaredOptionalAttributes.add(attributeType);
+ }
+ if (optionalAttributes == Collections.EMPTY_SET)
+ {
+ optionalAttributes = declaredOptionalAttributes;
+ }
+ else
+ {
+ optionalAttributes.addAll(declaredOptionalAttributes);
+ }
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/ObjectClassSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/ObjectClassSyntaxImpl.java
new file mode 100644
index 0000000..23a294a
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/ObjectClassSyntaxImpl.java
@@ -0,0 +1,214 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_ILLEGAL_TOKEN;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_OBJECTCLASS_EMPTY_VALUE;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_OBJECTCLASS_EXPECTED_OPEN_PARENTHESIS;
+import static org.opends.sdk.schema.SchemaConstants.EMR_OID_FIRST_COMPONENT_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_OBJECTCLASS_NAME;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class implements the object class description syntax, which is
+ * used to hold objectclass definitions in the server schema. The format
+ * of this syntax is defined in RFC 2252.
+ */
+final class ObjectClassSyntaxImpl extends AbstractSyntaxImpl
+{
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_OID_FIRST_COMPONENT_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_OBJECTCLASS_NAME;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // We'll use the decodeObjectClass method to determine if the value
+ // is acceptable.
+ try
+ {
+ final String definition = value.toString();
+ final SubstringReader reader = new SubstringReader(definition);
+
+ // We'll do this a character at a time. First, skip over any
+ // leading whitespace.
+ reader.skipWhitespaces();
+
+ if (reader.remaining() <= 0)
+ {
+ // This means that the value was empty or contained only
+ // whitespace. That is illegal.
+ final Message message =
+ ERR_ATTR_SYNTAX_OBJECTCLASS_EMPTY_VALUE.get();
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("ObjectClassSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ // The next character must be an open parenthesis. If it is not,
+ // then that is an error.
+ final char c = reader.read();
+ if (c != '(')
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_OBJECTCLASS_EXPECTED_OPEN_PARENTHESIS.get(
+ definition, (reader.pos() - 1), String.valueOf(c));
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("ObjectClassSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+
+ // Skip over any spaces immediately following the opening
+ // parenthesis.
+ reader.skipWhitespaces();
+
+ // The next set of characters must be the OID.
+ SchemaUtils.readOID(reader);
+
+ // At this point, we should have a pretty specific syntax that
+ // describes what may come next, but some of the components are
+ // optional and it would be pretty easy to put something in the
+ // wrong order, so we will be very flexible about what we can
+ // accept. Just look at the next token, figure out what it is and
+ // how to treat what comes after it, then repeat until we get to
+ // the end of the value. But before we start, set default values
+ // for everything else we might need to know.
+ while (true)
+ {
+ final String tokenName = SchemaUtils.readTokenName(reader);
+
+ if (tokenName == null)
+ {
+ // No more tokens.
+ break;
+ }
+ else if (tokenName.equalsIgnoreCase("name"))
+ {
+ SchemaUtils.readNameDescriptors(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("desc"))
+ {
+ // This specifies the description for the attribute type. It
+ // is an arbitrary string of characters enclosed in single
+ // quotes.
+ SchemaUtils.readQuotedString(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("obsolete"))
+ {
+ // This indicates whether the attribute type should be
+ // considered obsolete. We do not need to do any more parsing
+ // for this token.
+ }
+ else if (tokenName.equalsIgnoreCase("sup"))
+ {
+ SchemaUtils.readOIDs(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("abstract"))
+ {
+ // This indicates that entries must not include this
+ // objectclass unless they also include a non-abstract
+ // objectclass that inherits from this class. We do not need
+ // any more parsing for this token.
+ }
+ else if (tokenName.equalsIgnoreCase("structural"))
+ {
+ // This indicates that this is a structural objectclass.
+ // We do not need any more parsing for this token.
+ }
+ else if (tokenName.equalsIgnoreCase("auxiliary"))
+ {
+ // This indicates that this is an auxiliary objectclass.
+ // We do not need any more parsing for this token.
+ }
+ else if (tokenName.equalsIgnoreCase("must"))
+ {
+ SchemaUtils.readOIDs(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("may"))
+ {
+ SchemaUtils.readOIDs(reader);
+ }
+ else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+ {
+ // This must be a non-standard property and it must be
+ // followed by either a single definition in single quotes or
+ // an open parenthesis followed by one or more values in
+ // single quotes separated by spaces followed by a close
+ // parenthesis.
+ SchemaUtils.readExtensions(reader);
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+ final DecodeException e = DecodeException.error(message);
+ StaticUtils.DEBUG_LOG.throwing("ObjectClassSyntax",
+ "valueIsAcceptable", e);
+ throw e;
+ }
+ }
+ return true;
+ }
+ catch (final DecodeException de)
+ {
+ invalidReason.append(de.getMessageObject());
+ return false;
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/ObjectClassType.java b/sdk/src/org/opends/sdk/schema/ObjectClassType.java
new file mode 100644
index 0000000..4affff3
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/ObjectClassType.java
@@ -0,0 +1,82 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+/**
+ * This enumeration defines the set of possible objectclass types that
+ * may be used, as defined in RFC 2252.
+ */
+public enum ObjectClassType
+{
+ /**
+ * The objectclass type that to use for classes declared "abstract".
+ */
+ ABSTRACT("ABSTRACT"),
+
+ /**
+ * The objectclass type that to use for classes declared "structural".
+ */
+ STRUCTURAL("STRUCTURAL"),
+
+ /**
+ * The objectclass type that to use for classes declared "auxiliary".
+ */
+ AUXILIARY("AUXILIARY");
+
+ // The string representation of this objectclass type.
+ private final String typeString;
+
+
+
+ /**
+ * Creates a new objectclass type with the provided string
+ * representation.
+ *
+ * @param typeString
+ * The string representation for this objectclass type.
+ */
+ private ObjectClassType(String typeString)
+ {
+ this.typeString = typeString;
+ }
+
+
+
+ /**
+ * Retrieves a string representation of this objectclass type.
+ *
+ * @return A string representation of this objectclass type.
+ */
+ @Override
+ public String toString()
+ {
+ return typeString;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/ObjectIdentifierEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/ObjectIdentifierEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..b006242
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/ObjectIdentifierEqualityMatchingRuleImpl.java
@@ -0,0 +1,188 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.Assertion;
+import org.opends.sdk.ConditionResult;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class defines the objectIdentifierMatch matching rule defined in
+ * X.520 and referenced in RFC 2252. This expects to work on OIDs and
+ * will match either an attribute/objectclass name or a numeric OID.
+ * NOTE: This matching rule requires a schema to lookup object
+ * identifiers in the descriptor form.
+ */
+final class ObjectIdentifierEqualityMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+ static class OIDAssertion implements Assertion
+ {
+ private final String oid;
+
+
+
+ OIDAssertion(String oid)
+ {
+ this.oid = oid;
+ }
+
+
+
+ public ConditionResult matches(ByteSequence attributeValue)
+ {
+ final String attrStr = attributeValue.toString();
+
+ // We should have normalized all values to OIDs. If not, we know
+ // the descriptor form is not valid in the schema.
+ if (attrStr.length() == 0
+ || !StaticUtils.isDigit(attrStr.charAt(0)))
+ {
+ return ConditionResult.UNDEFINED;
+ }
+ if (oid.length() == 0 || !StaticUtils.isDigit(oid.charAt(0)))
+ {
+ return ConditionResult.UNDEFINED;
+ }
+
+ return attrStr.equals(oid) ? ConditionResult.TRUE
+ : ConditionResult.FALSE;
+ }
+ }
+
+
+
+ static String resolveNames(Schema schema, String oid)
+ {
+ if (!StaticUtils.isDigit(oid.charAt(0)))
+ {
+ // Do an best effort attempt to normalize names to OIDs.
+
+ String schemaName = null;
+
+ if (schema.hasAttributeType(oid))
+ {
+ schemaName = schema.getAttributeType(oid).getOID();
+ }
+
+ if (schemaName == null)
+ {
+ if (schema.hasDITContentRule(oid))
+ {
+ schemaName =
+ schema.getDITContentRule(oid).getStructuralClass()
+ .getOID();
+ }
+ }
+
+ if (schemaName == null)
+ {
+ if (schema.hasSyntax(oid))
+ {
+ schemaName = schema.getSyntax(oid).getOID();
+ }
+ }
+
+ if (schemaName == null)
+ {
+ if (schema.hasObjectClass(oid))
+ {
+ schemaName = schema.getObjectClass(oid).getOID();
+ }
+ }
+
+ if (schemaName == null)
+ {
+ if (schema.hasMatchingRule(oid))
+ {
+ schemaName = schema.getMatchingRule(oid).getOID();
+ }
+ }
+
+ if (schemaName == null)
+ {
+ if (schema.hasMatchingRuleUse(oid))
+ {
+ schemaName =
+ schema.getMatchingRuleUse(oid).getMatchingRule().getOID();
+ }
+ }
+
+ if (schemaName == null)
+ {
+ if (schema.hasNameForm(oid))
+ {
+ schemaName = schema.getNameForm(oid).getOID();
+ }
+ }
+
+ if (schemaName != null)
+ {
+ return schemaName;
+ }
+ else
+ {
+ return StaticUtils.toLowerCase(oid);
+ }
+ }
+ return oid;
+ }
+
+
+
+ @Override
+ public Assertion getAssertion(Schema schema, ByteSequence value)
+ throws DecodeException
+ {
+ final String definition = value.toString();
+ final SubstringReader reader = new SubstringReader(definition);
+ final String normalized =
+ resolveNames(schema, SchemaUtils.readOID(reader));
+
+ return new OIDAssertion(normalized);
+ }
+
+
+
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value) throws DecodeException
+ {
+ final String definition = value.toString();
+ final SubstringReader reader = new SubstringReader(definition);
+ final String normalized =
+ resolveNames(schema, SchemaUtils.readOID(reader));
+ return ByteString.valueOf(normalized);
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/ObjectIdentifierFirstComponentEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/ObjectIdentifierFirstComponentEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..a743223
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/ObjectIdentifierFirstComponentEqualityMatchingRuleImpl.java
@@ -0,0 +1,110 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_EMPTY_VALUE;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_EXPECTED_OPEN_PARENTHESIS;
+
+import org.opends.messages.Message;
+import org.opends.sdk.Assertion;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class implements the objectIdentifierFirstComponentMatch
+ * matching rule defined in X.520 and referenced in RFC 2252. This rule
+ * is intended for use with attributes whose values contain a set of
+ * parentheses enclosing a space-delimited set of names and/or
+ * name-value pairs (like attribute type or objectclass descriptions) in
+ * which the "first component" is the first item after the opening
+ * parenthesis.
+ */
+final class ObjectIdentifierFirstComponentEqualityMatchingRuleImpl
+ extends AbstractMatchingRuleImpl
+{
+ @Override
+ public Assertion getAssertion(Schema schema, ByteSequence value)
+ throws DecodeException
+ {
+ final String definition = value.toString();
+ final SubstringReader reader = new SubstringReader(definition);
+ final String normalized =
+ ObjectIdentifierEqualityMatchingRuleImpl.resolveNames(schema,
+ SchemaUtils.readOID(reader));
+
+ return new ObjectIdentifierEqualityMatchingRuleImpl.OIDAssertion(
+ normalized);
+ }
+
+
+
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value) throws DecodeException
+ {
+ final String definition = value.toString();
+ final SubstringReader reader = new SubstringReader(definition);
+
+ // We'll do this a character at a time. First, skip over any leading
+ // whitespace.
+ reader.skipWhitespaces();
+
+ if (reader.remaining() <= 0)
+ {
+ // This means that the value was empty or contained only
+ // whitespace. That is illegal.
+ final Message message = ERR_ATTR_SYNTAX_EMPTY_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ // The next character must be an open parenthesis. If it is not,
+ // then that is an error.
+ final char c = reader.read();
+ if (c != '(')
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_EXPECTED_OPEN_PARENTHESIS.get(definition,
+ (reader.pos() - 1), String.valueOf(c));
+ throw DecodeException.error(message);
+ }
+
+ // Skip over any spaces immediately following the opening
+ // parenthesis.
+ reader.skipWhitespaces();
+
+ // The next set of characters must be the OID.
+ final String normalized =
+ ObjectIdentifierEqualityMatchingRuleImpl.resolveNames(schema,
+ SchemaUtils.readOID(reader));
+ return ByteString.valueOf(normalized);
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/OctetStringEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/OctetStringEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..83ab9e2
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/OctetStringEqualityMatchingRuleImpl.java
@@ -0,0 +1,49 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the octetStringMatch matching rule defined in
+ * X.520. It will be used as the default equality matching rule for the
+ * binary and octet string syntaxes.
+ */
+final class OctetStringEqualityMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value)
+ {
+ return value.toByteString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/OctetStringOrderingMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/OctetStringOrderingMatchingRuleImpl.java
new file mode 100644
index 0000000..1c08743
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/OctetStringOrderingMatchingRuleImpl.java
@@ -0,0 +1,49 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the octetStringOrderingMatch matching rule defined
+ * in X.520. This will be the default ordering matching rule for the
+ * binary and octet string syntaxes.
+ */
+final class OctetStringOrderingMatchingRuleImpl extends
+ AbstractOrderingMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value)
+ {
+ return value.toByteString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/OctetStringSubstringMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/OctetStringSubstringMatchingRuleImpl.java
new file mode 100644
index 0000000..57d7354
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/OctetStringSubstringMatchingRuleImpl.java
@@ -0,0 +1,49 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the octetStringSubstringsMatch matching rule
+ * defined in X.520. It will be used as the default substring matching
+ * rule for the binary and octet string syntaxes.
+ */
+final class OctetStringSubstringMatchingRuleImpl extends
+ AbstractSubstringMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value)
+ {
+ return value.toByteString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/OctetStringSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/OctetStringSyntaxImpl.java
new file mode 100644
index 0000000..ccc4977
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/OctetStringSyntaxImpl.java
@@ -0,0 +1,99 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.EMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_OCTET_STRING_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the octet string attribute syntax, which is
+ * equivalent to the binary syntax and should be considered a
+ * replacement for it. Equality, ordering, and substring matching will
+ * be allowed by default.
+ */
+final class OctetStringSyntaxImpl extends AbstractSyntaxImpl
+{
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_OCTET_STRING_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_OCTET_STRING_NAME;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_OCTET_STRING_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // All values will be acceptable for the octet string syntax.
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/OtherMailboxSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/OtherMailboxSyntaxImpl.java
new file mode 100644
index 0000000..3956aea
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/OtherMailboxSyntaxImpl.java
@@ -0,0 +1,177 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.schema.SchemaConstants.EMR_CASE_IGNORE_LIST_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_IGNORE_LIST_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_OTHER_MAILBOX_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the other mailbox attribute syntax, which
+ * consists of a printable string component (the mailbox type) followed
+ * by a dollar sign and an IA5 string component (the mailbox). Equality
+ * and substring matching will be allowed by default.
+ */
+final class OtherMailboxSyntaxImpl extends AbstractSyntaxImpl
+{
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_CASE_IGNORE_LIST_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_OTHER_MAILBOX_NAME;
+ }
+
+
+
+ @Override
+ public String getSubstringMatchingRule()
+ {
+ return SMR_CASE_IGNORE_LIST_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // Check to see if the provided value was null. If so, then that's
+ // not acceptable.
+ if (value == null)
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_OTHER_MAILBOX_EMPTY_VALUE
+ .get());
+ return false;
+ }
+
+ // Get the value as a string and determine its length. If it is
+ // empty, then that's not acceptable.
+ final String valueString = value.toString();
+ final int valueLength = valueString.length();
+ if (valueLength == 0)
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_OTHER_MAILBOX_EMPTY_VALUE
+ .get());
+ return false;
+ }
+
+ // Iterate through the characters in the vale until we find a dollar
+ // sign. Every character up to that point must be a printable string
+ // character.
+ int pos = 0;
+ for (; pos < valueLength; pos++)
+ {
+ final char c = valueString.charAt(pos);
+ if (c == '$')
+ {
+ if (pos == 0)
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_OTHER_MAILBOX_NO_MBTYPE
+ .get(valueString));
+ return false;
+ }
+
+ pos++;
+ break;
+ }
+ else if (!PrintableStringSyntaxImpl.isPrintableCharacter(c))
+ {
+
+ invalidReason
+ .append(ERR_ATTR_SYNTAX_OTHER_MAILBOX_ILLEGAL_MBTYPE_CHAR
+ .get(valueString, String.valueOf(c), pos));
+ return false;
+ }
+ }
+
+ // Make sure there is at least one character left for the mailbox.
+ if (pos >= valueLength)
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_OTHER_MAILBOX_NO_MAILBOX
+ .get(valueString));
+ return false;
+ }
+
+ // The remaining characters in the value must be IA5 (ASCII)
+ // characters.
+ for (; pos < valueLength; pos++)
+ {
+ final char c = valueString.charAt(pos);
+ if (c != (c & 0x7F))
+ {
+
+ invalidReason
+ .append(ERR_ATTR_SYNTAX_OTHER_MAILBOX_ILLEGAL_MB_CHAR.get(
+ valueString, String.valueOf(c), pos));
+ return false;
+ }
+ }
+
+ // If we've gotten here, then the value is OK.
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/PostalAddressSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/PostalAddressSyntaxImpl.java
new file mode 100644
index 0000000..bb90087
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/PostalAddressSyntaxImpl.java
@@ -0,0 +1,101 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.EMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_POSTAL_ADDRESS_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the postal address attribute syntax, which is a
+ * list of UCS (Universal Character Set, as defined in the ISO 10646
+ * specification and includes UTF-8 and UTF-16) strings separated by
+ * dollar signs. By default, they will be treated in a case-insensitive
+ * manner, and equality and substring matching will be allowed.
+ */
+final class PostalAddressSyntaxImpl extends AbstractSyntaxImpl
+{
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_POSTAL_ADDRESS_NAME;
+ }
+
+
+
+ @Override
+ public String getSubstringMatchingRule()
+ {
+ return SMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // We'll allow any value.
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/PresentationAddressEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/PresentationAddressEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..5c80c28
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/PresentationAddressEqualityMatchingRuleImpl.java
@@ -0,0 +1,85 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the presentationAddressMatch matching rule
+ * defined in X.520 and referenced in RFC 2252. However, since this
+ * matching rule and the associated syntax have been deprecated, this
+ * matching rule behaves exactly like the caseIgnoreMatch rule.
+ */
+final class PresentationAddressEqualityMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value)
+ {
+ final StringBuilder buffer = new StringBuilder();
+ prepareUnicode(buffer, value, TRIM, CASE_FOLD);
+
+ final int bufferLength = buffer.length();
+ if (bufferLength == 0)
+ {
+ if (value.length() > 0)
+ {
+ // This should only happen if the value is composed entirely of
+ // spaces. In that case, the normalized value is a single space.
+ return SchemaConstants.SINGLE_SPACE_VALUE;
+ }
+ else
+ {
+ // The value is empty, so it is already normalized.
+ return ByteString.empty();
+ }
+ }
+
+ // Replace any consecutive spaces with a single space.
+ for (int pos = bufferLength - 1; pos > 0; pos--)
+ {
+ if (buffer.charAt(pos) == ' ')
+ {
+ if (buffer.charAt(pos - 1) == ' ')
+ {
+ buffer.delete(pos, pos + 1);
+ }
+ }
+ }
+
+ return ByteString.valueOf(buffer.toString());
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/PresentationAddressSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/PresentationAddressSyntaxImpl.java
new file mode 100644
index 0000000..ad4531d
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/PresentationAddressSyntaxImpl.java
@@ -0,0 +1,113 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.*;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the presentation address attribute syntax,
+ * which is defined in RFC 1278. However, because this LDAP syntax is
+ * being deprecated, this implementation behaves exactly like the
+ * directory string syntax.
+ */
+final class PresentationAddressSyntaxImpl extends AbstractSyntaxImpl
+{
+ @Override
+ public String getApproximateMatchingRule()
+ {
+ return AMR_DOUBLE_METAPHONE_OID;
+ }
+
+
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_PRESENTATION_ADDRESS_NAME;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_CASE_IGNORE_OID;
+ }
+
+
+
+ @Override
+ public String getSubstringMatchingRule()
+ {
+ return SMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // We will accept any value for this syntax.
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/PrintableStringSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/PrintableStringSyntaxImpl.java
new file mode 100644
index 0000000..06b2c20
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/PrintableStringSyntaxImpl.java
@@ -0,0 +1,250 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_PRINTABLE_STRING_EMPTY_VALUE;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_PRINTABLE_STRING_ILLEGAL_CHARACTER;
+import static org.opends.sdk.schema.SchemaConstants.*;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the printable string attribute syntax, which is
+ * simply a string of characters from a limited ASCII character set
+ * (uppercase and lowercase letters, numeric digits, the space, and a
+ * set of various symbols). By default, they will be treated in a
+ * case-insensitive manner, and equality, ordering, substring, and
+ * approximate matching will be allowed.
+ */
+final class PrintableStringSyntaxImpl extends AbstractSyntaxImpl
+{
+
+ /**
+ * Indicates whether the provided character is a valid printable
+ * character.
+ *
+ * @param c
+ * The character for which to make the determination.
+ * @return <CODE>true</CODE> if the provided character is a printable
+ * character, or <CODE>false</CODE> if not.
+ */
+ static boolean isPrintableCharacter(char c)
+ {
+ switch (c)
+ {
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ case 'g':
+ case 'h':
+ case 'i':
+ case 'j':
+ case 'k':
+ case 'l':
+ case 'm':
+ case 'n':
+ case 'o':
+ case 'p':
+ case 'q':
+ case 'r':
+ case 's':
+ case 't':
+ case 'u':
+ case 'v':
+ case 'w':
+ case 'x':
+ case 'y':
+ case 'z':
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ case 'G':
+ case 'H':
+ case 'I':
+ case 'J':
+ case 'K':
+ case 'L':
+ case 'M':
+ case 'N':
+ case 'O':
+ case 'P':
+ case 'Q':
+ case 'R':
+ case 'S':
+ case 'T':
+ case 'U':
+ case 'V':
+ case 'W':
+ case 'X':
+ case 'Y':
+ case 'Z':
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '\'':
+ case '(':
+ case ')':
+ case '+':
+ case ',':
+ case '-':
+ case '.':
+ case '=':
+ case '/':
+ case ':':
+ case '?':
+ case ' ':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+
+
+ @Override
+ public String getApproximateMatchingRule()
+ {
+ return AMR_DOUBLE_METAPHONE_OID;
+ }
+
+
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_PRINTABLE_STRING_NAME;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_CASE_IGNORE_OID;
+ }
+
+
+
+ @Override
+ public String getSubstringMatchingRule()
+ {
+ return SMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // Check to see if the provided value was null. If so, then that's
+ // not acceptable.
+ if (value == null)
+ {
+
+ invalidReason
+ .append(WARN_ATTR_SYNTAX_PRINTABLE_STRING_EMPTY_VALUE.get());
+ return false;
+ }
+
+ // Get the value as a string and determine its length. If it is
+ // empty, then that's not acceptable.
+ final String valueString = value.toString();
+ final int valueLength = valueString.length();
+ if (valueLength == 0)
+ {
+
+ invalidReason
+ .append(WARN_ATTR_SYNTAX_PRINTABLE_STRING_EMPTY_VALUE.get());
+ return false;
+ }
+
+ // Iterate through all the characters and see if they are
+ // acceptable.
+ for (int i = 0; i < valueLength; i++)
+ {
+ final char c = valueString.charAt(i);
+ if (!isPrintableCharacter(c))
+ {
+
+ invalidReason
+ .append(WARN_ATTR_SYNTAX_PRINTABLE_STRING_ILLEGAL_CHARACTER
+ .get(valueString, String.valueOf(c), i));
+ return false;
+ }
+ }
+
+ // If we've gotten here, then the value is OK.
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/ProtocolInformationEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/ProtocolInformationEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..c21b29e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/ProtocolInformationEqualityMatchingRuleImpl.java
@@ -0,0 +1,85 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the protocolInformationMatch matching rule
+ * defined in X.520 and referenced in RFC 2252. However, since this
+ * matching rule and the associated syntax have been deprecated, this
+ * matching rule behaves exactly like the caseIgnoreMatch rule.
+ */
+final class ProtocolInformationEqualityMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value)
+ {
+ final StringBuilder buffer = new StringBuilder();
+ prepareUnicode(buffer, value, TRIM, CASE_FOLD);
+
+ final int bufferLength = buffer.length();
+ if (bufferLength == 0)
+ {
+ if (value.length() > 0)
+ {
+ // This should only happen if the value is composed entirely of
+ // spaces. In that case, the normalized value is a single space.
+ return SchemaConstants.SINGLE_SPACE_VALUE;
+ }
+ else
+ {
+ // The value is empty, so it is already normalized.
+ return ByteString.empty();
+ }
+ }
+
+ // Replace any consecutive spaces with a single space.
+ for (int pos = bufferLength - 1; pos > 0; pos--)
+ {
+ if (buffer.charAt(pos) == ' ')
+ {
+ if (buffer.charAt(pos - 1) == ' ')
+ {
+ buffer.delete(pos, pos + 1);
+ }
+ }
+ }
+
+ return ByteString.valueOf(buffer.toString());
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/ProtocolInformationSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/ProtocolInformationSyntaxImpl.java
new file mode 100644
index 0000000..fb9c8de
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/ProtocolInformationSyntaxImpl.java
@@ -0,0 +1,113 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.*;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the protocol information attribute syntax,
+ * which is being deprecated. As such, this implementation behaves
+ * exactly like the directory string syntax.
+ */
+final class ProtocolInformationSyntaxImpl extends AbstractSyntaxImpl
+{
+
+ @Override
+ public String getApproximateMatchingRule()
+ {
+ return AMR_DOUBLE_METAPHONE_OID;
+ }
+
+
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_PROTOCOL_INFORMATION_NAME;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_CASE_IGNORE_OID;
+ }
+
+
+
+ @Override
+ public String getSubstringMatchingRule()
+ {
+ return SMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // We will accept any value for this syntax.
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/RegexSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/RegexSyntaxImpl.java
new file mode 100644
index 0000000..45ff406
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/RegexSyntaxImpl.java
@@ -0,0 +1,127 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_LDAPSYNTAX_REGEX_INVALID_VALUE;
+import static org.opends.sdk.schema.SchemaConstants.AMR_DOUBLE_METAPHONE_OID;
+import static org.opends.sdk.schema.SchemaConstants.EMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_IGNORE_OID;
+
+import java.util.regex.Pattern;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class provides a regex mechanism where a new syntax and its
+ * corresponding matching rules can be created on-the-fly. A regex
+ * syntax is an LDAPSyntaxDescriptionSyntax with X-PATTERN extension.
+ */
+final class RegexSyntaxImpl extends AbstractSyntaxImpl
+{
+ // The Pattern associated with the regex.
+ private final Pattern pattern;
+
+
+
+ RegexSyntaxImpl(Pattern pattern)
+ {
+ Validator.ensureNotNull(pattern);
+ this.pattern = pattern;
+ }
+
+
+
+ @Override
+ public String getApproximateMatchingRule()
+ {
+ return AMR_DOUBLE_METAPHONE_OID;
+ }
+
+
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return "Regex(" + pattern.toString() + ")";
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_CASE_IGNORE_OID;
+ }
+
+
+
+ @Override
+ public String getSubstringMatchingRule()
+ {
+ return SMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ final String strValue = value.toString();
+ final boolean matches = pattern.matcher(strValue).matches();
+ if (!matches)
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_LDAPSYNTAX_REGEX_INVALID_VALUE.get(strValue,
+ pattern.pattern());
+ invalidReason.append(message);
+ }
+ return matches;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/Schema.java b/sdk/src/org/opends/sdk/schema/Schema.java
new file mode 100644
index 0000000..227c522
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/Schema.java
@@ -0,0 +1,2531 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.sdk.*;
+import org.opends.sdk.responses.SearchResultEntry;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class defines a data structure that holds information about the
+ * components of the LDAP schema. It includes the following kinds of
+ * elements:
+ * <UL>
+ * <LI>Attribute type definitions</LI>
+ * <LI>Object class definitions</LI>
+ * <LI>Attribute syntax definitions</LI>
+ * <LI>Matching rule definitions</LI>
+ * <LI>Matching rule use definitions</LI>
+ * <LI>DIT content rule definitions</LI>
+ * <LI>DIT structure rule definitions</LI>
+ * <LI>Name form definitions</LI>
+ * </UL>
+ */
+public final class Schema
+{
+ private static class EmptyImpl implements Impl
+ {
+ private final Map<SchemaLocal<?>, Object> attachments;
+
+ private final SchemaCompatOptions options;
+
+
+
+ private EmptyImpl()
+ {
+ this.options = SchemaCompatOptions.defaultOptions();
+ this.attachments = new WeakHashMap<SchemaLocal<?>, Object>();
+ }
+
+
+
+ @SuppressWarnings("unchecked")
+ public <T> T getAttachment(SchemaLocal<T> attachment)
+ {
+ T o;
+ synchronized (attachments)
+ {
+ o = (T) attachments.get(attachment);
+ if (o == null)
+ {
+ o = attachment.initialValue();
+ if (o != null)
+ {
+ attachments.put(attachment, o);
+ }
+ }
+ }
+ return o;
+ }
+
+
+
+ public AttributeType getAttributeType(String name)
+ {
+ // Construct an placeholder attribute type with the given name,
+ // the default matching rule, and the default syntax. The OID of
+ // the attribute will be the normalized OID alias with "-oid"
+ // appended to the given name.
+ final StringBuilder builder = new StringBuilder(name.length() + 4);
+ StaticUtils.toLowerCase(name, builder);
+ builder.append("-oid");
+ final String noid = builder.toString();
+
+ return new AttributeType(noid, Collections.singletonList(name),
+ "", Schema.getDefaultMatchingRule(), Schema
+ .getDefaultSyntax());
+ }
+
+
+
+ public Collection<AttributeType> getAttributeTypes()
+ {
+ return Collections.emptyList();
+ }
+
+
+
+ public List<AttributeType> getAttributeTypesByName(String name)
+ {
+ return Collections.emptyList();
+ }
+
+
+
+ public DITContentRule getDITContentRule(String name)
+ throws UnknownSchemaElementException
+ {
+ throw new UnknownSchemaElementException(WARN_DCR_UNKNOWN
+ .get(name));
+ }
+
+
+
+ public Collection<DITContentRule> getDITContentRules()
+ {
+ return Collections.emptyList();
+ }
+
+
+
+ public Collection<DITContentRule> getDITContentRulesByName(
+ String name)
+ {
+ return Collections.emptyList();
+ }
+
+
+
+ public DITStructureRule getDITStructureRule(int ruleID)
+ throws UnknownSchemaElementException
+ {
+ throw new UnknownSchemaElementException(WARN_DSR_UNKNOWN
+ .get(String.valueOf(ruleID)));
+ }
+
+
+
+ public Collection<DITStructureRule> getDITStructureRulesByName(
+ String name)
+ {
+ return Collections.emptyList();
+ }
+
+
+
+ public Collection<DITStructureRule> getDITStructureRulesByNameForm(
+ NameForm nameForm)
+ {
+ return Collections.emptyList();
+ }
+
+
+
+ public Collection<DITStructureRule> getDITStuctureRules()
+ {
+ return Collections.emptyList();
+ }
+
+
+
+ public MatchingRule getMatchingRule(String name)
+ throws UnknownSchemaElementException
+ {
+ throw new UnknownSchemaElementException(WARN_MR_UNKNOWN.get(name));
+ }
+
+
+
+ public Collection<MatchingRule> getMatchingRules()
+ {
+ return Collections.emptyList();
+ }
+
+
+
+ public Collection<MatchingRule> getMatchingRulesByName(String name)
+ {
+ return Collections.emptyList();
+ }
+
+
+
+ public MatchingRuleUse getMatchingRuleUse(MatchingRule matchingRule)
+ throws UnknownSchemaElementException
+ {
+ return getMatchingRuleUse(matchingRule.getOID());
+ }
+
+
+
+ public MatchingRuleUse getMatchingRuleUse(String name)
+ throws UnknownSchemaElementException
+ {
+ throw new UnknownSchemaElementException(WARN_MRU_UNKNOWN
+ .get(name));
+ }
+
+
+
+ public Collection<MatchingRuleUse> getMatchingRuleUses()
+ {
+ return Collections.emptyList();
+ }
+
+
+
+ public Collection<MatchingRuleUse> getMatchingRuleUsesByName(
+ String name)
+ {
+ return Collections.emptyList();
+ }
+
+
+
+ public NameForm getNameForm(String name)
+ throws UnknownSchemaElementException
+ {
+ throw new UnknownSchemaElementException(WARN_NAMEFORM_UNKNOWN
+ .get(name));
+ }
+
+
+
+ public Collection<NameForm> getNameFormByObjectClass(
+ ObjectClass structuralClass)
+ {
+ return Collections.emptyList();
+ }
+
+
+
+ public Collection<NameForm> getNameForms()
+ {
+ return Collections.emptyList();
+ }
+
+
+
+ public Collection<NameForm> getNameFormsByName(String name)
+ {
+ return Collections.emptyList();
+ }
+
+
+
+ public ObjectClass getObjectClass(String name)
+ throws UnknownSchemaElementException
+ {
+ throw new UnknownSchemaElementException(WARN_OBJECTCLASS_UNKNOWN
+ .get(name));
+ }
+
+
+
+ public Collection<ObjectClass> getObjectClasses()
+ {
+ return Collections.emptyList();
+ }
+
+
+
+ public Collection<ObjectClass> getObjectClassesByName(String name)
+ {
+ return Collections.emptyList();
+ }
+
+
+
+ public SchemaCompatOptions getSchemaCompatOptions()
+ {
+ return options;
+ }
+
+
+
+ public Syntax getSyntax(String numericOID)
+ {
+ // Fake up a syntax substituted by the default syntax.
+ return new Syntax(numericOID);
+ }
+
+
+
+ public Collection<Syntax> getSyntaxes()
+ {
+ return Collections.emptyList();
+ }
+
+
+
+ public boolean hasAttributeType(String name)
+ {
+ // In theory a non-strict schema always contains the requested
+ // attribute type, so we could always return true. However, we
+ // should provide a way for callers to differentiate between a
+ // real attribute type and a faked up attribute type.
+ return false;
+ }
+
+
+
+ public boolean hasDITContentRule(String name)
+ {
+ return false;
+ }
+
+
+
+ public boolean hasDITStructureRule(int ruleID)
+ {
+ return false;
+ }
+
+
+
+ public boolean hasMatchingRule(String name)
+ {
+ return false;
+ }
+
+
+
+ public boolean hasMatchingRuleUse(String name)
+ {
+ return false;
+ }
+
+
+
+ public boolean hasNameForm(String name)
+ {
+ return false;
+ }
+
+
+
+ public boolean hasObjectClass(String name)
+ {
+ return false;
+ }
+
+
+
+ public boolean hasSyntax(String numericOID)
+ {
+ return false;
+ }
+
+
+
+ public boolean isStrict()
+ {
+ return false;
+ }
+
+
+
+ @SuppressWarnings("unchecked")
+ public <T> T removeAttachment(SchemaLocal<T> attachment)
+ {
+ T o;
+ synchronized (attachments)
+ {
+ o = (T) attachments.remove(attachment);
+ }
+ return o;
+ }
+
+
+
+ public <T> void setAttachment(SchemaLocal<T> attachment, T value)
+ {
+ synchronized (attachments)
+ {
+ attachments.put(attachment, value);
+ }
+ }
+ }
+
+
+
+ private static interface Impl
+ {
+ <T> T getAttachment(SchemaLocal<T> attachment);
+
+
+
+ AttributeType getAttributeType(String name)
+ throws UnknownSchemaElementException;
+
+
+
+ Collection<AttributeType> getAttributeTypes();
+
+
+
+ List<AttributeType> getAttributeTypesByName(String name);
+
+
+
+ DITContentRule getDITContentRule(String name)
+ throws UnknownSchemaElementException;
+
+
+
+ Collection<DITContentRule> getDITContentRules();
+
+
+
+ Collection<DITContentRule> getDITContentRulesByName(String name);
+
+
+
+ DITStructureRule getDITStructureRule(int ruleID)
+ throws UnknownSchemaElementException;
+
+
+
+ Collection<DITStructureRule> getDITStructureRulesByName(String name);
+
+
+
+ Collection<DITStructureRule> getDITStructureRulesByNameForm(
+ NameForm nameForm);
+
+
+
+ Collection<DITStructureRule> getDITStuctureRules();
+
+
+
+ MatchingRule getMatchingRule(String name)
+ throws UnknownSchemaElementException;
+
+
+
+ Collection<MatchingRule> getMatchingRules();
+
+
+
+ Collection<MatchingRule> getMatchingRulesByName(String name);
+
+
+
+ MatchingRuleUse getMatchingRuleUse(MatchingRule matchingRule)
+ throws UnknownSchemaElementException;
+
+
+
+ MatchingRuleUse getMatchingRuleUse(String name)
+ throws UnknownSchemaElementException;
+
+
+
+ Collection<MatchingRuleUse> getMatchingRuleUses();
+
+
+
+ Collection<MatchingRuleUse> getMatchingRuleUsesByName(String name);
+
+
+
+ NameForm getNameForm(String name)
+ throws UnknownSchemaElementException;
+
+
+
+ Collection<NameForm> getNameFormByObjectClass(
+ ObjectClass structuralClass);
+
+
+
+ Collection<NameForm> getNameForms();
+
+
+
+ Collection<NameForm> getNameFormsByName(String name);
+
+
+
+ ObjectClass getObjectClass(String name)
+ throws UnknownSchemaElementException;
+
+
+
+ Collection<ObjectClass> getObjectClasses();
+
+
+
+ Collection<ObjectClass> getObjectClassesByName(String name);
+
+
+
+ SchemaCompatOptions getSchemaCompatOptions();
+
+
+
+ Syntax getSyntax(String numericOID)
+ throws UnknownSchemaElementException;
+
+
+
+ Collection<Syntax> getSyntaxes();
+
+
+
+ boolean hasAttributeType(String name);
+
+
+
+ boolean hasDITContentRule(String name);
+
+
+
+ boolean hasDITStructureRule(int ruleID);
+
+
+
+ boolean hasMatchingRule(String name);
+
+
+
+ boolean hasMatchingRuleUse(String name);
+
+
+
+ boolean hasNameForm(String name);
+
+
+
+ boolean hasObjectClass(String name);
+
+
+
+ boolean hasSyntax(String numericOID);
+
+
+
+ boolean isStrict();
+
+
+
+ <T> T removeAttachment(SchemaLocal<T> attachment);
+
+
+
+ <T> void setAttachment(SchemaLocal<T> attachment, T value);
+ }
+
+
+
+ private static class NonStrictImpl implements Impl
+ {
+ private final Impl strictImpl;
+
+
+
+ private NonStrictImpl(Impl strictImpl)
+ {
+ this.strictImpl = strictImpl;
+ }
+
+
+
+ public <T> T getAttachment(SchemaLocal<T> attachment)
+ {
+ return strictImpl.getAttachment(attachment);
+ }
+
+
+
+ public AttributeType getAttributeType(String name)
+ throws UnknownSchemaElementException
+ {
+ if (!strictImpl.hasAttributeType(name))
+ {
+ // Construct an placeholder attribute type with the given name,
+ // the default matching rule, and the default syntax. The OID of
+ // the attribute will be the normalized OID alias with "-oid"
+ // appended to the given name.
+ final StringBuilder builder = new StringBuilder(
+ name.length() + 4);
+ StaticUtils.toLowerCase(name, builder);
+ builder.append("-oid");
+ final String noid = builder.toString();
+
+ return new AttributeType(noid, Collections.singletonList(name),
+ "", Schema.getDefaultMatchingRule(), Schema
+ .getDefaultSyntax());
+ }
+ return strictImpl.getAttributeType(name);
+ }
+
+
+
+ public Collection<AttributeType> getAttributeTypes()
+ {
+ return strictImpl.getAttributeTypes();
+ }
+
+
+
+ public List<AttributeType> getAttributeTypesByName(String name)
+ {
+ return strictImpl.getAttributeTypesByName(name);
+ }
+
+
+
+ public DITContentRule getDITContentRule(String name)
+ throws UnknownSchemaElementException
+ {
+ return strictImpl.getDITContentRule(name);
+ }
+
+
+
+ public Collection<DITContentRule> getDITContentRules()
+ {
+ return strictImpl.getDITContentRules();
+ }
+
+
+
+ public Collection<DITContentRule> getDITContentRulesByName(
+ String name)
+ {
+ return strictImpl.getDITContentRulesByName(name);
+ }
+
+
+
+ public DITStructureRule getDITStructureRule(int ruleID)
+ throws UnknownSchemaElementException
+ {
+ return strictImpl.getDITStructureRule(ruleID);
+ }
+
+
+
+ public Collection<DITStructureRule> getDITStructureRulesByName(
+ String name)
+ {
+ return strictImpl.getDITStructureRulesByName(name);
+ }
+
+
+
+ public Collection<DITStructureRule> getDITStructureRulesByNameForm(
+ NameForm nameForm)
+ {
+ return strictImpl.getDITStructureRulesByNameForm(nameForm);
+ }
+
+
+
+ public Collection<DITStructureRule> getDITStuctureRules()
+ {
+ return strictImpl.getDITStuctureRules();
+ }
+
+
+
+ public MatchingRule getMatchingRule(String name)
+ throws UnknownSchemaElementException
+ {
+ return strictImpl.getMatchingRule(name);
+ }
+
+
+
+ public Collection<MatchingRule> getMatchingRules()
+ {
+ return strictImpl.getMatchingRules();
+ }
+
+
+
+ public Collection<MatchingRule> getMatchingRulesByName(String name)
+ {
+ return strictImpl.getMatchingRulesByName(name);
+ }
+
+
+
+ public MatchingRuleUse getMatchingRuleUse(MatchingRule matchingRule)
+ throws UnknownSchemaElementException
+ {
+ return strictImpl.getMatchingRuleUse(matchingRule);
+ }
+
+
+
+ public MatchingRuleUse getMatchingRuleUse(String name)
+ throws UnknownSchemaElementException
+ {
+ return strictImpl.getMatchingRuleUse(name);
+ }
+
+
+
+ public Collection<MatchingRuleUse> getMatchingRuleUses()
+ {
+ return strictImpl.getMatchingRuleUses();
+ }
+
+
+
+ public Collection<MatchingRuleUse> getMatchingRuleUsesByName(
+ String name)
+ {
+ return strictImpl.getMatchingRuleUsesByName(name);
+ }
+
+
+
+ public NameForm getNameForm(String name)
+ throws UnknownSchemaElementException
+ {
+ return strictImpl.getNameForm(name);
+ }
+
+
+
+ public Collection<NameForm> getNameFormByObjectClass(
+ ObjectClass structuralClass)
+ {
+ return strictImpl.getNameFormByObjectClass(structuralClass);
+ }
+
+
+
+ public Collection<NameForm> getNameForms()
+ {
+ return strictImpl.getNameForms();
+ }
+
+
+
+ public Collection<NameForm> getNameFormsByName(String name)
+ {
+ return strictImpl.getNameFormsByName(name);
+ }
+
+
+
+ public ObjectClass getObjectClass(String name)
+ throws UnknownSchemaElementException
+ {
+ return strictImpl.getObjectClass(name);
+ }
+
+
+
+ public Collection<ObjectClass> getObjectClasses()
+ {
+ return strictImpl.getObjectClasses();
+ }
+
+
+
+ public Collection<ObjectClass> getObjectClassesByName(String name)
+ {
+ return strictImpl.getObjectClassesByName(name);
+ }
+
+
+
+ public SchemaCompatOptions getSchemaCompatOptions()
+ {
+ return strictImpl.getSchemaCompatOptions();
+ }
+
+
+
+ public Syntax getSyntax(String numericOID)
+ {
+ if (!strictImpl.hasSyntax(numericOID))
+ {
+ return new Syntax(numericOID);
+ }
+ return strictImpl.getSyntax(numericOID);
+ }
+
+
+
+ public Collection<Syntax> getSyntaxes()
+ {
+ return strictImpl.getSyntaxes();
+ }
+
+
+
+ public boolean hasAttributeType(String name)
+ {
+ // In theory a non-strict schema always contains the requested
+ // attribute type, so we could always return true. However, we
+ // should provide a way for callers to differentiate between a
+ // real attribute type and a faked up attribute type.
+ return strictImpl.hasAttributeType(name);
+ }
+
+
+
+ public boolean hasDITContentRule(String name)
+ {
+ return strictImpl.hasDITContentRule(name);
+ }
+
+
+
+ public boolean hasDITStructureRule(int ruleID)
+ {
+ return strictImpl.hasDITStructureRule(ruleID);
+ }
+
+
+
+ public boolean hasMatchingRule(String name)
+ {
+ return strictImpl.hasMatchingRule(name);
+ }
+
+
+
+ public boolean hasMatchingRuleUse(String name)
+ {
+ return strictImpl.hasMatchingRuleUse(name);
+ }
+
+
+
+ public boolean hasNameForm(String name)
+ {
+ return strictImpl.hasNameForm(name);
+ }
+
+
+
+ public boolean hasObjectClass(String name)
+ {
+ return strictImpl.hasObjectClass(name);
+ }
+
+
+
+ public boolean hasSyntax(String numericOID)
+ {
+ return strictImpl.hasSyntax(numericOID);
+ }
+
+
+
+ public boolean isStrict()
+ {
+ return false;
+ }
+
+
+
+ public <T> T removeAttachment(SchemaLocal<T> attachment)
+ {
+ return strictImpl.removeAttachment(attachment);
+ }
+
+
+
+ public <T> void setAttachment(SchemaLocal<T> attachment, T value)
+ {
+ strictImpl.setAttachment(attachment, value);
+ }
+ }
+
+
+
+ private static class StrictImpl implements Impl
+ {
+ private final Map<SchemaLocal<?>, Object> attachments;
+
+ private final Map<Integer, DITStructureRule> id2StructureRules;
+
+ private final Map<String, List<AttributeType>> name2AttributeTypes;
+
+ private final Map<String, List<DITContentRule>> name2ContentRules;
+
+ private final Map<String, List<MatchingRule>> name2MatchingRules;
+
+ private final Map<String, List<MatchingRuleUse>> name2MatchingRuleUses;
+
+ private final Map<String, List<NameForm>> name2NameForms;
+
+ private final Map<String, List<ObjectClass>> name2ObjectClasses;
+
+ private final Map<String, List<DITStructureRule>> name2StructureRules;
+
+ private final Map<String, List<DITStructureRule>> nameForm2StructureRules;
+
+ private final Map<String, AttributeType> numericOID2AttributeTypes;
+
+ private final Map<String, DITContentRule> numericOID2ContentRules;
+
+ private final Map<String, MatchingRule> numericOID2MatchingRules;
+
+ private final Map<String, MatchingRuleUse> numericOID2MatchingRuleUses;
+
+ private final Map<String, NameForm> numericOID2NameForms;
+
+ private final Map<String, ObjectClass> numericOID2ObjectClasses;
+
+ private final Map<String, Syntax> numericOID2Syntaxes;
+
+ private final Map<String, List<NameForm>> objectClass2NameForms;
+
+ private final SchemaCompatOptions options;
+
+
+
+ private StrictImpl(Map<String, Syntax> numericOID2Syntaxes,
+ Map<String, MatchingRule> numericOID2MatchingRules,
+ Map<String, MatchingRuleUse> numericOID2MatchingRuleUses,
+ Map<String, AttributeType> numericOID2AttributeTypes,
+ Map<String, ObjectClass> numericOID2ObjectClasses,
+ Map<String, NameForm> numericOID2NameForms,
+ Map<String, DITContentRule> numericOID2ContentRules,
+ Map<Integer, DITStructureRule> id2StructureRules,
+ Map<String, List<MatchingRule>> name2MatchingRules,
+ Map<String, List<MatchingRuleUse>> name2MatchingRuleUses,
+ Map<String, List<AttributeType>> name2AttributeTypes,
+ Map<String, List<ObjectClass>> name2ObjectClasses,
+ Map<String, List<NameForm>> name2NameForms,
+ Map<String, List<DITContentRule>> name2ContentRules,
+ Map<String, List<DITStructureRule>> name2StructureRules,
+ Map<String, List<NameForm>> objectClass2NameForms,
+ Map<String, List<DITStructureRule>> nameForm2StructureRules,
+ SchemaCompatOptions options)
+ {
+ this.numericOID2Syntaxes = Collections
+ .unmodifiableMap(numericOID2Syntaxes);
+ this.numericOID2MatchingRules = Collections
+ .unmodifiableMap(numericOID2MatchingRules);
+ this.numericOID2MatchingRuleUses = Collections
+ .unmodifiableMap(numericOID2MatchingRuleUses);
+ this.numericOID2AttributeTypes = Collections
+ .unmodifiableMap(numericOID2AttributeTypes);
+ this.numericOID2ObjectClasses = Collections
+ .unmodifiableMap(numericOID2ObjectClasses);
+ this.numericOID2NameForms = Collections
+ .unmodifiableMap(numericOID2NameForms);
+ this.numericOID2ContentRules = Collections
+ .unmodifiableMap(numericOID2ContentRules);
+ this.id2StructureRules = Collections
+ .unmodifiableMap(id2StructureRules);
+ this.name2MatchingRules = Collections
+ .unmodifiableMap(name2MatchingRules);
+ this.name2MatchingRuleUses = Collections
+ .unmodifiableMap(name2MatchingRuleUses);
+ this.name2AttributeTypes = Collections
+ .unmodifiableMap(name2AttributeTypes);
+ this.name2ObjectClasses = Collections
+ .unmodifiableMap(name2ObjectClasses);
+ this.name2NameForms = Collections.unmodifiableMap(name2NameForms);
+ this.name2ContentRules = Collections
+ .unmodifiableMap(name2ContentRules);
+ this.name2StructureRules = Collections
+ .unmodifiableMap(name2StructureRules);
+ this.objectClass2NameForms = Collections
+ .unmodifiableMap(objectClass2NameForms);
+ this.nameForm2StructureRules = Collections
+ .unmodifiableMap(nameForm2StructureRules);
+ this.options = options;
+ attachments = new WeakHashMap<SchemaLocal<?>, Object>();
+ }
+
+
+
+ @SuppressWarnings("unchecked")
+ public <T> T getAttachment(SchemaLocal<T> attachment)
+ {
+ T o;
+ synchronized (attachments)
+ {
+ o = (T) attachments.get(attachment);
+ if (o == null)
+ {
+ o = attachment.initialValue();
+ if (o != null)
+ {
+ attachments.put(attachment, o);
+ }
+ }
+ }
+ return o;
+ }
+
+
+
+ public AttributeType getAttributeType(String name)
+ throws UnknownSchemaElementException
+ {
+ final AttributeType type = numericOID2AttributeTypes.get(name);
+ if (type != null)
+ {
+ return type;
+ }
+ final List<AttributeType> attributes = name2AttributeTypes
+ .get(StaticUtils.toLowerCase(name));
+ if (attributes != null)
+ {
+ if (attributes.size() == 1)
+ {
+ return attributes.get(0);
+ }
+ throw new UnknownSchemaElementException(
+ WARN_ATTR_TYPE_AMBIGIOUS.get(name));
+ }
+ throw new UnknownSchemaElementException(WARN_ATTR_TYPE_UNKNOWN
+ .get(name));
+ }
+
+
+
+ public Collection<AttributeType> getAttributeTypes()
+ {
+ return numericOID2AttributeTypes.values();
+ }
+
+
+
+ public List<AttributeType> getAttributeTypesByName(String name)
+ {
+ final List<AttributeType> attributes = name2AttributeTypes
+ .get(StaticUtils.toLowerCase(name));
+ if (attributes == null)
+ {
+ return Collections.emptyList();
+ }
+ else
+ {
+ return attributes;
+ }
+ }
+
+
+
+ public DITContentRule getDITContentRule(String name)
+ throws UnknownSchemaElementException
+ {
+ final DITContentRule rule = numericOID2ContentRules.get(name);
+ if (rule != null)
+ {
+ return rule;
+ }
+ final List<DITContentRule> rules = name2ContentRules
+ .get(StaticUtils.toLowerCase(name));
+ if (rules != null)
+ {
+ if (rules.size() == 1)
+ {
+ return rules.get(0);
+ }
+ throw new UnknownSchemaElementException(WARN_DCR_AMBIGIOUS
+ .get(name));
+ }
+ throw new UnknownSchemaElementException(WARN_DCR_UNKNOWN
+ .get(name));
+ }
+
+
+
+ public Collection<DITContentRule> getDITContentRules()
+ {
+ return numericOID2ContentRules.values();
+ }
+
+
+
+ public Collection<DITContentRule> getDITContentRulesByName(
+ String name)
+ {
+ final List<DITContentRule> rules = name2ContentRules
+ .get(StaticUtils.toLowerCase(name));
+ if (rules == null)
+ {
+ return Collections.emptyList();
+ }
+ else
+ {
+ return rules;
+ }
+ }
+
+
+
+ public DITStructureRule getDITStructureRule(int ruleID)
+ throws UnknownSchemaElementException
+ {
+ final DITStructureRule rule = id2StructureRules.get(ruleID);
+ if (rule == null)
+ {
+ throw new UnknownSchemaElementException(WARN_DSR_UNKNOWN
+ .get(String.valueOf(ruleID)));
+ }
+ return rule;
+ }
+
+
+
+ public Collection<DITStructureRule> getDITStructureRulesByName(
+ String name)
+ {
+ final List<DITStructureRule> rules = name2StructureRules
+ .get(StaticUtils.toLowerCase(name));
+ if (rules == null)
+ {
+ return Collections.emptyList();
+ }
+ else
+ {
+ return rules;
+ }
+ }
+
+
+
+ public Collection<DITStructureRule> getDITStructureRulesByNameForm(
+ NameForm nameForm)
+ {
+ final List<DITStructureRule> rules = nameForm2StructureRules
+ .get(nameForm.getOID());
+ if (rules == null)
+ {
+ return Collections.emptyList();
+ }
+ else
+ {
+ return rules;
+ }
+ }
+
+
+
+ public Collection<DITStructureRule> getDITStuctureRules()
+ {
+ return id2StructureRules.values();
+ }
+
+
+
+ public MatchingRule getMatchingRule(String name)
+ throws UnknownSchemaElementException
+ {
+ final MatchingRule rule = numericOID2MatchingRules.get(name);
+ if (rule != null)
+ {
+ return rule;
+ }
+ final List<MatchingRule> rules = name2MatchingRules
+ .get(StaticUtils.toLowerCase(name));
+ if (rules != null)
+ {
+ if (rules.size() == 1)
+ {
+ return rules.get(0);
+ }
+ throw new UnknownSchemaElementException(WARN_MR_AMBIGIOUS
+ .get(name));
+ }
+ throw new UnknownSchemaElementException(WARN_MR_UNKNOWN.get(name));
+ }
+
+
+
+ public Collection<MatchingRule> getMatchingRules()
+ {
+ return numericOID2MatchingRules.values();
+ }
+
+
+
+ public Collection<MatchingRule> getMatchingRulesByName(String name)
+ {
+ final List<MatchingRule> rules = name2MatchingRules
+ .get(StaticUtils.toLowerCase(name));
+ if (rules == null)
+ {
+ return Collections.emptyList();
+ }
+ else
+ {
+ return rules;
+ }
+ }
+
+
+
+ public MatchingRuleUse getMatchingRuleUse(MatchingRule matchingRule)
+ throws UnknownSchemaElementException
+ {
+ return getMatchingRuleUse(matchingRule.getOID());
+ }
+
+
+
+ public MatchingRuleUse getMatchingRuleUse(String name)
+ throws UnknownSchemaElementException
+ {
+ final MatchingRuleUse rule = numericOID2MatchingRuleUses
+ .get(name);
+ if (rule != null)
+ {
+ return rule;
+ }
+ final List<MatchingRuleUse> uses = name2MatchingRuleUses
+ .get(StaticUtils.toLowerCase(name));
+ if (uses != null)
+ {
+ if (uses.size() == 1)
+ {
+ return uses.get(0);
+ }
+ throw new UnknownSchemaElementException(WARN_MRU_AMBIGIOUS
+ .get(name));
+ }
+ throw new UnknownSchemaElementException(WARN_MRU_UNKNOWN
+ .get(name));
+ }
+
+
+
+ public Collection<MatchingRuleUse> getMatchingRuleUses()
+ {
+ return numericOID2MatchingRuleUses.values();
+ }
+
+
+
+ public Collection<MatchingRuleUse> getMatchingRuleUsesByName(
+ String name)
+ {
+ final List<MatchingRuleUse> rules = name2MatchingRuleUses
+ .get(StaticUtils.toLowerCase(name));
+ if (rules == null)
+ {
+ return Collections.emptyList();
+ }
+ else
+ {
+ return rules;
+ }
+ }
+
+
+
+ public NameForm getNameForm(String name)
+ throws UnknownSchemaElementException
+ {
+ final NameForm form = numericOID2NameForms.get(name);
+ if (form != null)
+ {
+ return form;
+ }
+ final List<NameForm> forms = name2NameForms.get(StaticUtils
+ .toLowerCase(name));
+ if (forms != null)
+ {
+ if (forms.size() == 1)
+ {
+ return forms.get(0);
+ }
+ throw new UnknownSchemaElementException(WARN_NAMEFORM_AMBIGIOUS
+ .get(name));
+ }
+ throw new UnknownSchemaElementException(WARN_NAMEFORM_UNKNOWN
+ .get(name));
+ }
+
+
+
+ public Collection<NameForm> getNameFormByObjectClass(
+ ObjectClass structuralClass)
+ {
+ final List<NameForm> forms = objectClass2NameForms
+ .get(structuralClass.getOID());
+ if (forms == null)
+ {
+ return Collections.emptyList();
+ }
+ else
+ {
+ return forms;
+ }
+ }
+
+
+
+ public Collection<NameForm> getNameForms()
+ {
+ return numericOID2NameForms.values();
+ }
+
+
+
+ public Collection<NameForm> getNameFormsByName(String name)
+ {
+ final List<NameForm> forms = name2NameForms.get(StaticUtils
+ .toLowerCase(name));
+ if (forms == null)
+ {
+ return Collections.emptyList();
+ }
+ else
+ {
+ return forms;
+ }
+ }
+
+
+
+ public ObjectClass getObjectClass(String name)
+ throws UnknownSchemaElementException
+ {
+ final ObjectClass oc = numericOID2ObjectClasses.get(name);
+ if (oc != null)
+ {
+ return oc;
+ }
+ final List<ObjectClass> classes = name2ObjectClasses
+ .get(StaticUtils.toLowerCase(name));
+ if (classes != null)
+ {
+ if (classes.size() == 1)
+ {
+ return classes.get(0);
+ }
+ throw new UnknownSchemaElementException(
+ WARN_OBJECTCLASS_AMBIGIOUS.get(name));
+ }
+ throw new UnknownSchemaElementException(WARN_OBJECTCLASS_UNKNOWN
+ .get(name));
+ }
+
+
+
+ public Collection<ObjectClass> getObjectClasses()
+ {
+ return numericOID2ObjectClasses.values();
+ }
+
+
+
+ public Collection<ObjectClass> getObjectClassesByName(String name)
+ {
+ final List<ObjectClass> classes = name2ObjectClasses
+ .get(StaticUtils.toLowerCase(name));
+ if (classes == null)
+ {
+ return Collections.emptyList();
+ }
+ else
+ {
+ return classes;
+ }
+ }
+
+
+
+ public SchemaCompatOptions getSchemaCompatOptions()
+ {
+ return options;
+ }
+
+
+
+ public Syntax getSyntax(String numericOID)
+ throws UnknownSchemaElementException
+ {
+ final Syntax syntax = numericOID2Syntaxes.get(numericOID);
+ if (syntax == null)
+ {
+ throw new UnknownSchemaElementException(WARN_SYNTAX_UNKNOWN
+ .get(numericOID));
+ }
+ return syntax;
+ }
+
+
+
+ public Collection<Syntax> getSyntaxes()
+ {
+ return numericOID2Syntaxes.values();
+ }
+
+
+
+ public boolean hasAttributeType(String name)
+ {
+ if (numericOID2AttributeTypes.containsKey(name))
+ {
+ return true;
+ }
+ final List<AttributeType> attributes = name2AttributeTypes
+ .get(StaticUtils.toLowerCase(name));
+ return attributes != null && attributes.size() == 1;
+ }
+
+
+
+ public boolean hasDITContentRule(String name)
+ {
+ if (numericOID2ContentRules.containsKey(name))
+ {
+ return true;
+ }
+ final List<DITContentRule> rules = name2ContentRules
+ .get(StaticUtils.toLowerCase(name));
+ return rules != null && rules.size() == 1;
+ }
+
+
+
+ public boolean hasDITStructureRule(int ruleID)
+ {
+ return id2StructureRules.containsKey(ruleID);
+ }
+
+
+
+ public boolean hasMatchingRule(String name)
+ {
+ if (numericOID2MatchingRules.containsKey(name))
+ {
+ return true;
+ }
+ final List<MatchingRule> rules = name2MatchingRules
+ .get(StaticUtils.toLowerCase(name));
+ return rules != null && rules.size() == 1;
+ }
+
+
+
+ public boolean hasMatchingRuleUse(String name)
+ {
+ if (numericOID2MatchingRuleUses.containsKey(name))
+ {
+ return true;
+ }
+ final List<MatchingRuleUse> uses = name2MatchingRuleUses
+ .get(StaticUtils.toLowerCase(name));
+ return uses != null && uses.size() == 1;
+ }
+
+
+
+ public boolean hasNameForm(String name)
+ {
+ if (numericOID2NameForms.containsKey(name))
+ {
+ return true;
+ }
+ final List<NameForm> forms = name2NameForms.get(StaticUtils
+ .toLowerCase(name));
+ return forms != null && forms.size() == 1;
+ }
+
+
+
+ public boolean hasObjectClass(String name)
+ {
+ if (numericOID2ObjectClasses.containsKey(name))
+ {
+ return true;
+ }
+ final List<ObjectClass> classes = name2ObjectClasses
+ .get(StaticUtils.toLowerCase(name));
+ return classes != null && classes.size() == 1;
+ }
+
+
+
+ public boolean hasSyntax(String numericOID)
+ {
+ return numericOID2Syntaxes.containsKey(numericOID);
+ }
+
+
+
+ public boolean isStrict()
+ {
+ return true;
+ }
+
+
+
+ @SuppressWarnings("unchecked")
+ public <T> T removeAttachment(SchemaLocal<T> attachment)
+ {
+ T o;
+ synchronized (attachments)
+ {
+ o = (T) attachments.remove(attachment);
+ }
+ return o;
+ }
+
+
+
+ public <T> void setAttachment(SchemaLocal<T> attachment, T value)
+ {
+ synchronized (attachments)
+ {
+ attachments.put(attachment, value);
+ }
+ }
+ }
+
+
+
+ private static final Schema CORE_SCHEMA = CoreSchemaImpl
+ .getInstance();
+
+ private static final Schema EMPTY_SCHEMA = new Schema(new EmptyImpl());
+
+ private static volatile Schema DEFAULT_SCHEMA = CoreSchemaImpl
+ .getInstance();
+
+ private static final AttributeDescription ATTR_ATTRIBUTE_TYPES = AttributeDescription
+ .valueOf("attributeTypes");
+
+ private static final AttributeDescription ATTR_DIT_CONTENT_RULES = AttributeDescription
+ .valueOf("dITContentRules");
+
+ private static final AttributeDescription ATTR_DIT_STRUCTURE_RULES = AttributeDescription
+ .valueOf("dITStructureRules");
+
+ private static final AttributeDescription ATTR_LDAP_SYNTAXES = AttributeDescription
+ .valueOf("ldapSyntaxes");
+
+ private static final AttributeDescription ATTR_MATCHING_RULE_USE = AttributeDescription
+ .valueOf("matchingRuleUse");
+
+ private static final AttributeDescription ATTR_MATCHING_RULES = AttributeDescription
+ .valueOf("matchingRules");
+
+ private static final AttributeDescription ATTR_NAME_FORMS = AttributeDescription
+ .valueOf("nameForms");
+
+ private static final AttributeDescription ATTR_OBJECT_CLASSES = AttributeDescription
+ .valueOf("objectClasses");
+
+ private static final AttributeDescription ATTR_SUBSCHEMA_SUBENTRY = AttributeDescription
+ .valueOf("subschemaSubentry");
+
+ private static final String[] SUBSCHEMA_ATTRS = new String[] {
+ ATTR_LDAP_SYNTAXES.toString(), ATTR_ATTRIBUTE_TYPES.toString(),
+ ATTR_DIT_CONTENT_RULES.toString(),
+ ATTR_DIT_STRUCTURE_RULES.toString(),
+ ATTR_MATCHING_RULE_USE.toString(),
+ ATTR_MATCHING_RULES.toString(), ATTR_NAME_FORMS.toString(),
+ ATTR_OBJECT_CLASSES.toString() };
+
+
+
+ /**
+ * Returns the core schema. The core schema is non-strict and contains
+ * the following standard LDAP schema elements:
+ * <ul>
+ * <li><a href="http://tools.ietf.org/html/rfc4512">RFC 4512 -
+ * Lightweight Directory Access Protocol (LDAP): Directory Information
+ * Models </a>
+ * <li><a href="http://tools.ietf.org/html/rfc4517">RFC 4517 -
+ * Lightweight Directory Access Protocol (LDAP): Syntaxes and Matching
+ * Rules </a>
+ * <li><a href="http://tools.ietf.org/html/rfc4519">RFC 4519 -
+ * Lightweight Directory Access Protocol (LDAP): Schema for User
+ * Applications </a>
+ * <li><a href="http://tools.ietf.org/html/rfc4530">RFC 4530 -
+ * Lightweight Directory Access Protocol (LDAP): entryUUID Operational
+ * Attribute </a>
+ * <li><a href="http://tools.ietf.org/html/rfc3045">RFC 3045 - Storing
+ * Vendor Information in the LDAP root DSE </a>
+ * <li><a href="http://tools.ietf.org/html/rfc3112">RFC 3112 - LDAP
+ * Authentication Password Schema </a>
+ * </ul>
+ *
+ * @return The core schema.
+ */
+ public static Schema getCoreSchema()
+ {
+ return CORE_SCHEMA;
+ }
+
+
+
+ /**
+ * Returns the default schema which should be used by this
+ * application. The default schema is initially set to the core
+ * schema.
+ *
+ * @return The default schema which should be used by this
+ * application.
+ */
+ public static Schema getDefaultSchema()
+ {
+ return DEFAULT_SCHEMA;
+ }
+
+
+
+ /**
+ * Returns the empty schema. The empty schema is non-strict and does
+ * not contain any schema elements.
+ *
+ * @return The empty schema.
+ */
+ public static Schema getEmptySchema()
+ {
+ return EMPTY_SCHEMA;
+ }
+
+
+
+ /**
+ * Reads the schema associated with the provided entry from an LDAP
+ * Directory Server.
+ *
+ * @param connection
+ * The connection to the Directory Server.
+ * @param dn
+ * The name of the entry whose schema is to be retrieved.
+ * @param warnings
+ * A list to which any warning messages encountered while
+ * decoding the schema should be appended.
+ * @return The schema.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code dn} could not be decoded using the default
+ * schema.
+ * @throws ErrorResultException
+ * If the server returned an error result code.
+ * @throws InterruptedException
+ * If the current thread was interrupted while waiting.
+ * @throws SchemaNotFoundException
+ * If the requested schema was not found in the Directory
+ * Server.
+ */
+ public static Schema getSchema(Connection connection, String dn,
+ List<Message> warnings) throws ErrorResultException,
+ InterruptedException, LocalizedIllegalArgumentException,
+ SchemaNotFoundException
+ {
+ Validator.ensureNotNull(connection, dn, warnings);
+ SearchResultEntry result = connection.readEntry(dn,
+ ATTR_SUBSCHEMA_SUBENTRY.toString());
+ Attribute subentryAttr;
+ if ((subentryAttr = result.getAttribute(ATTR_SUBSCHEMA_SUBENTRY)) == null
+ || subentryAttr.isEmpty())
+ {
+ throw new SchemaNotFoundException(ERR_NO_SUBSCHEMA_SUBENTRY_ATTR
+ .get(dn));
+ }
+
+ String subschemaDNString = subentryAttr.iterator().next()
+ .toString();
+ DN subschemaDN;
+ try
+ {
+ subschemaDN = DN.valueOf(subschemaDNString);
+ }
+ catch (LocalizedIllegalArgumentException e)
+ {
+ throw new SchemaNotFoundException(
+ ERR_INVALID_SUBSCHEMA_SUBENTRY_ATTR.get(dn,
+ subschemaDNString, e.getMessageObject()));
+ }
+ result = connection.readEntry(subschemaDN, SUBSCHEMA_ATTRS);
+
+ final SchemaBuilder builder = new SchemaBuilder();
+ Attribute attr = result.getAttribute(ATTR_LDAP_SYNTAXES);
+
+ if (attr != null)
+ {
+ for (final ByteString def : attr)
+ {
+ try
+ {
+ builder.addSyntax(def.toString(), true);
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ warnings.add(e.getMessageObject());
+ }
+ }
+ }
+
+ attr = result.getAttribute(ATTR_ATTRIBUTE_TYPES);
+ if (attr != null)
+ {
+ for (final ByteString def : attr)
+ {
+ try
+ {
+ builder.addAttributeType(def.toString(), true);
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ warnings.add(e.getMessageObject());
+ }
+ }
+ }
+
+ attr = result.getAttribute(ATTR_OBJECT_CLASSES);
+ if (attr != null)
+ {
+ for (final ByteString def : attr)
+ {
+ try
+ {
+ builder.addObjectClass(def.toString(), true);
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ warnings.add(e.getMessageObject());
+ }
+ }
+ }
+
+ attr = result.getAttribute(ATTR_MATCHING_RULE_USE);
+ if (attr != null)
+ {
+ for (final ByteString def : attr)
+ {
+ try
+ {
+ builder.addMatchingRuleUse(def.toString(), true);
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ warnings.add(e.getMessageObject());
+ }
+ }
+ }
+
+ attr = result.getAttribute(ATTR_MATCHING_RULES);
+ if (attr != null)
+ {
+ for (final ByteString def : attr)
+ {
+ try
+ {
+ builder.addMatchingRule(def.toString(), true);
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ warnings.add(e.getMessageObject());
+ }
+ }
+ }
+
+ attr = result.getAttribute(ATTR_DIT_CONTENT_RULES);
+ if (attr != null)
+ {
+ for (final ByteString def : attr)
+ {
+ try
+ {
+ builder.addDITContentRule(def.toString(), true);
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ warnings.add(e.getMessageObject());
+ }
+ }
+ }
+
+ attr = result.getAttribute(ATTR_DIT_STRUCTURE_RULES);
+ if (attr != null)
+ {
+ for (final ByteString def : attr)
+ {
+ try
+ {
+ builder.addDITStructureRule(def.toString(), true);
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ warnings.add(e.getMessageObject());
+ }
+ }
+ }
+
+ attr = result.getAttribute(ATTR_NAME_FORMS);
+ if (attr != null)
+ {
+ for (final ByteString def : attr)
+ {
+ try
+ {
+ builder.addNameForm(def.toString(), true);
+ }
+ catch (final LocalizedIllegalArgumentException e)
+ {
+ warnings.add(e.getMessageObject());
+ }
+ }
+ }
+
+ return builder.toSchema(warnings);
+ }
+
+
+
+ /**
+ * Sets the default schema which should be used by this application.
+ * The default schema is initially set to the core schema.
+ *
+ * @param schema
+ * The default schema which should be used by this
+ * application.
+ */
+ public static void setDefaultSchema(Schema schema)
+ {
+ DEFAULT_SCHEMA = schema;
+ }
+
+
+
+ static MatchingRule getDefaultMatchingRule()
+ {
+ return CoreSchema.getOctetStringMatchingRule();
+ }
+
+
+
+ static Syntax getDefaultSyntax()
+ {
+ return CoreSchema.getOctetStringSyntax();
+ }
+
+
+
+ private final Impl impl;
+
+
+
+ Schema(Map<String, Syntax> numericOID2Syntaxes,
+ Map<String, MatchingRule> numericOID2MatchingRules,
+ Map<String, MatchingRuleUse> numericOID2MatchingRuleUses,
+ Map<String, AttributeType> numericOID2AttributeTypes,
+ Map<String, ObjectClass> numericOID2ObjectClasses,
+ Map<String, NameForm> numericOID2NameForms,
+ Map<String, DITContentRule> numericOID2ContentRules,
+ Map<Integer, DITStructureRule> id2StructureRules,
+ Map<String, List<MatchingRule>> name2MatchingRules,
+ Map<String, List<MatchingRuleUse>> name2MatchingRuleUses,
+ Map<String, List<AttributeType>> name2AttributeTypes,
+ Map<String, List<ObjectClass>> name2ObjectClasses,
+ Map<String, List<NameForm>> name2NameForms,
+ Map<String, List<DITContentRule>> name2ContentRules,
+ Map<String, List<DITStructureRule>> name2StructureRules,
+ Map<String, List<NameForm>> objectClass2NameForms,
+ Map<String, List<DITStructureRule>> nameForm2StructureRules,
+ SchemaCompatOptions options)
+ {
+ impl = new StrictImpl(numericOID2Syntaxes,
+ numericOID2MatchingRules, numericOID2MatchingRuleUses,
+ numericOID2AttributeTypes, numericOID2ObjectClasses,
+ numericOID2NameForms, numericOID2ContentRules,
+ id2StructureRules, name2MatchingRules, name2MatchingRuleUses,
+ name2AttributeTypes, name2ObjectClasses, name2NameForms,
+ name2ContentRules, name2StructureRules, objectClass2NameForms,
+ nameForm2StructureRules, options);
+ }
+
+
+
+ private Schema(Impl impl)
+ {
+ this.impl = impl;
+ }
+
+
+
+ /**
+ * Returns the attribute type with the specified name or numeric OID.
+ *
+ * @param name
+ * The name or OID of the attribute type to retrieve.
+ * @return The requested attribute type.
+ * @throws UnknownSchemaElementException
+ * If this is a strict schema and the requested attribute
+ * type was not found or if the provided name is ambiguous.
+ */
+ public AttributeType getAttributeType(String name)
+ throws UnknownSchemaElementException
+ {
+ return impl.getAttributeType(name);
+ }
+
+
+
+ /**
+ * Returns an unmodifiable collection containing all of the attribute
+ * types contained in this schema.
+ *
+ * @return An unmodifiable collection containing all of the attribute
+ * types contained in this schema.
+ */
+ public Collection<AttributeType> getAttributeTypes()
+ {
+ return impl.getAttributeTypes();
+ }
+
+
+
+ /**
+ * Returns an unmodifiable collection containing all of the attribute
+ * types having the specified name or numeric OID.
+ *
+ * @param name
+ * The name or OID of the attribute types to retrieve.
+ * @return An unmodifiable collection containing all of the attribute
+ * types having the specified name or numeric OID.
+ */
+ public List<AttributeType> getAttributeTypesByName(String name)
+ {
+ return impl.getAttributeTypesByName(name);
+ }
+
+
+
+ /**
+ * Returns the DIT content rule with the specified name or numeric
+ * OID.
+ *
+ * @param name
+ * The name or OID of the DIT content rule to retrieve.
+ * @return The requested DIT content rule.
+ * @throws UnknownSchemaElementException
+ * If this is a strict schema and the requested DIT content
+ * rule was not found or if the provided name is ambiguous.
+ */
+ public DITContentRule getDITContentRule(String name)
+ throws UnknownSchemaElementException
+ {
+ return impl.getDITContentRule(name);
+ }
+
+
+
+ /**
+ * Returns an unmodifiable collection containing all of the DIT
+ * content rules contained in this schema.
+ *
+ * @return An unmodifiable collection containing all of the DIT
+ * content rules contained in this schema.
+ */
+ public Collection<DITContentRule> getDITContentRules()
+ {
+ return impl.getDITContentRules();
+ }
+
+
+
+ /**
+ * Returns an unmodifiable collection containing all of the DIT
+ * content rules having the specified name or numeric OID.
+ *
+ * @param name
+ * The name or OID of the DIT content rules to retrieve.
+ * @return An unmodifiable collection containing all of the DIT
+ * content rules having the specified name or numeric OID.
+ */
+ public Collection<DITContentRule> getDITContentRulesByName(String name)
+ {
+ return impl.getDITContentRulesByName(name);
+ }
+
+
+
+ /**
+ * Returns the DIT structure rule with the specified name or numeric
+ * OID.
+ *
+ * @param ruleID
+ * The ID of the DIT structure rule to retrieve.
+ * @return The requested DIT structure rule.
+ * @throws UnknownSchemaElementException
+ * If this is a strict schema and the requested DIT
+ * structure rule was not found.
+ */
+ public DITStructureRule getDITStructureRule(int ruleID)
+ throws UnknownSchemaElementException
+ {
+ return impl.getDITStructureRule(ruleID);
+ }
+
+
+
+ /**
+ * Returns an unmodifiable collection containing all of the DIT
+ * structure rules having the specified name or numeric OID.
+ *
+ * @param name
+ * The name or OID of the DIT structure rules to retrieve.
+ * @return An unmodifiable collection containing all of the DIT
+ * structure rules having the specified name or numeric OID.
+ */
+ public Collection<DITStructureRule> getDITStructureRulesByName(
+ String name)
+ {
+ return impl.getDITStructureRulesByName(name);
+ }
+
+
+
+ /**
+ * Retrieves the DIT structure rules for the provided name form.
+ *
+ * @param nameForm
+ * The name form.
+ * @return The requested DIT structure rules.
+ */
+ public Collection<DITStructureRule> getDITStructureRulesByNameForm(
+ NameForm nameForm)
+ {
+ return impl.getDITStructureRulesByNameForm(nameForm);
+ }
+
+
+
+ /**
+ * Returns an unmodifiable collection containing all of the DIT
+ * structure rules contained in this schema.
+ *
+ * @return An unmodifiable collection containing all of the DIT
+ * structure rules contained in this schema.
+ */
+ public Collection<DITStructureRule> getDITStuctureRules()
+ {
+ return impl.getDITStuctureRules();
+ }
+
+
+
+ /**
+ * Returns the matching rule with the specified name or numeric OID.
+ *
+ * @param name
+ * The name or OID of the matching rule to retrieve.
+ * @return The requested matching rule.
+ * @throws UnknownSchemaElementException
+ * If this is a strict schema and the requested matching
+ * rule was not found or if the provided name is ambiguous.
+ */
+ public MatchingRule getMatchingRule(String name)
+ throws UnknownSchemaElementException
+ {
+ return impl.getMatchingRule(name);
+ }
+
+
+
+ /**
+ * Returns an unmodifiable collection containing all of the matching
+ * rules contained in this schema.
+ *
+ * @return An unmodifiable collection containing all of the matching
+ * rules contained in this schema.
+ */
+ public Collection<MatchingRule> getMatchingRules()
+ {
+ return impl.getMatchingRules();
+ }
+
+
+
+ /**
+ * Returns an unmodifiable collection containing all of the matching
+ * rules having the specified name or numeric OID.
+ *
+ * @param name
+ * The name or OID of the matching rules to retrieve.
+ * @return An unmodifiable collection containing all of the matching
+ * rules having the specified name or numeric OID.
+ */
+ public Collection<MatchingRule> getMatchingRulesByName(String name)
+ {
+ return impl.getMatchingRulesByName(name);
+ }
+
+
+
+ /**
+ * Returns the matching rule use associated with the provided matching
+ * rule.
+ *
+ * @param matchingRule
+ * The matching rule whose matching rule use is to be
+ * retrieved.
+ * @return The requested matching rule use.
+ * @throws UnknownSchemaElementException
+ * If this is a strict schema and the requested matching
+ * rule use was not found or if the provided name is
+ * ambiguous.
+ */
+ public MatchingRuleUse getMatchingRuleUse(MatchingRule matchingRule)
+ throws UnknownSchemaElementException
+ {
+ return getMatchingRuleUse(matchingRule.getOID());
+ }
+
+
+
+ /**
+ * Returns the matching rule use with the specified name or numeric
+ * OID.
+ *
+ * @param name
+ * The name or OID of the matching rule use to retrieve.
+ * @return The requested matching rule use.
+ * @throws UnknownSchemaElementException
+ * If this is a strict schema and the requested matching
+ * rule use was not found or if the provided name is
+ * ambiguous.
+ */
+ public MatchingRuleUse getMatchingRuleUse(String name)
+ throws UnknownSchemaElementException
+ {
+ return impl.getMatchingRuleUse(name);
+ }
+
+
+
+ /**
+ * Returns an unmodifiable collection containing all of the matching
+ * rule uses contained in this schema.
+ *
+ * @return An unmodifiable collection containing all of the matching
+ * rule uses contained in this schema.
+ */
+ public Collection<MatchingRuleUse> getMatchingRuleUses()
+ {
+ return impl.getMatchingRuleUses();
+ }
+
+
+
+ /**
+ * Returns an unmodifiable collection containing all of the matching
+ * rule uses having the specified name or numeric OID.
+ *
+ * @param name
+ * The name or OID of the matching rule uses to retrieve.
+ * @return An unmodifiable collection containing all of the matching
+ * rule uses having the specified name or numeric OID.
+ */
+ public Collection<MatchingRuleUse> getMatchingRuleUsesByName(
+ String name)
+ {
+ return impl.getMatchingRuleUsesByName(name);
+ }
+
+
+
+ /**
+ * Returns the name form with the specified name or numeric OID.
+ *
+ * @param name
+ * The name or OID of the name form to retrieve.
+ * @return The requested name form.
+ * @throws UnknownSchemaElementException
+ * If this is a strict schema and the requested name form
+ * was not found or if the provided name is ambiguous.
+ */
+ public NameForm getNameForm(String name)
+ throws UnknownSchemaElementException
+ {
+ return impl.getNameForm(name);
+ }
+
+
+
+ /**
+ * Retrieves the name forms for the specified structural objectclass.
+ *
+ * @param structuralClass
+ * The structural objectclass for the name form to retrieve.
+ * @return The requested name forms
+ */
+ public Collection<NameForm> getNameFormByObjectClass(
+ ObjectClass structuralClass)
+ {
+ return impl.getNameFormByObjectClass(structuralClass);
+ }
+
+
+
+ /**
+ * Returns an unmodifiable collection containing all of the name forms
+ * contained in this schema.
+ *
+ * @return An unmodifiable collection containing all of the name forms
+ * contained in this schema.
+ */
+ public Collection<NameForm> getNameForms()
+ {
+ return impl.getNameForms();
+ }
+
+
+
+ /**
+ * Returns an unmodifiable collection containing all of the name forms
+ * having the specified name or numeric OID.
+ *
+ * @param name
+ * The name or OID of the name forms to retrieve.
+ * @return An unmodifiable collection containing all of the name forms
+ * having the specified name or numeric OID.
+ */
+ public Collection<NameForm> getNameFormsByName(String name)
+ {
+ return impl.getNameFormsByName(name);
+ }
+
+
+
+ /**
+ * Returns the object class with the specified name or numeric OID.
+ *
+ * @param name
+ * The name or OID of the object class to retrieve.
+ * @return The requested object class.
+ * @throws UnknownSchemaElementException
+ * If this is a strict schema and the requested object class
+ * was not found or if the provided name is ambiguous.
+ */
+ public ObjectClass getObjectClass(String name)
+ throws UnknownSchemaElementException
+ {
+ return impl.getObjectClass(name);
+ }
+
+
+
+ /**
+ * Returns an unmodifiable collection containing all of the object
+ * classes contained in this schema.
+ *
+ * @return An unmodifiable collection containing all of the object
+ * classes contained in this schema.
+ */
+ public Collection<ObjectClass> getObjectClasses()
+ {
+ return impl.getObjectClasses();
+ }
+
+
+
+ /**
+ * Returns an unmodifiable collection containing all of the object
+ * classes having the specified name or numeric OID.
+ *
+ * @param name
+ * The name or OID of the object classes to retrieve.
+ * @return An unmodifiable collection containing all of the object
+ * classes having the specified name or numeric OID.
+ */
+ public Collection<ObjectClass> getObjectClassesByName(String name)
+ {
+ return impl.getObjectClassesByName(name);
+ }
+
+
+
+ /**
+ * Returns the syntax with the specified numeric OID.
+ *
+ * @param numericOID
+ * The OID of the syntax to retrieve.
+ * @return The requested syntax.
+ * @throws UnknownSchemaElementException
+ * If this is a strict schema and the requested syntax was
+ * not found or if the provided name is ambiguous.
+ */
+ public Syntax getSyntax(String numericOID)
+ throws UnknownSchemaElementException
+ {
+ return impl.getSyntax(numericOID);
+ }
+
+
+
+ /**
+ * Returns an unmodifiable collection containing all of the syntaxes
+ * contained in this schema.
+ *
+ * @return An unmodifiable collection containing all of the syntaxes
+ * contained in this schema.
+ */
+ public Collection<Syntax> getSyntaxes()
+ {
+ return impl.getSyntaxes();
+ }
+
+
+
+ /**
+ * Indicates whether or not this schema contains an attribute type
+ * with the specified name or numeric OID.
+ *
+ * @param name
+ * The name or OID of the attribute type.
+ * @return {@code true} if this schema contains an attribute type with
+ * the specified name or numeric OID, otherwise {@code false}.
+ */
+ public boolean hasAttributeType(String name)
+ {
+ return impl.hasAttributeType(name);
+ }
+
+
+
+ /**
+ * Indicates whether or not this schema contains a DIT content rule
+ * with the specified name or numeric OID.
+ *
+ * @param name
+ * The name or OID of the DIT content rule.
+ * @return {@code true} if this schema contains a DIT content rule
+ * with the specified name or numeric OID, otherwise {@code
+ * false}.
+ */
+ public boolean hasDITContentRule(String name)
+ {
+ return impl.hasDITContentRule(name);
+ }
+
+
+
+ /**
+ * Indicates whether or not this schema contains a DIT structure rule
+ * with the specified rule ID.
+ *
+ * @param ruleID
+ * The ID of the DIT structure rule.
+ * @return {@code true} if this schema contains a DIT structure rule
+ * with the specified rule ID, otherwise {@code false}.
+ */
+ public boolean hasDITStructureRule(int ruleID)
+ {
+ return impl.hasDITStructureRule(ruleID);
+ }
+
+
+
+ /**
+ * Indicates whether or not this schema contains a matching rule with
+ * the specified name or numeric OID.
+ *
+ * @param name
+ * The name or OID of the matching rule.
+ * @return {@code true} if this schema contains a matching rule with
+ * the specified name or numeric OID, otherwise {@code false}.
+ */
+ public boolean hasMatchingRule(String name)
+ {
+ return impl.hasMatchingRule(name);
+ }
+
+
+
+ /**
+ * Indicates whether or not this schema contains a matching rule use
+ * with the specified name or numeric OID.
+ *
+ * @param name
+ * The name or OID of the matching rule use.
+ * @return {@code true} if this schema contains a matching rule use
+ * with the specified name or numeric OID, otherwise {@code
+ * false}.
+ */
+ public boolean hasMatchingRuleUse(String name)
+ {
+ return impl.hasMatchingRuleUse(name);
+ }
+
+
+
+ /**
+ * Indicates whether or not this schema contains a name form with the
+ * specified name or numeric OID.
+ *
+ * @param name
+ * The name or OID of the name form.
+ * @return {@code true} if this schema contains a name form with the
+ * specified name or numeric OID, otherwise {@code false}.
+ */
+ public boolean hasNameForm(String name)
+ {
+ return impl.hasNameForm(name);
+ }
+
+
+
+ /**
+ * Indicates whether or not this schema contains an object class with
+ * the specified name or numeric OID.
+ *
+ * @param name
+ * The name or OID of the object class.
+ * @return {@code true} if this schema contains an object class with
+ * the specified name or numeric OID, otherwise {@code false}.
+ */
+ public boolean hasObjectClass(String name)
+ {
+ return impl.hasObjectClass(name);
+ }
+
+
+
+ /**
+ * Indicates whether or not this schema contains a syntax with the
+ * specified numeric OID.
+ *
+ * @param numericOID
+ * The OID of the syntax.
+ * @return {@code true} if this schema contains a syntax with the
+ * specified numeric OID, otherwise {@code false}.
+ */
+ public boolean hasSyntax(String numericOID)
+ {
+ return impl.hasSyntax(numericOID);
+ }
+
+
+
+ /**
+ * Indicates whether or not this schema is strict. Attribute type
+ * queries in non-strict schema always succeed: if the requested
+ * attribute type is not found then a temporary attribute type is
+ * created automatically having the Octet String syntax and associated
+ * matching rules. Strict schema, on the other hand, throw an
+ * {@link UnknownSchemaElementException} whenever an attempt is made
+ * to retrieve a non-existent attribute type.
+ *
+ * @return {@code true} if this schema is strict.
+ */
+ public boolean isStrict()
+ {
+ return impl.isStrict();
+ }
+
+
+
+ /**
+ * Returns a non-strict view of this schema. Attribute type queries in
+ * non-strict schema always succeed: if the requested attribute type
+ * is not found then a temporary attribute type is created
+ * automatically having the Octet String syntax and associated
+ * matching rules. Strict schema, on the other hand, throw an
+ * {@link UnknownSchemaElementException} whenever an attempt is made
+ * to retrieve a non-existent attribute type.
+ *
+ * @return A non-strict view of this schema.
+ */
+ public Schema nonStrict()
+ {
+ if (impl.isStrict())
+ {
+ return new Schema(new NonStrictImpl(impl));
+ }
+ else
+ {
+ return this;
+ }
+ }
+
+
+
+ <T> T getAttachment(SchemaLocal<T> attachment)
+ {
+ return impl.getAttachment(attachment);
+ }
+
+
+
+ SchemaCompatOptions getSchemaCompatOptions()
+ {
+ return impl.getSchemaCompatOptions();
+ }
+
+
+
+ <T> T removeAttachment(SchemaLocal<T> attachment)
+ {
+ return impl.removeAttachment(attachment);
+ }
+
+
+
+ <T> void setAttachment(SchemaLocal<T> attachment, T value)
+ {
+ impl.setAttachment(attachment, value);
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/SchemaBuilder.java b/sdk/src/org/opends/sdk/schema/SchemaBuilder.java
new file mode 100644
index 0000000..f8f1c9e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/SchemaBuilder.java
@@ -0,0 +1,3007 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.CoreMessages.*;
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.schema.SchemaConstants.EXTENSIBLE_OBJECT_OBJECTCLASS_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_GENERIC_ENUM_NAME;
+import static org.opends.sdk.schema.SchemaConstants.SCHEMA_PROPERTY_APPROX_RULE;
+import static org.opends.sdk.schema.SchemaConstants.TOP_OBJECTCLASS_NAME;
+
+import java.util.*;
+import java.util.regex.Pattern;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.SubstringReader;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Schema builders should be used for incremental construction of new
+ * schemas.
+ */
+public final class SchemaBuilder
+{
+
+ private Map<Integer, DITStructureRule> id2StructureRules;
+ private Map<String, List<AttributeType>> name2AttributeTypes;
+ private Map<String, List<DITContentRule>> name2ContentRules;
+ private Map<String, List<MatchingRule>> name2MatchingRules;
+ private Map<String, List<MatchingRuleUse>> name2MatchingRuleUses;
+ private Map<String, List<NameForm>> name2NameForms;
+ private Map<String, List<ObjectClass>> name2ObjectClasses;
+ private Map<String, List<DITStructureRule>> name2StructureRules;
+ private Map<String, List<DITStructureRule>> nameForm2StructureRules;
+ private Map<String, AttributeType> numericOID2AttributeTypes;
+ private Map<String, DITContentRule> numericOID2ContentRules;
+ private Map<String, MatchingRule> numericOID2MatchingRules;
+ private Map<String, MatchingRuleUse> numericOID2MatchingRuleUses;
+ private Map<String, NameForm> numericOID2NameForms;
+ private Map<String, ObjectClass> numericOID2ObjectClasses;
+ private Map<String, Syntax> numericOID2Syntaxes;
+ private Map<String, List<NameForm>> objectClass2NameForms;
+ private SchemaCompatOptions options;
+ private Schema schema;
+
+
+
+ /**
+ * Creates a new schema builder with no schema elements and default
+ * compatibility options.
+ */
+ public SchemaBuilder()
+ {
+ initBuilder();
+ }
+
+
+
+ /**
+ * Creates a new schema builder containing all of the schema elements
+ * from the provided schema and its compatibility options.
+ *
+ * @param schema
+ * The initial contents of the schema builder.
+ * @throws NullPointerException
+ * If {@code schema} was {@code null}.
+ */
+ public SchemaBuilder(Schema schema) throws NullPointerException
+ {
+ Validator.ensureNotNull(schema);
+ initBuilder();
+ setSchemaCompatOptions(schema.getSchemaCompatOptions());
+ addSchema(schema, true);
+ }
+
+
+
+ /**
+ * Adds the provided attribute type definition to this schema builder.
+ *
+ * @param definition
+ * The attribute type definition.
+ * @param overwrite
+ * {@code true} if any existing attribute type with the same
+ * OID should be overwritten.
+ * @return A reference to this schema builder.
+ * @throws LocalizedIllegalArgumentException
+ * If the provided attribute type definition could not be
+ * parsed.
+ * @throws ConflictingSchemaElementException
+ * If {@code overwrite} was {@code false} and a conflicting
+ * schema element was found.
+ * @throws NullPointerException
+ * If {@code definition} was {@code null}.
+ */
+ public SchemaBuilder addAttributeType(String definition,
+ boolean overwrite) throws LocalizedIllegalArgumentException,
+ ConflictingSchemaElementException
+ {
+ Validator.ensureNotNull(definition);
+ try
+ {
+ final SubstringReader reader = new SubstringReader(definition);
+
+ // We'll do this a character at a time. First, skip over any
+ // leading whitespace.
+ reader.skipWhitespaces();
+
+ if (reader.remaining() <= 0)
+ {
+ // This means that the definition was empty or contained only
+ // whitespace. That is illegal.
+ final Message message =
+ ERR_ATTR_SYNTAX_ATTRTYPE_EMPTY_VALUE.get();
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // The next character must be an open parenthesis. If it is not,
+ // then that is an error.
+ final char c = reader.read();
+ if (c != '(')
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_ATTRTYPE_EXPECTED_OPEN_PARENTHESIS.get(
+ definition, (reader.pos() - 1), String.valueOf(c));
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // Skip over any spaces immediately following the opening
+ // parenthesis.
+ reader.skipWhitespaces();
+
+ // The next set of characters must be the OID.
+ final String oid = SchemaUtils.readOID(reader);
+
+ List<String> names = Collections.emptyList();
+ String description = "".intern();
+ boolean isObsolete = false;
+ String superiorType = null;
+ String equalityMatchingRule = null;
+ String orderingMatchingRule = null;
+ String substringMatchingRule = null;
+ String approximateMatchingRule = null;
+ String syntax = null;
+ boolean isSingleValue = false;
+ boolean isCollective = false;
+ boolean isNoUserModification = false;
+ AttributeUsage attributeUsage = AttributeUsage.USER_APPLICATIONS;
+ Map<String, List<String>> extraProperties =
+ Collections.emptyMap();
+
+ // At this point, we should have a pretty specific syntax that
+ // describes what may come next, but some of the components are
+ // optional and it would be pretty easy to put something in the
+ // wrong order, so we will be very flexible about what we can
+ // accept. Just look at the next token, figure out what it is and
+ // how to treat what comes after it, then repeat until we get to
+ // the end of the definition. But before we start, set default
+ // values for everything else we might need to know.
+ while (true)
+ {
+ final String tokenName = SchemaUtils.readTokenName(reader);
+
+ if (tokenName == null)
+ {
+ // No more tokens.
+ break;
+ }
+ else if (tokenName.equalsIgnoreCase("name"))
+ {
+ names = SchemaUtils.readNameDescriptors(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("desc"))
+ {
+ // This specifies the description for the attribute type. It
+ // is an arbitrary string of characters enclosed in single
+ // quotes.
+ description = SchemaUtils.readQuotedString(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("obsolete"))
+ {
+ // This indicates whether the attribute type should be
+ // considered obsolete. We do not need to do any more parsing
+ // for this token.
+ isObsolete = true;
+ }
+ else if (tokenName.equalsIgnoreCase("sup"))
+ {
+ // This specifies the name or OID of the superior attribute
+ // type from which this attribute type should inherit its
+ // properties.
+ superiorType = SchemaUtils.readOID(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("equality"))
+ {
+ // This specifies the name or OID of the equality matching
+ // rule to use for this attribute type.
+ equalityMatchingRule = SchemaUtils.readOID(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("ordering"))
+ {
+ // This specifies the name or OID of the ordering matching
+ // rule to use for this attribute type.
+ orderingMatchingRule = SchemaUtils.readOID(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("substr"))
+ {
+ // This specifies the name or OID of the substring matching
+ // rule to use for this attribute type.
+ substringMatchingRule = SchemaUtils.readOID(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("syntax"))
+ {
+ // This specifies the numeric OID of the syntax for this
+ // matching rule. It may optionally be immediately followed by
+ // an open curly brace, an integer definition, and a close
+ // curly brace to suggest the minimum number of characters
+ // that should be allowed in values of that type. This
+ // implementation will ignore any such length because it does
+ // not impose any practical limit on the length of attribute
+ // values.
+ syntax = SchemaUtils.readOIDLen(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("single-definition"))
+ {
+ // This indicates that attributes of this type are allowed to
+ // have at most one definition. We do not need any more
+ // parsing for this token.
+ isSingleValue = true;
+ }
+ else if (tokenName.equalsIgnoreCase("single-value"))
+ {
+ // This indicates that attributes of this type are allowed to
+ // have at most one value. We do not need any more parsing for
+ // this token.
+ isSingleValue = true;
+ }
+ else if (tokenName.equalsIgnoreCase("collective"))
+ {
+ // This indicates that attributes of this type are collective
+ // (i.e., have their values generated dynamically in some
+ // way). We do not need any more parsing for this token.
+ isCollective = true;
+ }
+ else if (tokenName.equalsIgnoreCase("no-user-modification"))
+ {
+ // This indicates that the values of attributes of this type
+ // are not to be modified by end users. We do not need any
+ // more parsing for this token.
+ isNoUserModification = true;
+ }
+ else if (tokenName.equalsIgnoreCase("usage"))
+ {
+ // This specifies the usage string for this attribute type. It
+ // should be followed by one of the strings
+ // "userApplications", "directoryOperation",
+ // "distributedOperation", or "dSAOperation".
+ int length = 0;
+
+ reader.skipWhitespaces();
+ reader.mark();
+
+ while (reader.read() != ' ')
+ {
+ length++;
+ }
+
+ reader.reset();
+ final String usageStr = reader.read(length);
+ if (usageStr.equalsIgnoreCase("userapplications"))
+ {
+ attributeUsage = AttributeUsage.USER_APPLICATIONS;
+ }
+ else if (usageStr.equalsIgnoreCase("directoryoperation"))
+ {
+ attributeUsage = AttributeUsage.DIRECTORY_OPERATION;
+ }
+ else if (usageStr.equalsIgnoreCase("distributedoperation"))
+ {
+ attributeUsage = AttributeUsage.DISTRIBUTED_OPERATION;
+ }
+ else if (usageStr.equalsIgnoreCase("dsaoperation"))
+ {
+ attributeUsage = AttributeUsage.DSA_OPERATION;
+ }
+ else
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_ATTRTYPE_INVALID_ATTRIBUTE_USAGE.get(
+ String.valueOf(oid), usageStr);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ }
+ else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+ {
+ // This must be a non-standard property and it must be
+ // followed by either a single definition in single quotes or
+ // an open parenthesis followed by one or more values in
+ // single quotes separated by spaces followed by a close
+ // parenthesis.
+ if (extraProperties.isEmpty())
+ {
+ extraProperties = new HashMap<String, List<String>>();
+ }
+ extraProperties.put(tokenName, SchemaUtils
+ .readExtensions(reader));
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ }
+
+ final List<String> approxRules =
+ extraProperties.get(SCHEMA_PROPERTY_APPROX_RULE);
+ if (approxRules != null && !approxRules.isEmpty())
+ {
+ approximateMatchingRule = approxRules.get(0);
+ }
+
+ final AttributeType attrType =
+ new AttributeType(oid, names, description, isObsolete,
+ superiorType, equalityMatchingRule, orderingMatchingRule,
+ substringMatchingRule, approximateMatchingRule, syntax,
+ isSingleValue, isCollective, isNoUserModification,
+ attributeUsage, extraProperties, definition);
+
+ addAttributeType(attrType, overwrite);
+ }
+ catch (final DecodeException e)
+ {
+ throw new LocalizedIllegalArgumentException(e.getMessageObject(),
+ e.getCause());
+ }
+ return this;
+ }
+
+
+
+ /**
+ * Adds the provided attribute type definition to this schema builder.
+ *
+ * @param oid
+ * The OID of the attribute type definition.
+ * @param names
+ * The user-friendly names of the attribute type definition.
+ * @param description
+ * The description of the attribute type definition.
+ * @param obsolete
+ * {@code true} if the attribute type definition is obsolete,
+ * otherwise {@code false}.
+ * @param superiorType
+ * The OID of the superior attribute type definition.
+ * @param equalityMatchingRule
+ * The OID of the equality matching rule, which may be
+ * {@code null} indicating that the superior attribute type's
+ * matching rule should be used or, if none is defined, the
+ * default matching rule associated with the syntax.
+ * @param orderingMatchingRule
+ * The OID of the ordering matching rule, which may be
+ * {@code null} indicating that the superior attribute type's
+ * matching rule should be used or, if none is defined, the
+ * default matching rule associated with the syntax.
+ * @param substringMatchingRule
+ * The OID of the substring matching rule, which may be
+ * {@code null} indicating that the superior attribute type's
+ * matching rule should be used or, if none is defined, the
+ * default matching rule associated with the syntax.
+ * @param approximateMatchingRule
+ * The OID of the approximate matching rule, which may be
+ * {@code null} indicating that the superior attribute type's
+ * matching rule should be used or, if none is defined, the
+ * default matching rule associated with the syntax.
+ * @param syntax
+ * The OID of the syntax definition.
+ * @param singleValue
+ * {@code true} if the attribute type definition is
+ * single-valued, otherwise {@code false}.
+ * @param collective
+ * {@code true} if the attribute type definition is a
+ * collective attribute, otherwise {@code false}.
+ * @param noUserModification
+ * {@code true} if the attribute type definition is
+ * read-only, otherwise {@code false}.
+ * @param attributeUsage
+ * The intended use of the attribute type definition.
+ * @param extraProperties
+ * A map containing additional properties associated with the
+ * attribute type definition.
+ * @param overwrite
+ * {@code true} if any existing attribute type with the same
+ * OID should be overwritten.
+ * @return A reference to this schema builder.
+ * @throws ConflictingSchemaElementException
+ * If {@code overwrite} was {@code false} and a conflicting
+ * schema element was found.
+ */
+ public SchemaBuilder addAttributeType(String oid, List<String> names,
+ String description, boolean obsolete, String superiorType,
+ String equalityMatchingRule, String orderingMatchingRule,
+ String substringMatchingRule, String approximateMatchingRule,
+ String syntax, boolean singleValue, boolean collective,
+ boolean noUserModification, AttributeUsage attributeUsage,
+ Map<String, List<String>> extraProperties, boolean overwrite)
+ throws ConflictingSchemaElementException
+ {
+ final AttributeType attrType =
+ new AttributeType(oid, names, description, obsolete,
+ superiorType, equalityMatchingRule, orderingMatchingRule,
+ substringMatchingRule, approximateMatchingRule, syntax,
+ singleValue, collective, noUserModification,
+ attributeUsage, extraProperties, null);
+ addAttributeType(attrType, overwrite);
+ return this;
+ }
+
+
+
+ /**
+ * Adds the provided DIT content rule definition to this schema
+ * builder.
+ *
+ * @param definition
+ * The DIT content rule definition.
+ * @param overwrite
+ * {@code true} if any existing DIT content rule with the
+ * same OID should be overwritten.
+ * @return A reference to this schema builder.
+ * @throws LocalizedIllegalArgumentException
+ * If the provided DIT content rule definition could not be
+ * parsed.
+ * @throws ConflictingSchemaElementException
+ * If {@code overwrite} was {@code false} and a conflicting
+ * schema element was found.
+ * @throws NullPointerException
+ * If {@code definition} was {@code null}.
+ */
+ public SchemaBuilder addDITContentRule(String definition,
+ boolean overwrite) throws LocalizedIllegalArgumentException,
+ ConflictingSchemaElementException
+ {
+ Validator.ensureNotNull(definition);
+ try
+ {
+ final SubstringReader reader = new SubstringReader(definition);
+
+ // We'll do this a character at a time. First, skip over any
+ // leading whitespace.
+ reader.skipWhitespaces();
+
+ if (reader.remaining() <= 0)
+ {
+ // This means that the value was empty or contained only
+ // whitespace. That is illegal.
+ final Message message = ERR_ATTR_SYNTAX_DCR_EMPTY_VALUE.get();
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // The next character must be an open parenthesis. If it is not,
+ // then that is an error.
+ final char c = reader.read();
+ if (c != '(')
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_DCR_EXPECTED_OPEN_PARENTHESIS.get(
+ definition, (reader.pos() - 1), String.valueOf(c));
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // Skip over any spaces immediately following the opening
+ // parenthesis.
+ reader.skipWhitespaces();
+
+ // The next set of characters must be the OID.
+ final String structuralClass = SchemaUtils.readOID(reader);
+
+ List<String> names = Collections.emptyList();
+ String description = "".intern();
+ boolean isObsolete = false;
+ Set<String> auxiliaryClasses = Collections.emptySet();
+ Set<String> optionalAttributes = Collections.emptySet();
+ Set<String> prohibitedAttributes = Collections.emptySet();
+ Set<String> requiredAttributes = Collections.emptySet();
+ Map<String, List<String>> extraProperties =
+ Collections.emptyMap();
+
+ // At this point, we should have a pretty specific syntax that
+ // describes what may come next, but some of the components are
+ // optional and it would be pretty easy to put something in the
+ // wrong order, so we will be very flexible about what we can
+ // accept. Just look at the next token, figure out what it is and
+ // how to treat what comes after it, then repeat until we get to
+ // the end of the value. But before we start, set default values
+ // for everything else we might need to know.
+ while (true)
+ {
+ final String tokenName = SchemaUtils.readTokenName(reader);
+
+ if (tokenName == null)
+ {
+ // No more tokens.
+ break;
+ }
+ else if (tokenName.equalsIgnoreCase("name"))
+ {
+ names = SchemaUtils.readNameDescriptors(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("desc"))
+ {
+ // This specifies the description for the attribute type. It
+ // is an arbitrary string of characters enclosed in single
+ // quotes.
+ description = SchemaUtils.readQuotedString(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("obsolete"))
+ {
+ // This indicates whether the attribute type should be
+ // considered obsolete. We do not need to do any more parsing
+ // for this token.
+ isObsolete = true;
+ }
+ else if (tokenName.equalsIgnoreCase("aux"))
+ {
+ auxiliaryClasses = SchemaUtils.readOIDs(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("must"))
+ {
+ requiredAttributes = SchemaUtils.readOIDs(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("may"))
+ {
+ optionalAttributes = SchemaUtils.readOIDs(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("not"))
+ {
+ prohibitedAttributes = SchemaUtils.readOIDs(reader);
+ }
+ else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+ {
+ // This must be a non-standard property and it must be
+ // followed by either a single definition in single quotes or
+ // an open parenthesis followed by one or more values in
+ // single quotes separated by spaces followed by a close
+ // parenthesis.
+ if (extraProperties.isEmpty())
+ {
+ extraProperties = new HashMap<String, List<String>>();
+ }
+ extraProperties.put(tokenName, SchemaUtils
+ .readExtensions(reader));
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ }
+
+ final DITContentRule rule =
+ new DITContentRule(structuralClass, names, description,
+ isObsolete, auxiliaryClasses, optionalAttributes,
+ prohibitedAttributes, requiredAttributes,
+ extraProperties, definition);
+ addDITContentRule(rule, overwrite);
+ }
+ catch (final DecodeException e)
+ {
+ throw new LocalizedIllegalArgumentException(e.getMessageObject(),
+ e.getCause());
+ }
+ return this;
+ }
+
+
+
+ /**
+ * Adds the provided DIT content rule definition to this schema
+ * builder.
+ *
+ * @param structuralClass
+ * The name of the structural object class to which the DIT
+ * content rule applies.
+ * @param names
+ * The user-friendly names of the DIT content rule
+ * definition.
+ * @param description
+ * The description of the DIT content rule definition.
+ * @param obsolete
+ * {@code true} if the DIT content rule definition is
+ * obsolete, otherwise {@code false}.
+ * @param auxiliaryClasses
+ * A list of auxiliary object classes that entries subject to
+ * the DIT content rule may belong to.
+ * @param optionalAttributes
+ * A list of attribute types that entries subject to the DIT
+ * content rule may contain.
+ * @param prohibitedAttributes
+ * A list of attribute types that entries subject to the DIT
+ * content rule must not contain.
+ * @param requiredAttributes
+ * A list of attribute types that entries subject to the DIT
+ * content rule must contain.
+ * @param extraProperties
+ * A map containing additional properties associated with the
+ * DIT content rule definition.
+ * @param overwrite
+ * {@code true} if any existing DIT content rule with the
+ * same OID should be overwritten.
+ * @return A reference to this schema builder.
+ * @throws ConflictingSchemaElementException
+ * If {@code overwrite} was {@code false} and a conflicting
+ * schema element was found.
+ */
+ public SchemaBuilder addDITContentRule(String structuralClass,
+ List<String> names, String description, boolean obsolete,
+ Set<String> auxiliaryClasses, Set<String> optionalAttributes,
+ Set<String> prohibitedAttributes, Set<String> requiredAttributes,
+ Map<String, List<String>> extraProperties, boolean overwrite)
+ throws ConflictingSchemaElementException
+ {
+ final DITContentRule rule =
+ new DITContentRule(structuralClass, names, description,
+ obsolete, auxiliaryClasses, optionalAttributes,
+ prohibitedAttributes, requiredAttributes, extraProperties,
+ null);
+ addDITContentRule(rule, overwrite);
+ return this;
+ }
+
+
+
+ /**
+ * Adds the provided DIT structure rule definition to this schema
+ * builder.
+ *
+ * @param ruleID
+ * The rule identifier of the DIT structure rule.
+ * @param names
+ * The user-friendly names of the DIT structure rule
+ * definition.
+ * @param description
+ * The description of the DIT structure rule definition.
+ * @param obsolete
+ * {@code true} if the DIT structure rule definition is
+ * obsolete, otherwise {@code false}.
+ * @param nameForm
+ * The name form associated with the DIT structure rule.
+ * @param superiorRules
+ * A list of superior rules (by rule id).
+ * @param extraProperties
+ * A map containing additional properties associated with the
+ * DIT structure rule definition.
+ * @param overwrite
+ * {@code true} if any existing DIT structure rule with the
+ * same OID should be overwritten.
+ * @return A reference to this schema builder.
+ * @throws ConflictingSchemaElementException
+ * If {@code overwrite} was {@code false} and a conflicting
+ * schema element was found.
+ */
+ public SchemaBuilder addDITStructureRule(Integer ruleID,
+ List<String> names, String description, boolean obsolete,
+ String nameForm, Set<Integer> superiorRules,
+ Map<String, List<String>> extraProperties, boolean overwrite)
+ throws ConflictingSchemaElementException
+ {
+ final DITStructureRule rule =
+ new DITStructureRule(ruleID, names, description, obsolete,
+ nameForm, superiorRules, extraProperties, null);
+ addDITStructureRule(rule, overwrite);
+ return this;
+ }
+
+
+
+ /**
+ * Adds the provided DIT structure rule definition to this schema
+ * builder.
+ *
+ * @param definition
+ * The DIT structure rule definition.
+ * @param overwrite
+ * {@code true} if any existing DIT structure rule with the
+ * same OID should be overwritten.
+ * @return A reference to this schema builder.
+ * @throws LocalizedIllegalArgumentException
+ * If the provided DIT structure rule definition could not
+ * be parsed.
+ * @throws ConflictingSchemaElementException
+ * If {@code overwrite} was {@code false} and a conflicting
+ * schema element was found.
+ * @throws NullPointerException
+ * If {@code definition} was {@code null}.
+ */
+ public SchemaBuilder addDITStructureRule(String definition,
+ boolean overwrite) throws LocalizedIllegalArgumentException,
+ ConflictingSchemaElementException
+ {
+ Validator.ensureNotNull(definition);
+ try
+ {
+ final SubstringReader reader = new SubstringReader(definition);
+
+ // We'll do this a character at a time. First, skip over any
+ // leading whitespace.
+ reader.skipWhitespaces();
+
+ if (reader.remaining() <= 0)
+ {
+ // This means that the value was empty or contained only
+ // whitespace. That is illegal.
+ final Message message = ERR_ATTR_SYNTAX_DSR_EMPTY_VALUE.get();
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // The next character must be an open parenthesis. If it is not,
+ // then that is an error.
+ final char c = reader.read();
+ if (c != '(')
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_DSR_EXPECTED_OPEN_PARENTHESIS.get(
+ definition, (reader.pos() - 1), String.valueOf(c));
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // Skip over any spaces immediately following the opening
+ // parenthesis.
+ reader.skipWhitespaces();
+
+ // The next set of characters must be the OID.
+ final Integer ruleID = SchemaUtils.readRuleID(reader);
+
+ List<String> names = Collections.emptyList();
+ String description = "".intern();
+ boolean isObsolete = false;
+ String nameForm = null;
+ Set<Integer> superiorRules = Collections.emptySet();
+ Map<String, List<String>> extraProperties =
+ Collections.emptyMap();
+
+ // At this point, we should have a pretty specific syntax that
+ // describes what may come next, but some of the components are
+ // optional and it would be pretty easy to put something in the
+ // wrong order, so we will be very flexible about what we can
+ // accept. Just look at the next token, figure out what it is and
+ // how to treat what comes after it, then repeat until we get to
+ // the end of the value. But before we start, set default values
+ // for everything else we might need to know.
+ while (true)
+ {
+ final String tokenName = SchemaUtils.readTokenName(reader);
+
+ if (tokenName == null)
+ {
+ // No more tokens.
+ break;
+ }
+ else if (tokenName.equalsIgnoreCase("name"))
+ {
+ names = SchemaUtils.readNameDescriptors(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("desc"))
+ {
+ // This specifies the description for the attribute type. It
+ // is an arbitrary string of characters enclosed in single
+ // quotes.
+ description = SchemaUtils.readQuotedString(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("obsolete"))
+ {
+ // This indicates whether the attribute type should be
+ // considered obsolete. We do not need to do any more parsing
+ // for this token.
+ isObsolete = true;
+ }
+ else if (tokenName.equalsIgnoreCase("form"))
+ {
+ nameForm = SchemaUtils.readOID(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("sup"))
+ {
+ superiorRules = SchemaUtils.readRuleIDs(reader);
+ }
+ else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+ {
+ // This must be a non-standard property and it must be
+ // followed by either a single definition in single quotes or
+ // an open parenthesis followed by one or more values in
+ // single quotes separated by spaces followed by a close
+ // parenthesis.
+ if (extraProperties.isEmpty())
+ {
+ extraProperties = new HashMap<String, List<String>>();
+ }
+ extraProperties.put(tokenName, SchemaUtils
+ .readExtensions(reader));
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ }
+
+ if (nameForm == null)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_DSR_NO_NAME_FORM.get(definition);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ final DITStructureRule rule =
+ new DITStructureRule(ruleID, names, description, isObsolete,
+ nameForm, superiorRules, extraProperties, definition);
+ addDITStructureRule(rule, overwrite);
+ }
+ catch (final DecodeException e)
+ {
+ throw new LocalizedIllegalArgumentException(e.getMessageObject(),
+ e.getCause());
+ }
+ return this;
+ }
+
+
+
+ /**
+ * Adds the provided enumeration syntax definition to this schema
+ * builder.
+ *
+ * @param oid
+ * The OID of the enumeration syntax definition.
+ * @param description
+ * The description of the enumeration syntax definition.
+ * @param overwrite
+ * {@code true} if any existing syntax with the same OID
+ * should be overwritten.
+ * @param enumerations
+ * The range of values which attribute values must match in
+ * order to be valid.
+ * @return A reference to this schema builder.
+ * @throws ConflictingSchemaElementException
+ * If {@code overwrite} was {@code false} and a conflicting
+ * schema element was found.
+ */
+ public SchemaBuilder addEnumerationSyntax(String oid,
+ String description, boolean overwrite, String... enumerations)
+ throws ConflictingSchemaElementException
+ {
+ Validator.ensureNotNull((Object) enumerations);
+
+ final EnumSyntaxImpl enumImpl =
+ new EnumSyntaxImpl(oid, Arrays.asList(enumerations));
+ final Syntax enumSyntax =
+ new Syntax(oid, description, Collections.singletonMap("X-ENUM",
+ Arrays.asList(enumerations)), null, enumImpl);
+ final MatchingRule enumOMR =
+ new MatchingRule(enumImpl.getOrderingMatchingRule(),
+ Collections.singletonList(OMR_GENERIC_ENUM_NAME + oid), "",
+ false, oid, CoreSchemaImpl.OPENDS_ORIGIN, null,
+ new EnumOrderingMatchingRule(enumImpl));
+
+ addSyntax(enumSyntax, overwrite);
+ try
+ {
+ addMatchingRule(enumOMR, overwrite);
+ }
+ catch (final ConflictingSchemaElementException e)
+ {
+ removeSyntax(oid);
+ }
+ return this;
+ }
+
+
+
+ /**
+ * Adds the provided matching rule definition to this schema builder.
+ *
+ * @param definition
+ * The matching rule definition.
+ * @param overwrite
+ * {@code true} if any existing matching rule with the same
+ * OID should be overwritten.
+ * @return A reference to this schema builder.
+ * @throws LocalizedIllegalArgumentException
+ * If the provided matching rule definition could not be
+ * parsed.
+ * @throws ConflictingSchemaElementException
+ * If {@code overwrite} was {@code false} and a conflicting
+ * schema element was found.
+ * @throws NullPointerException
+ * If {@code definition} was {@code null}.
+ */
+ public SchemaBuilder addMatchingRule(String definition,
+ boolean overwrite) throws LocalizedIllegalArgumentException,
+ ConflictingSchemaElementException
+ {
+ Validator.ensureNotNull(definition);
+ try
+ {
+ final SubstringReader reader = new SubstringReader(definition);
+
+ // We'll do this a character at a time. First, skip over any
+ // leading whitespace.
+ reader.skipWhitespaces();
+
+ if (reader.remaining() <= 0)
+ {
+ // This means that the value was empty or contained only
+ // whitespace. That is illegal.
+ final Message message = ERR_ATTR_SYNTAX_MR_EMPTY_VALUE.get();
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // The next character must be an open parenthesis. If it is not,
+ // then that is an error.
+ final char c = reader.read();
+ if (c != '(')
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_MR_EXPECTED_OPEN_PARENTHESIS.get(
+ definition, (reader.pos() - 1), String.valueOf(c));
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // Skip over any spaces immediately following the opening
+ // parenthesis.
+ reader.skipWhitespaces();
+
+ // The next set of characters must be the OID.
+ final String oid = SchemaUtils.readOID(reader);
+
+ List<String> names = Collections.emptyList();
+ String description = "".intern();
+ boolean isObsolete = false;
+ String syntax = null;
+ Map<String, List<String>> extraProperties =
+ Collections.emptyMap();
+
+ // At this point, we should have a pretty specific syntax that
+ // describes what may come next, but some of the components are
+ // optional and it would be pretty easy to put something in the
+ // wrong order, so we will be very flexible about what we can
+ // accept. Just look at the next token, figure out what it is and
+ // how to treat what comes after it, then repeat until we get to
+ // the end of the value. But before we start, set default values
+ // for everything else we might need to know.
+ while (true)
+ {
+ final String tokenName = SchemaUtils.readTokenName(reader);
+
+ if (tokenName == null)
+ {
+ // No more tokens.
+ break;
+ }
+ else if (tokenName.equalsIgnoreCase("name"))
+ {
+ names = SchemaUtils.readNameDescriptors(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("desc"))
+ {
+ // This specifies the description for the matching rule. It is
+ // an arbitrary string of characters enclosed in single
+ // quotes.
+ description = SchemaUtils.readQuotedString(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("obsolete"))
+ {
+ // This indicates whether the matching rule should be
+ // considered obsolete. We do not need to do any more parsing
+ // for this token.
+ isObsolete = true;
+ }
+ else if (tokenName.equalsIgnoreCase("syntax"))
+ {
+ syntax = SchemaUtils.readOID(reader);
+ }
+ else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+ {
+ // This must be a non-standard property and it must be
+ // followed by either a single definition in single quotes or
+ // an open parenthesis followed by one or more values in
+ // single quotes separated by spaces followed by a close
+ // parenthesis.
+ if (extraProperties.isEmpty())
+ {
+ extraProperties = new HashMap<String, List<String>>();
+ }
+ extraProperties.put(tokenName, SchemaUtils
+ .readExtensions(reader));
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ }
+
+ // Make sure that a syntax was specified.
+ if (syntax == null)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_MR_NO_SYNTAX.get(definition);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ addMatchingRule(new MatchingRule(oid, names, description,
+ isObsolete, syntax, extraProperties, definition, null),
+ overwrite);
+ }
+ catch (final DecodeException e)
+ {
+ throw new LocalizedIllegalArgumentException(e.getMessageObject(),
+ e.getCause());
+ }
+ return this;
+ }
+
+
+
+ /**
+ * Adds the provided matching rule definition to this schema builder.
+ *
+ * @param oid
+ * The OID of the matching rule definition.
+ * @param names
+ * The user-friendly names of the matching rule definition.
+ * @param description
+ * The description of the matching rule definition.
+ * @param obsolete
+ * {@code true} if the matching rule definition is obsolete,
+ * otherwise {@code false}.
+ * @param assertionSyntax
+ * The OID of the assertion syntax definition.
+ * @param extraProperties
+ * A map containing additional properties associated with the
+ * matching rule definition.
+ * @param implementation
+ * The implementation of the matching rule.
+ * @param overwrite
+ * {@code true} if any existing matching rule with the same
+ * OID should be overwritten.
+ * @return A reference to this schema builder.
+ * @throws ConflictingSchemaElementException
+ * If {@code overwrite} was {@code false} and a conflicting
+ * schema element was found.
+ */
+ public SchemaBuilder addMatchingRule(String oid, List<String> names,
+ String description, boolean obsolete, String assertionSyntax,
+ Map<String, List<String>> extraProperties,
+ MatchingRuleImpl implementation, boolean overwrite)
+ throws ConflictingSchemaElementException
+ {
+ Validator.ensureNotNull(implementation);
+ final MatchingRule matchingRule =
+ new MatchingRule(oid, names, description, obsolete,
+ assertionSyntax, extraProperties, null, implementation);
+ addMatchingRule(matchingRule, overwrite);
+ return this;
+ }
+
+
+
+ /**
+ * Adds the provided matching rule use definition to this schema
+ * builder.
+ *
+ * @param definition
+ * The matching rule use definition.
+ * @param overwrite
+ * {@code true} if any existing matching rule use with the
+ * same OID should be overwritten.
+ * @return A reference to this schema builder.
+ * @throws LocalizedIllegalArgumentException
+ * If the provided matching rule use definition could not be
+ * parsed.
+ * @throws ConflictingSchemaElementException
+ * If {@code overwrite} was {@code false} and a conflicting
+ * schema element was found.
+ * @throws NullPointerException
+ * If {@code definition} was {@code null}.
+ */
+ public SchemaBuilder addMatchingRuleUse(String definition,
+ boolean overwrite) throws LocalizedIllegalArgumentException,
+ ConflictingSchemaElementException
+ {
+ Validator.ensureNotNull(definition);
+ try
+ {
+ final SubstringReader reader = new SubstringReader(definition);
+
+ // We'll do this a character at a time. First, skip over any
+ // leading whitespace.
+ reader.skipWhitespaces();
+
+ if (reader.remaining() <= 0)
+ {
+ // This means that the value was empty or contained only
+ // whitespace. That is illegal.
+ final Message message = ERR_ATTR_SYNTAX_MRUSE_EMPTY_VALUE.get();
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // The next character must be an open parenthesis. If it is not,
+ // then that is an error.
+ final char c = reader.read();
+ if (c != '(')
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_MRUSE_EXPECTED_OPEN_PARENTHESIS.get(
+ definition, (reader.pos() - 1), String.valueOf(c));
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // Skip over any spaces immediately following the opening
+ // parenthesis.
+ reader.skipWhitespaces();
+
+ // The next set of characters must be the OID.
+ final String oid = SchemaUtils.readOID(reader);
+
+ List<String> names = Collections.emptyList();
+ String description = "".intern();
+ boolean isObsolete = false;
+ Set<String> attributes = null;
+ Map<String, List<String>> extraProperties =
+ Collections.emptyMap();
+
+ // At this point, we should have a pretty specific syntax that
+ // describes what may come next, but some of the components are
+ // optional and it would be pretty easy to put something in the
+ // wrong order, so we will be very flexible about what we can
+ // accept. Just look at the next token, figure out what it is and
+ // how to treat what comes after it, then repeat until we get to
+ // the end of the value. But before we start, set default values
+ // for everything else we might need to know.
+ while (true)
+ {
+ final String tokenName = SchemaUtils.readTokenName(reader);
+
+ if (tokenName == null)
+ {
+ // No more tokens.
+ break;
+ }
+ else if (tokenName.equalsIgnoreCase("name"))
+ {
+ names = SchemaUtils.readNameDescriptors(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("desc"))
+ {
+ // This specifies the description for the attribute type. It
+ // is an arbitrary string of characters enclosed in single
+ // quotes.
+ description = SchemaUtils.readQuotedString(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("obsolete"))
+ {
+ // This indicates whether the attribute type should be
+ // considered obsolete. We do not need to do any more parsing
+ // for this token.
+ isObsolete = true;
+ }
+ else if (tokenName.equalsIgnoreCase("applies"))
+ {
+ attributes = SchemaUtils.readOIDs(reader);
+ }
+ else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+ {
+ // This must be a non-standard property and it must be
+ // followed by either a single definition in single quotes or
+ // an open parenthesis followed by one or more values in
+ // single quotes separated by spaces followed by a close
+ // parenthesis.
+ if (extraProperties.isEmpty())
+ {
+ extraProperties = new HashMap<String, List<String>>();
+ }
+ extraProperties.put(tokenName, SchemaUtils
+ .readExtensions(reader));
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ }
+
+ // Make sure that the set of attributes was defined.
+ if (attributes == null || attributes.size() == 0)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_MRUSE_NO_ATTR.get(definition);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ final MatchingRuleUse use =
+ new MatchingRuleUse(oid, names, description, isObsolete,
+ attributes, extraProperties, definition);
+ addMatchingRuleUse(use, overwrite);
+ }
+ catch (final DecodeException e)
+ {
+ throw new LocalizedIllegalArgumentException(e.getMessageObject(),
+ e.getCause());
+ }
+ return this;
+ }
+
+
+
+ /**
+ * Adds the provided matching rule use definition to this schema
+ * builder.
+ *
+ * @param oid
+ * The OID of the matching rule use definition.
+ * @param names
+ * The user-friendly names of the matching rule use
+ * definition.
+ * @param description
+ * The description of the matching rule use definition.
+ * @param obsolete
+ * {@code true} if the matching rule use definition is
+ * obsolete, otherwise {@code false}.
+ * @param attributeOIDs
+ * The list of attribute types the matching rule applies to.
+ * @param extraProperties
+ * A map containing additional properties associated with the
+ * matching rule use definition.
+ * @param overwrite
+ * {@code true} if any existing matching rule use with the
+ * same OID should be overwritten.
+ * @return A reference to this schema builder.
+ * @throws ConflictingSchemaElementException
+ * If {@code overwrite} was {@code false} and a conflicting
+ * schema element was found.
+ */
+ public SchemaBuilder addMatchingRuleUse(String oid,
+ List<String> names, String description, boolean obsolete,
+ Set<String> attributeOIDs,
+ Map<String, List<String>> extraProperties, boolean overwrite)
+ throws ConflictingSchemaElementException
+ {
+ final MatchingRuleUse use =
+ new MatchingRuleUse(oid, names, description, obsolete,
+ attributeOIDs, extraProperties, null);
+ addMatchingRuleUse(use, overwrite);
+ return this;
+ }
+
+
+
+ /**
+ * Adds the provided name form definition to this schema builder.
+ *
+ * @param definition
+ * The name form definition.
+ * @param overwrite
+ * {@code true} if any existing name form with the same OID
+ * should be overwritten.
+ * @return A reference to this schema builder.
+ * @throws LocalizedIllegalArgumentException
+ * If the provided name form definition could not be parsed.
+ * @throws ConflictingSchemaElementException
+ * If {@code overwrite} was {@code false} and a conflicting
+ * schema element was found.
+ * @throws NullPointerException
+ * If {@code definition} was {@code null}.
+ */
+ public SchemaBuilder addNameForm(String definition, boolean overwrite)
+ throws LocalizedIllegalArgumentException,
+ ConflictingSchemaElementException
+ {
+ Validator.ensureNotNull(definition);
+ try
+ {
+ final SubstringReader reader = new SubstringReader(definition);
+
+ // We'll do this a character at a time. First, skip over any
+ // leading whitespace.
+ reader.skipWhitespaces();
+
+ if (reader.remaining() <= 0)
+ {
+ // This means that the value was empty or contained only
+ // whitespace. That is illegal.
+ final Message message =
+ ERR_ATTR_SYNTAX_NAME_FORM_EMPTY_VALUE.get();
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // The next character must be an open parenthesis. If it is not,
+ // then that is an error.
+ final char c = reader.read();
+ if (c != '(')
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_NAME_FORM_EXPECTED_OPEN_PARENTHESIS.get(
+ definition, (reader.pos() - 1), c);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // Skip over any spaces immediately following the opening
+ // parenthesis.
+ reader.skipWhitespaces();
+
+ // The next set of characters must be the OID.
+ final String oid = SchemaUtils.readOID(reader);
+
+ List<String> names = Collections.emptyList();
+ String description = "".intern();
+ boolean isObsolete = false;
+ String structuralClass = null;
+ Set<String> optionalAttributes = Collections.emptySet();
+ Set<String> requiredAttributes = null;
+ Map<String, List<String>> extraProperties =
+ Collections.emptyMap();
+
+ // At this point, we should have a pretty specific syntax that
+ // describes what may come next, but some of the components are
+ // optional and it would be pretty easy to put something in the
+ // wrong order, so we will be very flexible about what we can
+ // accept. Just look at the next token, figure out what it is and
+ // how to treat what comes after it, then repeat until we get to
+ // the end of the value. But before we start, set default values
+ // for everything else we might need to know.
+ while (true)
+ {
+ final String tokenName = SchemaUtils.readTokenName(reader);
+
+ if (tokenName == null)
+ {
+ // No more tokens.
+ break;
+ }
+ else if (tokenName.equalsIgnoreCase("name"))
+ {
+ names = SchemaUtils.readNameDescriptors(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("desc"))
+ {
+ // This specifies the description for the attribute type. It
+ // is an arbitrary string of characters enclosed in single
+ // quotes.
+ description = SchemaUtils.readQuotedString(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("obsolete"))
+ {
+ // This indicates whether the attribute type should be
+ // considered obsolete. We do not need to do any more parsing
+ // for this token.
+ isObsolete = true;
+ }
+ else if (tokenName.equalsIgnoreCase("oc"))
+ {
+ structuralClass = SchemaUtils.readOID(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("must"))
+ {
+ requiredAttributes = SchemaUtils.readOIDs(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("may"))
+ {
+ optionalAttributes = SchemaUtils.readOIDs(reader);
+ }
+ else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+ {
+ // This must be a non-standard property and it must be
+ // followed by either a single definition in single quotes or
+ // an open parenthesis followed by one or more values in
+ // single quotes separated by spaces followed by a close
+ // parenthesis.
+ if (extraProperties.isEmpty())
+ {
+ extraProperties = new HashMap<String, List<String>>();
+ }
+ extraProperties.put(tokenName, SchemaUtils
+ .readExtensions(reader));
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ }
+
+ // Make sure that a structural class was specified. If not, then
+ // it cannot be valid.
+ if (structuralClass == null)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_NAME_FORM_NO_STRUCTURAL_CLASS
+ .get(definition);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ if (requiredAttributes == null || requiredAttributes.size() == 0)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_NAME_FORM_NO_REQUIRED_ATTR.get(definition);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ final NameForm nameForm =
+ new NameForm(oid, names, description, isObsolete,
+ structuralClass, requiredAttributes, optionalAttributes,
+ extraProperties, definition);
+ addNameForm(nameForm, overwrite);
+ }
+ catch (final DecodeException e)
+ {
+ throw new LocalizedIllegalArgumentException(e.getMessageObject(),
+ e.getCause());
+ }
+ return this;
+ }
+
+
+
+ /**
+ * Adds the provided name form definition to this schema builder.
+ *
+ * @param oid
+ * The OID of the name form definition.
+ * @param names
+ * The user-friendly names of the name form definition.
+ * @param description
+ * The description of the name form definition.
+ * @param obsolete
+ * {@code true} if the name form definition is obsolete,
+ * otherwise {@code false}.
+ * @param structuralClass
+ * The structural object class this rule applies to.
+ * @param requiredAttributes
+ * A list of naming attribute types that entries subject to
+ * the name form must contain.
+ * @param optionalAttributes
+ * A list of naming attribute types that entries subject to
+ * the name form may contain.
+ * @param extraProperties
+ * A map containing additional properties associated with the
+ * name form definition.
+ * @param overwrite
+ * {@code true} if any existing name form use with the same
+ * OID should be overwritten.
+ * @return A reference to this schema builder.
+ * @throws ConflictingSchemaElementException
+ * If {@code overwrite} was {@code false} and a conflicting
+ * schema element was found.
+ */
+ public SchemaBuilder addNameForm(String oid, List<String> names,
+ String description, boolean obsolete, String structuralClass,
+ Set<String> requiredAttributes, Set<String> optionalAttributes,
+ Map<String, List<String>> extraProperties, boolean overwrite)
+ throws ConflictingSchemaElementException
+ {
+ final NameForm nameForm =
+ new NameForm(oid, names, description, obsolete,
+ structuralClass, requiredAttributes, optionalAttributes,
+ extraProperties, null);
+ addNameForm(nameForm, overwrite);
+ return this;
+ }
+
+
+
+ /**
+ * Adds the provided object class definition to this schema builder.
+ *
+ * @param definition
+ * The object class definition.
+ * @param overwrite
+ * {@code true} if any existing object class with the same
+ * OID should be overwritten.
+ * @return A reference to this schema builder.
+ * @throws LocalizedIllegalArgumentException
+ * If the provided object class definition could not be
+ * parsed.
+ * @throws ConflictingSchemaElementException
+ * If {@code overwrite} was {@code false} and a conflicting
+ * schema element was found.
+ * @throws NullPointerException
+ * If {@code definition} was {@code null}.
+ */
+ public SchemaBuilder addObjectClass(String definition,
+ boolean overwrite) throws LocalizedIllegalArgumentException,
+ ConflictingSchemaElementException
+ {
+ Validator.ensureNotNull(definition);
+ try
+ {
+ final SubstringReader reader = new SubstringReader(definition);
+
+ // We'll do this a character at a time. First, skip over any
+ // leading whitespace.
+ reader.skipWhitespaces();
+
+ if (reader.remaining() <= 0)
+ {
+ // This means that the value was empty or contained only
+ // whitespace. That is illegal.
+ final Message message =
+ ERR_ATTR_SYNTAX_OBJECTCLASS_EMPTY_VALUE.get();
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // The next character must be an open parenthesis. If it is not,
+ // then that is an error.
+ final char c = reader.read();
+ if (c != '(')
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_OBJECTCLASS_EXPECTED_OPEN_PARENTHESIS.get(
+ definition, (reader.pos() - 1), String.valueOf(c));
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // Skip over any spaces immediately following the opening
+ // parenthesis.
+ reader.skipWhitespaces();
+
+ // The next set of characters must be the OID.
+ final String oid = SchemaUtils.readOID(reader);
+
+ List<String> names = Collections.emptyList();
+ String description = "".intern();
+ boolean isObsolete = false;
+ Set<String> superiorClasses = Collections.emptySet();
+ Set<String> requiredAttributes = Collections.emptySet();
+ Set<String> optionalAttributes = Collections.emptySet();
+ ObjectClassType objectClassType = ObjectClassType.STRUCTURAL;
+ Map<String, List<String>> extraProperties =
+ Collections.emptyMap();
+
+ // At this point, we should have a pretty specific syntax that
+ // describes what may come next, but some of the components are
+ // optional and it would be pretty easy to put something in the
+ // wrong order, so we will be very flexible about what we can
+ // accept. Just look at the next token, figure out what it is and
+ // how to treat what comes after it, then repeat until we get to
+ // the end of the value. But before we start, set default values
+ // for everything else we might need to know.
+ while (true)
+ {
+ final String tokenName = SchemaUtils.readTokenName(reader);
+
+ if (tokenName == null)
+ {
+ // No more tokens.
+ break;
+ }
+ else if (tokenName.equalsIgnoreCase("name"))
+ {
+ names = SchemaUtils.readNameDescriptors(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("desc"))
+ {
+ // This specifies the description for the attribute type. It
+ // is an arbitrary string of characters enclosed in single
+ // quotes.
+ description = SchemaUtils.readQuotedString(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("obsolete"))
+ {
+ // This indicates whether the attribute type should be
+ // considered obsolete. We do not need to do any more parsing
+ // for this token.
+ isObsolete = true;
+ }
+ else if (tokenName.equalsIgnoreCase("sup"))
+ {
+ superiorClasses = SchemaUtils.readOIDs(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("abstract"))
+ {
+ // This indicates that entries must not include this
+ // objectclass unless they also include a non-abstract
+ // objectclass that inherits from this class. We do not need
+ // any more parsing for this token.
+ objectClassType = ObjectClassType.ABSTRACT;
+ }
+ else if (tokenName.equalsIgnoreCase("structural"))
+ {
+ // This indicates that this is a structural objectclass. We do
+ // not need any more parsing for this token.
+ objectClassType = ObjectClassType.STRUCTURAL;
+ }
+ else if (tokenName.equalsIgnoreCase("auxiliary"))
+ {
+ // This indicates that this is an auxiliary objectclass. We do
+ // not need any more parsing for this token.
+ objectClassType = ObjectClassType.AUXILIARY;
+ }
+ else if (tokenName.equalsIgnoreCase("must"))
+ {
+ requiredAttributes = SchemaUtils.readOIDs(reader);
+ }
+ else if (tokenName.equalsIgnoreCase("may"))
+ {
+ optionalAttributes = SchemaUtils.readOIDs(reader);
+ }
+ else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+ {
+ // This must be a non-standard property and it must be
+ // followed by either a single definition in single quotes or
+ // an open parenthesis followed by one or more values in
+ // single quotes separated by spaces followed by a close
+ // parenthesis.
+ if (extraProperties.isEmpty())
+ {
+ extraProperties = new HashMap<String, List<String>>();
+ }
+ extraProperties.put(tokenName, SchemaUtils
+ .readExtensions(reader));
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ }
+
+ if (oid.equals(EXTENSIBLE_OBJECT_OBJECTCLASS_OID))
+ {
+ addObjectClass(new ObjectClass(description, extraProperties),
+ overwrite);
+ }
+ else
+ {
+ if (objectClassType == ObjectClassType.STRUCTURAL
+ && superiorClasses.isEmpty())
+ {
+ superiorClasses = Collections.singleton(TOP_OBJECTCLASS_NAME);
+ }
+
+ addObjectClass(new ObjectClass(oid, names, description,
+ isObsolete, superiorClasses, requiredAttributes,
+ optionalAttributes, objectClassType, extraProperties,
+ definition), overwrite);
+ }
+ }
+ catch (final DecodeException e)
+ {
+ throw new LocalizedIllegalArgumentException(e.getMessageObject(),
+ e.getCause());
+ }
+ return this;
+ }
+
+
+
+ /**
+ * Adds the provided object class definition to this schema builder.
+ *
+ * @param oid
+ * The OID of the object class definition.
+ * @param names
+ * The user-friendly names of the object class definition.
+ * @param description
+ * The description of the object class definition.
+ * @param obsolete
+ * {@code true} if the object class definition is obsolete,
+ * otherwise {@code false}.
+ * @param superiorClassOIDs
+ * A list of direct superclasses of the object class.
+ * @param requiredAttributeOIDs
+ * A list of attribute types that entries must contain.
+ * @param optionalAttributeOIDs
+ * A list of attribute types that entries may contain.
+ * @param objectClassType
+ * The type of the object class.
+ * @param extraProperties
+ * A map containing additional properties associated with the
+ * object class definition.
+ * @param overwrite
+ * {@code true} if any existing object class with the same
+ * OID should be overwritten.
+ * @return A reference to this schema builder.
+ * @throws ConflictingSchemaElementException
+ * If {@code overwrite} was {@code false} and a conflicting
+ * schema element was found.
+ */
+ public SchemaBuilder addObjectClass(String oid, List<String> names,
+ String description, boolean obsolete,
+ Set<String> superiorClassOIDs, Set<String> requiredAttributeOIDs,
+ Set<String> optionalAttributeOIDs,
+ ObjectClassType objectClassType,
+ Map<String, List<String>> extraProperties, boolean overwrite)
+ throws ConflictingSchemaElementException
+ {
+ if (oid.equals(EXTENSIBLE_OBJECT_OBJECTCLASS_OID))
+ {
+ addObjectClass(new ObjectClass(description, extraProperties),
+ overwrite);
+ }
+ else
+ {
+ if (objectClassType == ObjectClassType.STRUCTURAL
+ && superiorClassOIDs.isEmpty())
+ {
+ superiorClassOIDs = Collections.singleton(TOP_OBJECTCLASS_NAME);
+ }
+
+ addObjectClass(
+ new ObjectClass(oid, names, description, obsolete,
+ superiorClassOIDs, requiredAttributeOIDs,
+ optionalAttributeOIDs, objectClassType, extraProperties,
+ null), overwrite);
+ }
+ return this;
+ }
+
+
+
+ /**
+ * Adds the provided pattern syntax definition to this schema builder.
+ *
+ * @param oid
+ * The OID of the pattern syntax definition.
+ * @param description
+ * The description of the pattern syntax definition.
+ * @param pattern
+ * The regular expression pattern which attribute values must
+ * match in order to be valid.
+ * @param overwrite
+ * {@code true} if any existing syntax with the same OID
+ * should be overwritten.
+ * @return A reference to this schema builder.
+ * @throws ConflictingSchemaElementException
+ * If {@code overwrite} was {@code false} and a conflicting
+ * schema element was found.
+ */
+ public SchemaBuilder addPatternSyntax(String oid, String description,
+ Pattern pattern, boolean overwrite)
+ throws ConflictingSchemaElementException
+ {
+ Validator.ensureNotNull(pattern);
+
+ addSyntax(new Syntax(oid, description, Collections.singletonMap(
+ "X-PATTERN", Collections.singletonList(pattern.toString())),
+ null, null), overwrite);
+ return this;
+ }
+
+
+
+ /**
+ * Adds all of the schema elements in the provided schema to this
+ * schema builder.
+ *
+ * @param schema
+ * The schema to be copied into this schema builder.
+ * @param overwrite
+ * {@code true} if existing schema elements with the same
+ * conflicting OIDs should be overwritten.
+ * @return A reference to this schema builder.
+ * @throws ConflictingSchemaElementException
+ * If {@code overwrite} was {@code false} and conflicting
+ * schema elements were found.
+ * @throws NullPointerException
+ * If {@code schema} was {@code null}.
+ */
+ public SchemaBuilder addSchema(Schema schema, boolean overwrite)
+ throws ConflictingSchemaElementException, NullPointerException
+ {
+ Validator.ensureNotNull(schema);
+ for (final Syntax syntax : schema.getSyntaxes())
+ {
+ addSyntax(syntax.duplicate(), overwrite);
+ }
+
+ for (final MatchingRule matchingRule : schema.getMatchingRules())
+ {
+ addMatchingRule(matchingRule.duplicate(), overwrite);
+ }
+
+ for (final MatchingRuleUse matchingRuleUse : schema
+ .getMatchingRuleUses())
+ {
+ addMatchingRuleUse(matchingRuleUse.duplicate(), overwrite);
+ }
+
+ for (final AttributeType attributeType : schema.getAttributeTypes())
+ {
+ addAttributeType(attributeType.duplicate(), overwrite);
+ }
+
+ for (final ObjectClass objectClass : schema.getObjectClasses())
+ {
+ addObjectClass(objectClass.duplicate(), overwrite);
+ }
+
+ for (final NameForm nameForm : schema.getNameForms())
+ {
+ addNameForm(nameForm.duplicate(), overwrite);
+ }
+
+ for (final DITContentRule contentRule : schema.getDITContentRules())
+ {
+ addDITContentRule(contentRule.duplicate(), overwrite);
+ }
+
+ for (final DITStructureRule structureRule : schema
+ .getDITStuctureRules())
+ {
+ addDITStructureRule(structureRule.duplicate(), overwrite);
+ }
+
+ return this;
+ }
+
+
+
+ /**
+ * Adds the provided substitution syntax definition to this schema
+ * builder.
+ *
+ * @param oid
+ * The OID of the substitution syntax definition.
+ * @param description
+ * The description of the substitution syntax definition.
+ * @param substituteSyntax
+ * The OID of the syntax whose implementation should be
+ * substituted.
+ * @param overwrite
+ * {@code true} if any existing syntax with the same OID
+ * should be overwritten.
+ * @return A reference to this schema builder.
+ * @throws ConflictingSchemaElementException
+ * If {@code overwrite} was {@code false} and a conflicting
+ * schema element was found.
+ */
+ public SchemaBuilder addSubstitutionSyntax(String oid,
+ String description, String substituteSyntax, boolean overwrite)
+ throws ConflictingSchemaElementException
+ {
+ Validator.ensureNotNull(substituteSyntax);
+
+ addSyntax(new Syntax(oid, description, Collections.singletonMap(
+ "X-SUBST", Collections.singletonList(substituteSyntax)), null,
+ null), overwrite);
+ return this;
+ }
+
+
+
+ /**
+ * Adds the provided syntax definition to this schema builder.
+ *
+ * @param definition
+ * The syntax definition.
+ * @param overwrite
+ * {@code true} if any existing syntax with the same OID
+ * should be overwritten.
+ * @return A reference to this schema builder.
+ * @throws LocalizedIllegalArgumentException
+ * If the provided syntax definition could not be parsed.
+ * @throws ConflictingSchemaElementException
+ * If {@code overwrite} was {@code false} and a conflicting
+ * schema element was found.
+ * @throws NullPointerException
+ * If {@code definition} was {@code null}.
+ */
+ public SchemaBuilder addSyntax(String definition, boolean overwrite)
+ throws LocalizedIllegalArgumentException,
+ ConflictingSchemaElementException
+ {
+ Validator.ensureNotNull(definition);
+ try
+ {
+ final SubstringReader reader = new SubstringReader(definition);
+
+ // We'll do this a character at a time. First, skip over any
+ // leading whitespace.
+ reader.skipWhitespaces();
+
+ if (reader.remaining() <= 0)
+ {
+ // This means that the value was empty or contained only
+ // whitespace. That is illegal.
+ final Message message =
+ ERR_ATTR_SYNTAX_ATTRSYNTAX_EMPTY_VALUE.get();
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // The next character must be an open parenthesis. If it is not,
+ // then that is an error.
+ final char c = reader.read();
+ if (c != '(')
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_ATTRSYNTAX_EXPECTED_OPEN_PARENTHESIS.get(
+ definition, (reader.pos() - 1), String.valueOf(c));
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ // Skip over any spaces immediately following the opening
+ // parenthesis.
+ reader.skipWhitespaces();
+
+ // The next set of characters must be the OID.
+ final String oid = SchemaUtils.readOID(reader);
+
+ String description = "".intern();
+ Map<String, List<String>> extraProperties =
+ Collections.emptyMap();
+
+ // At this point, we should have a pretty specific syntax that
+ // describes what may come next, but some of the components are
+ // optional and it would be pretty easy to put something in the
+ // wrong order, so we will be very flexible about what we can
+ // accept. Just look at the next token, figure out what it is and
+ // how to treat what comes after it, then repeat until we get to
+ // the end of the value. But before we start, set default values
+ // for everything else we might need to know.
+ while (true)
+ {
+ final String tokenName = SchemaUtils.readTokenName(reader);
+
+ if (tokenName == null)
+ {
+ // No more tokens.
+ break;
+ }
+ else if (tokenName.equalsIgnoreCase("desc"))
+ {
+ // This specifies the description for the syntax. It is an
+ // arbitrary string of characters enclosed in single quotes.
+ description = SchemaUtils.readQuotedString(reader);
+ }
+ else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+ {
+ // This must be a non-standard property and it must be
+ // followed by either a single definition in single quotes or
+ // an open parenthesis followed by one or more values in
+ // single quotes separated by spaces followed by a close
+ // parenthesis.
+ if (extraProperties.isEmpty())
+ {
+ extraProperties = new HashMap<String, List<String>>();
+ }
+ extraProperties.put(tokenName, SchemaUtils
+ .readExtensions(reader));
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+ }
+
+ // See if it is a enum syntax
+ for (final Map.Entry<String, List<String>> property : extraProperties
+ .entrySet())
+ {
+ if (property.getKey().equalsIgnoreCase("x-enum"))
+ {
+ final EnumSyntaxImpl enumImpl =
+ new EnumSyntaxImpl(oid, property.getValue());
+ final Syntax enumSyntax =
+ new Syntax(oid, description, extraProperties, definition,
+ enumImpl);
+ final MatchingRule enumOMR =
+ new MatchingRule(enumImpl.getOrderingMatchingRule(),
+ Collections
+ .singletonList(OMR_GENERIC_ENUM_NAME + oid), "",
+ false, oid, CoreSchemaImpl.OPENDS_ORIGIN, null,
+ new EnumOrderingMatchingRule(enumImpl));
+
+ addSyntax(enumSyntax, overwrite);
+ addMatchingRule(enumOMR, overwrite);
+ return this;
+ }
+ }
+
+ addSyntax(new Syntax(oid, description, extraProperties,
+ definition, null), overwrite);
+ }
+ catch (final DecodeException e)
+ {
+ throw new LocalizedIllegalArgumentException(e.getMessageObject(),
+ e.getCause());
+ }
+ return this;
+ }
+
+
+
+ /**
+ * Adds the provided syntax definition to this schema builder.
+ *
+ * @param oid
+ * The OID of the syntax definition.
+ * @param description
+ * The description of the syntax definition.
+ * @param extraProperties
+ * A map containing additional properties associated with the
+ * syntax definition.
+ * @param implementation
+ * The implementation of the syntax.
+ * @param overwrite
+ * {@code true} if any existing syntax with the same OID
+ * should be overwritten.
+ * @return A reference to this schema builder.
+ * @throws ConflictingSchemaElementException
+ * If {@code overwrite} was {@code false} and a conflicting
+ * schema element was found.
+ * @throws NullPointerException
+ * If {@code definition} was {@code null}.
+ */
+ public SchemaBuilder addSyntax(String oid, String description,
+ Map<String, List<String>> extraProperties,
+ SyntaxImpl implementation, boolean overwrite)
+ throws ConflictingSchemaElementException
+ {
+ addSyntax(new Syntax(oid, description, extraProperties, null,
+ implementation), overwrite);
+ return this;
+ }
+
+
+
+ /**
+ * Removes the named attribute type from this schema builder.
+ *
+ * @param name
+ * The name or OID of the attribute type to be removed.
+ * @return {@code true} if the attribute type was found.
+ */
+ public boolean removeAttributeType(String name)
+ {
+ if (schema.hasAttributeType(name))
+ {
+ removeAttributeType(schema.getAttributeType(name));
+ return true;
+ }
+ return false;
+ }
+
+
+
+ /**
+ * Removes the named DIT content rule from this schema builder.
+ *
+ * @param name
+ * The name or OID of the DIT content rule to be removed.
+ * @return {@code true} if the DIT content rule was found.
+ */
+ public boolean removeDITContentRule(String name)
+ {
+ if (schema.hasDITContentRule(name))
+ {
+ removeDITContentRule(schema.getDITContentRule(name));
+ return true;
+ }
+ return false;
+ }
+
+
+
+ /**
+ * Removes the specified DIT structure rule from this schema builder.
+ *
+ * @param ruleID
+ * The ID of the DIT structure rule to be removed.
+ * @return {@code true} if the DIT structure rule was found.
+ */
+ public boolean removeDITStructureRule(Integer ruleID)
+ {
+ if (schema.hasDITStructureRule(ruleID))
+ {
+ removeDITStructureRule(schema.getDITStructureRule(ruleID));
+ return true;
+ }
+ return false;
+ }
+
+
+
+ /**
+ * Removes the named matching rule from this schema builder.
+ *
+ * @param name
+ * The name or OID of the matching rule to be removed.
+ * @return {@code true} if the matching rule was found.
+ */
+ public boolean removeMatchingRule(String name)
+ {
+ if (schema.hasMatchingRule(name))
+ {
+ removeMatchingRule(schema.getMatchingRule(name));
+ return true;
+ }
+ return false;
+ }
+
+
+
+ /**
+ * Removes the named matching rule use from this schema builder.
+ *
+ * @param name
+ * The name or OID of the matching rule use to be removed.
+ * @return {@code true} if the matching rule use was found.
+ */
+ public boolean removeMatchingRuleUse(String name)
+ {
+ if (schema.hasMatchingRuleUse(name))
+ {
+ removeMatchingRuleUse(schema.getMatchingRuleUse(name));
+ return true;
+ }
+ return false;
+ }
+
+
+
+ /**
+ * Removes the named name form from this schema builder.
+ *
+ * @param name
+ * The name or OID of the name form to be removed.
+ * @return {@code true} if the name form was found.
+ */
+ public boolean removeNameForm(String name)
+ {
+ if (schema.hasNameForm(name))
+ {
+ removeNameForm(schema.getNameForm(name));
+ return true;
+ }
+ return false;
+ }
+
+
+
+ /**
+ * Removes the named object class from this schema builder.
+ *
+ * @param name
+ * The name or OID of the object class to be removed.
+ * @return {@code true} if the object class was found.
+ */
+ public boolean removeObjectClass(String name)
+ {
+ if (schema.hasObjectClass(name))
+ {
+ removeObjectClass(schema.getObjectClass(name));
+ return true;
+ }
+ return false;
+ }
+
+
+
+ /**
+ * Removes the named syntax from this schema builder.
+ *
+ * @param numericOID
+ * The name of the syntax to be removed.
+ * @return {@code true} if the syntax was found.
+ */
+ public boolean removeSyntax(String numericOID)
+ {
+ if (schema.hasSyntax(numericOID))
+ {
+ removeSyntax(schema.getSyntax(numericOID));
+ return true;
+ }
+ return false;
+ }
+
+
+
+ /**
+ * Sets the schema compatibility options for this schema builder. The
+ * schema builder maintains its own set of compatibility options, so
+ * subsequent changes to the provided set of options will not impact
+ * this schema builder.
+ *
+ * @param options
+ * The set of schema compatibility options that this schema
+ * builder should use.
+ * @return A reference to this schema builder.
+ * @throws NullPointerException
+ * If {@code options} was {@code null}.
+ */
+ public SchemaBuilder setSchemaCompatOptions(
+ SchemaCompatOptions options) throws NullPointerException
+ {
+ Validator.ensureNotNull(options);
+ this.options.assign(options);
+ return this;
+ }
+
+
+
+ /**
+ * Returns a {@code Schema} containing all of the schema elements
+ * contained in this schema builder as well as the same set of schema
+ * compatibility options.
+ * <p>
+ * Any errors that were detected while validating the schema will be
+ * ignored.
+ * <p>
+ * When this method returns this schema builder is empty and contains
+ * a default set of compatibility options.
+ *
+ * @return A {@code Schema} containing all of the schema elements
+ * contained in this schema builder as well as the same set of
+ * schema compatibility options
+ */
+ public Schema toSchema()
+ {
+ return toSchema(null);
+ }
+
+
+
+ /**
+ * Returns a {@code Schema} containing all of the schema elements
+ * contained in this schema builder as well as the same set of schema
+ * compatibility options.
+ * <p>
+ * When this method returns this schema builder is empty and contains
+ * a default set of compatibility options.
+ *
+ * @param errorMessages
+ * A list into which any errors that were detected while
+ * validating the schema will be placed, may be {@code null}
+ * in which case any errors will be ignored.
+ * @return A {@code Schema} containing all of the schema elements
+ * contained in this schema builder as well as the same set of
+ * schema compatibility options
+ */
+ public Schema toSchema(List<Message> errorMessages)
+ {
+ if (errorMessages == null)
+ {
+ errorMessages = new LinkedList<Message>();
+ }
+
+ validate(errorMessages);
+ final Schema builtSchema = schema;
+ initBuilder();
+ return builtSchema;
+ }
+
+
+
+ private synchronized void addAttributeType(AttributeType attribute,
+ boolean overwrite) throws ConflictingSchemaElementException
+ {
+ AttributeType conflictingAttribute;
+ if (numericOID2AttributeTypes.containsKey(attribute.getOID()))
+ {
+ conflictingAttribute =
+ numericOID2AttributeTypes.get(attribute.getOID());
+ if (!overwrite)
+ {
+ final Message message =
+ ERR_SCHEMA_CONFLICTING_ATTRIBUTE_OID.get(attribute
+ .getNameOrOID(), attribute.getOID(),
+ conflictingAttribute.getNameOrOID());
+ throw new ConflictingSchemaElementException(message);
+ }
+ removeAttributeType(conflictingAttribute);
+ }
+
+ numericOID2AttributeTypes.put(attribute.getOID(), attribute);
+ for (final String name : attribute.getNames())
+ {
+ final String lowerName = StaticUtils.toLowerCase(name);
+ List<AttributeType> attrs;
+ if ((attrs = name2AttributeTypes.get(lowerName)) == null)
+ {
+ name2AttributeTypes.put(lowerName, Collections
+ .singletonList(attribute));
+ }
+ else if (attrs.size() == 1)
+ {
+ attrs = new ArrayList<AttributeType>(attrs);
+ attrs.add(attribute);
+ name2AttributeTypes.put(lowerName, attrs);
+ }
+ else
+ {
+ attrs.add(attribute);
+ }
+ }
+ }
+
+
+
+ private synchronized void addDITContentRule(DITContentRule rule,
+ boolean overwrite) throws ConflictingSchemaElementException
+ {
+ DITContentRule conflictingRule;
+ if (numericOID2ContentRules.containsKey(rule
+ .getStructuralClassOID()))
+ {
+ conflictingRule =
+ numericOID2ContentRules.get(rule.getStructuralClassOID());
+ if (!overwrite)
+ {
+ final Message message =
+ ERR_SCHEMA_CONFLICTING_DIT_CONTENT_RULE.get(rule
+ .getNameOrOID(), rule.getStructuralClassOID(),
+ conflictingRule.getNameOrOID());
+ throw new ConflictingSchemaElementException(message);
+ }
+ removeDITContentRule(conflictingRule);
+ }
+
+ numericOID2ContentRules.put(rule.getStructuralClassOID(), rule);
+ for (final String name : rule.getNames())
+ {
+ final String lowerName = StaticUtils.toLowerCase(name);
+ List<DITContentRule> rules;
+ if ((rules = name2ContentRules.get(lowerName)) == null)
+ {
+ name2ContentRules.put(lowerName, Collections
+ .singletonList(rule));
+ }
+ else if (rules.size() == 1)
+ {
+ rules = new ArrayList<DITContentRule>(rules);
+ rules.add(rule);
+ name2ContentRules.put(lowerName, rules);
+ }
+ else
+ {
+ rules.add(rule);
+ }
+ }
+ }
+
+
+
+ private synchronized void addDITStructureRule(DITStructureRule rule,
+ boolean overwrite) throws ConflictingSchemaElementException
+ {
+ DITStructureRule conflictingRule;
+ if (id2StructureRules.containsKey(rule.getRuleID()))
+ {
+ conflictingRule = id2StructureRules.get(rule.getRuleID());
+ if (!overwrite)
+ {
+ final Message message =
+ ERR_SCHEMA_CONFLICTING_DIT_STRUCTURE_RULE_ID.get(rule
+ .getNameOrRuleID(), rule.getRuleID(), conflictingRule
+ .getNameOrRuleID());
+ throw new ConflictingSchemaElementException(message);
+ }
+ removeDITStructureRule(conflictingRule);
+ }
+
+ id2StructureRules.put(rule.getRuleID(), rule);
+ for (final String name : rule.getNames())
+ {
+ final String lowerName = StaticUtils.toLowerCase(name);
+ List<DITStructureRule> rules;
+ if ((rules = name2StructureRules.get(lowerName)) == null)
+ {
+ name2StructureRules.put(lowerName, Collections
+ .singletonList(rule));
+ }
+ else if (rules.size() == 1)
+ {
+ rules = new ArrayList<DITStructureRule>(rules);
+ rules.add(rule);
+ name2StructureRules.put(lowerName, rules);
+ }
+ else
+ {
+ rules.add(rule);
+ }
+ }
+ }
+
+
+
+ private synchronized void addMatchingRule(MatchingRule rule,
+ boolean overwrite) throws ConflictingSchemaElementException
+ {
+ MatchingRule conflictingRule;
+ if (numericOID2MatchingRules.containsKey(rule.getOID()))
+ {
+ conflictingRule = numericOID2MatchingRules.get(rule.getOID());
+ if (!overwrite)
+ {
+ final Message message =
+ ERR_SCHEMA_CONFLICTING_MR_OID.get(rule.getNameOrOID(), rule
+ .getOID(), conflictingRule.getNameOrOID());
+ throw new ConflictingSchemaElementException(message);
+ }
+ removeMatchingRule(conflictingRule);
+ }
+
+ numericOID2MatchingRules.put(rule.getOID(), rule);
+ for (final String name : rule.getNames())
+ {
+ final String lowerName = StaticUtils.toLowerCase(name);
+ List<MatchingRule> rules;
+ if ((rules = name2MatchingRules.get(lowerName)) == null)
+ {
+ name2MatchingRules.put(lowerName, Collections
+ .singletonList(rule));
+ }
+ else if (rules.size() == 1)
+ {
+ rules = new ArrayList<MatchingRule>(rules);
+ rules.add(rule);
+ name2MatchingRules.put(lowerName, rules);
+ }
+ else
+ {
+ rules.add(rule);
+ }
+ }
+ }
+
+
+
+ private synchronized void addMatchingRuleUse(MatchingRuleUse use,
+ boolean overwrite) throws ConflictingSchemaElementException
+ {
+ MatchingRuleUse conflictingUse;
+ if (numericOID2MatchingRuleUses.containsKey(use
+ .getMatchingRuleOID()))
+ {
+ conflictingUse =
+ numericOID2MatchingRuleUses.get(use.getMatchingRuleOID());
+ if (!overwrite)
+ {
+ final Message message =
+ ERR_SCHEMA_CONFLICTING_MATCHING_RULE_USE.get(use
+ .getNameOrOID(), use.getMatchingRuleOID(),
+ conflictingUse.getNameOrOID());
+ throw new ConflictingSchemaElementException(message);
+ }
+ removeMatchingRuleUse(conflictingUse);
+ }
+
+ numericOID2MatchingRuleUses.put(use.getMatchingRuleOID(), use);
+ for (final String name : use.getNames())
+ {
+ final String lowerName = StaticUtils.toLowerCase(name);
+ List<MatchingRuleUse> uses;
+ if ((uses = name2MatchingRuleUses.get(lowerName)) == null)
+ {
+ name2MatchingRuleUses.put(lowerName, Collections
+ .singletonList(use));
+ }
+ else if (uses.size() == 1)
+ {
+ uses = new ArrayList<MatchingRuleUse>(uses);
+ uses.add(use);
+ name2MatchingRuleUses.put(lowerName, uses);
+ }
+ else
+ {
+ uses.add(use);
+ }
+ }
+ }
+
+
+
+ private synchronized void addNameForm(NameForm form, boolean overwrite)
+ throws ConflictingSchemaElementException
+ {
+ NameForm conflictingForm;
+ if (numericOID2NameForms.containsKey(form.getOID()))
+ {
+ conflictingForm = numericOID2NameForms.get(form.getOID());
+ if (!overwrite)
+ {
+ final Message message =
+ ERR_SCHEMA_CONFLICTING_NAME_FORM_OID.get(form
+ .getNameOrOID(), form.getOID(), conflictingForm
+ .getNameOrOID());
+ throw new ConflictingSchemaElementException(message);
+ }
+ removeNameForm(conflictingForm);
+ }
+
+ numericOID2NameForms.put(form.getOID(), form);
+ for (final String name : form.getNames())
+ {
+ final String lowerName = StaticUtils.toLowerCase(name);
+ List<NameForm> forms;
+ if ((forms = name2NameForms.get(lowerName)) == null)
+ {
+ name2NameForms.put(lowerName, Collections.singletonList(form));
+ }
+ else if (forms.size() == 1)
+ {
+ forms = new ArrayList<NameForm>(forms);
+ forms.add(form);
+ name2NameForms.put(lowerName, forms);
+ }
+ else
+ {
+ forms.add(form);
+ }
+ }
+ }
+
+
+
+ private synchronized void addObjectClass(ObjectClass oc,
+ boolean overwrite) throws ConflictingSchemaElementException
+ {
+ ObjectClass conflictingOC;
+ if (numericOID2ObjectClasses.containsKey(oc.getOID()))
+ {
+ conflictingOC = numericOID2ObjectClasses.get(oc.getOID());
+ if (!overwrite)
+ {
+ final Message message =
+ ERR_SCHEMA_CONFLICTING_OBJECTCLASS_OID.get(oc
+ .getNameOrOID(), oc.getOID(), conflictingOC
+ .getNameOrOID());
+ throw new ConflictingSchemaElementException(message);
+ }
+ removeObjectClass(conflictingOC);
+ }
+
+ numericOID2ObjectClasses.put(oc.getOID(), oc);
+ for (final String name : oc.getNames())
+ {
+ final String lowerName = StaticUtils.toLowerCase(name);
+ List<ObjectClass> classes;
+ if ((classes = name2ObjectClasses.get(lowerName)) == null)
+ {
+ name2ObjectClasses
+ .put(lowerName, Collections.singletonList(oc));
+ }
+ else if (classes.size() == 1)
+ {
+ classes = new ArrayList<ObjectClass>(classes);
+ classes.add(oc);
+ name2ObjectClasses.put(lowerName, classes);
+ }
+ else
+ {
+ classes.add(oc);
+ }
+ }
+ }
+
+
+
+ private synchronized void addSyntax(Syntax syntax, boolean overwrite)
+ throws ConflictingSchemaElementException
+ {
+ Syntax conflictingSyntax;
+ if (numericOID2Syntaxes.containsKey(syntax.getOID()))
+ {
+ conflictingSyntax = numericOID2Syntaxes.get(syntax.getOID());
+ if (!overwrite)
+ {
+ final Message message =
+ ERR_SCHEMA_CONFLICTING_SYNTAX_OID.get(syntax.toString(),
+ syntax.getOID(), conflictingSyntax.getOID());
+ throw new ConflictingSchemaElementException(message);
+ }
+ removeSyntax(conflictingSyntax);
+ }
+ numericOID2Syntaxes.put(syntax.getOID(), syntax);
+ }
+
+
+
+ private void initBuilder()
+ {
+ numericOID2Syntaxes = new HashMap<String, Syntax>();
+ numericOID2MatchingRules = new HashMap<String, MatchingRule>();
+ numericOID2MatchingRuleUses =
+ new HashMap<String, MatchingRuleUse>();
+ numericOID2AttributeTypes = new HashMap<String, AttributeType>();
+ numericOID2ObjectClasses = new HashMap<String, ObjectClass>();
+ numericOID2NameForms = new HashMap<String, NameForm>();
+ numericOID2ContentRules = new HashMap<String, DITContentRule>();
+ id2StructureRules = new HashMap<Integer, DITStructureRule>();
+
+ name2MatchingRules = new HashMap<String, List<MatchingRule>>();
+ name2MatchingRuleUses =
+ new HashMap<String, List<MatchingRuleUse>>();
+ name2AttributeTypes = new HashMap<String, List<AttributeType>>();
+ name2ObjectClasses = new HashMap<String, List<ObjectClass>>();
+ name2NameForms = new HashMap<String, List<NameForm>>();
+ name2ContentRules = new HashMap<String, List<DITContentRule>>();
+ name2StructureRules = new HashMap<String, List<DITStructureRule>>();
+
+ objectClass2NameForms = new HashMap<String, List<NameForm>>();
+ nameForm2StructureRules =
+ new HashMap<String, List<DITStructureRule>>();
+ options = SchemaCompatOptions.defaultOptions();
+ schema =
+ new Schema(numericOID2Syntaxes, numericOID2MatchingRules,
+ numericOID2MatchingRuleUses, numericOID2AttributeTypes,
+ numericOID2ObjectClasses, numericOID2NameForms,
+ numericOID2ContentRules, id2StructureRules,
+ name2MatchingRules, name2MatchingRuleUses,
+ name2AttributeTypes, name2ObjectClasses, name2NameForms,
+ name2ContentRules, name2StructureRules,
+ objectClass2NameForms, nameForm2StructureRules, options);
+ }
+
+
+
+ private synchronized void removeAttributeType(
+ AttributeType attributeType)
+ {
+ numericOID2AttributeTypes.remove(attributeType.getOID());
+ for (final String name : attributeType.getNames())
+ {
+ final String lowerName = StaticUtils.toLowerCase(name);
+ final List<AttributeType> attributes =
+ name2AttributeTypes.get(lowerName);
+ if (attributes != null && attributes.contains(attributeType))
+ {
+ if (attributes.size() <= 1)
+ {
+ name2AttributeTypes.remove(lowerName);
+ }
+ else
+ {
+ attributes.remove(attributeType);
+ }
+ }
+ }
+ }
+
+
+
+ private synchronized void removeDITContentRule(DITContentRule rule)
+ {
+ numericOID2ContentRules.remove(rule.getStructuralClassOID());
+ for (final String name : rule.getNames())
+ {
+ final String lowerName = StaticUtils.toLowerCase(name);
+ final List<DITContentRule> rules =
+ name2ContentRules.get(lowerName);
+ if (rules != null && rules.contains(rule))
+ {
+ if (rules.size() <= 1)
+ {
+ name2AttributeTypes.remove(lowerName);
+ }
+ else
+ {
+ rules.remove(rule);
+ }
+ }
+ }
+ }
+
+
+
+ private synchronized void removeDITStructureRule(DITStructureRule rule)
+ {
+ id2StructureRules.remove(rule.getRuleID());
+ for (final String name : rule.getNames())
+ {
+ final String lowerName = StaticUtils.toLowerCase(name);
+ final List<DITStructureRule> rules =
+ name2StructureRules.get(lowerName);
+ if (rules != null && rules.contains(rule))
+ {
+ if (rules.size() <= 1)
+ {
+ name2StructureRules.remove(lowerName);
+ }
+ else
+ {
+ rules.remove(rule);
+ }
+ }
+ }
+ }
+
+
+
+ private synchronized void removeMatchingRule(MatchingRule rule)
+ {
+ numericOID2MatchingRules.remove(rule.getOID());
+ for (final String name : rule.getNames())
+ {
+ final String lowerName = StaticUtils.toLowerCase(name);
+ final List<MatchingRule> rules =
+ name2MatchingRules.get(lowerName);
+ if (rules != null && rules.contains(rule))
+ {
+ if (rules.size() <= 1)
+ {
+ name2MatchingRules.remove(lowerName);
+ }
+ else
+ {
+ rules.remove(rule);
+ }
+ }
+ }
+ }
+
+
+
+ private synchronized void removeMatchingRuleUse(MatchingRuleUse use)
+ {
+ numericOID2MatchingRuleUses.remove(use.getMatchingRuleOID());
+ for (final String name : use.getNames())
+ {
+ final String lowerName = StaticUtils.toLowerCase(name);
+ final List<MatchingRuleUse> uses =
+ name2MatchingRuleUses.get(lowerName);
+ if (uses != null && uses.contains(use))
+ {
+ if (uses.size() <= 1)
+ {
+ name2MatchingRuleUses.remove(lowerName);
+ }
+ else
+ {
+ uses.remove(use);
+ }
+ }
+ }
+ }
+
+
+
+ private synchronized void removeNameForm(NameForm form)
+ {
+ numericOID2NameForms.remove(form.getOID());
+ name2NameForms.remove(form.getOID());
+ for (final String name : form.getNames())
+ {
+ final String lowerName = StaticUtils.toLowerCase(name);
+ final List<NameForm> forms = name2NameForms.get(lowerName);
+ if (forms != null && forms.contains(form))
+ {
+ if (forms.size() <= 1)
+ {
+ name2NameForms.remove(lowerName);
+ }
+ else
+ {
+ forms.remove(form);
+ }
+ }
+ }
+ }
+
+
+
+ private synchronized void removeObjectClass(ObjectClass oc)
+ {
+ numericOID2ObjectClasses.remove(oc.getOID());
+ name2ObjectClasses.remove(oc.getOID());
+ for (final String name : oc.getNames())
+ {
+ final String lowerName = StaticUtils.toLowerCase(name);
+ final List<ObjectClass> classes =
+ name2ObjectClasses.get(lowerName);
+ if (classes != null && classes.contains(oc))
+ {
+ if (classes.size() <= 1)
+ {
+ name2ObjectClasses.remove(lowerName);
+ }
+ else
+ {
+ classes.remove(oc);
+ }
+ }
+ }
+ }
+
+
+
+ private synchronized void removeSyntax(Syntax syntax)
+ {
+ numericOID2Syntaxes.remove(syntax.getOID());
+ }
+
+
+
+ private synchronized void validate(List<Message> warnings)
+ {
+ // Verify all references in all elements
+ for (final Syntax syntax : numericOID2Syntaxes.values().toArray(
+ new Syntax[numericOID2Syntaxes.values().size()]))
+ {
+ try
+ {
+ syntax.validate(warnings, schema);
+ }
+ catch (final SchemaException e)
+ {
+ removeSyntax(syntax);
+ warnings.add(ERR_SYNTAX_VALIDATION_FAIL.get(syntax.toString(),
+ e.toString()));
+ }
+ }
+
+ for (final MatchingRule rule : numericOID2MatchingRules.values()
+ .toArray(
+ new MatchingRule[numericOID2MatchingRules.values().size()]))
+ {
+ try
+ {
+ rule.validate(warnings, schema);
+ }
+ catch (final SchemaException e)
+ {
+ removeMatchingRule(rule);
+ warnings.add(ERR_MR_VALIDATION_FAIL.get(rule.toString(), e
+ .toString()));
+ }
+ }
+
+ for (final AttributeType attribute : numericOID2AttributeTypes
+ .values()
+ .toArray(
+ new AttributeType[numericOID2AttributeTypes.values().size()]))
+ {
+ try
+ {
+ attribute.validate(warnings, schema);
+ }
+ catch (final SchemaException e)
+ {
+ removeAttributeType(attribute);
+ warnings.add(ERR_ATTR_TYPE_VALIDATION_FAIL.get(attribute
+ .toString(), e.toString()));
+ }
+ }
+
+ for (final ObjectClass oc : numericOID2ObjectClasses.values()
+ .toArray(
+ new ObjectClass[numericOID2ObjectClasses.values().size()]))
+ {
+ try
+ {
+ oc.validate(warnings, schema);
+ }
+ catch (final SchemaException e)
+ {
+ removeObjectClass(oc);
+ warnings.add(ERR_OC_VALIDATION_FAIL.get(oc.toString(), e
+ .toString()));
+ }
+ }
+
+ for (final MatchingRuleUse use : numericOID2MatchingRuleUses
+ .values().toArray(
+ new MatchingRuleUse[numericOID2MatchingRuleUses.values()
+ .size()]))
+ {
+ try
+ {
+ use.validate(warnings, schema);
+ }
+ catch (final SchemaException e)
+ {
+ removeMatchingRuleUse(use);
+ warnings.add(ERR_MRU_VALIDATION_FAIL.get(use.toString(), e
+ .toString()));
+ }
+ }
+
+ for (final NameForm form : numericOID2NameForms.values().toArray(
+ new NameForm[numericOID2NameForms.values().size()]))
+ {
+ try
+ {
+ form.validate(warnings, schema);
+
+ // build the objectClass2NameForms map
+ List<NameForm> forms;
+ final String ocOID = form.getStructuralClass().getOID();
+ if ((forms = objectClass2NameForms.get(ocOID)) == null)
+ {
+ objectClass2NameForms.put(ocOID, Collections
+ .singletonList(form));
+ }
+ else if (forms.size() == 1)
+ {
+ forms = new ArrayList<NameForm>(forms);
+ forms.add(form);
+ objectClass2NameForms.put(ocOID, forms);
+ }
+ else
+ {
+ forms.add(form);
+ }
+ }
+ catch (final SchemaException e)
+ {
+ removeNameForm(form);
+ warnings.add(ERR_NAMEFORM_VALIDATION_FAIL.get(form.toString(),
+ e.toString()));
+ }
+ }
+
+ for (final DITContentRule rule : numericOID2ContentRules
+ .values()
+ .toArray(
+ new DITContentRule[numericOID2ContentRules.values().size()]))
+ {
+ try
+ {
+ rule.validate(warnings, schema);
+ }
+ catch (final SchemaException e)
+ {
+ removeDITContentRule(rule);
+ warnings.add(ERR_DCR_VALIDATION_FAIL.get(rule.toString(), e
+ .toString()));
+ }
+ }
+
+ for (final DITStructureRule rule : id2StructureRules.values()
+ .toArray(
+ new DITStructureRule[id2StructureRules.values().size()]))
+ {
+ try
+ {
+ rule.validate(warnings, schema);
+
+ // build the nameForm2StructureRules map
+ List<DITStructureRule> rules;
+ final String ocOID = rule.getNameForm().getOID();
+ if ((rules = nameForm2StructureRules.get(ocOID)) == null)
+ {
+ nameForm2StructureRules.put(ocOID, Collections
+ .singletonList(rule));
+ }
+ else if (rules.size() == 1)
+ {
+ rules = new ArrayList<DITStructureRule>(rules);
+ rules.add(rule);
+ nameForm2StructureRules.put(ocOID, rules);
+ }
+ else
+ {
+ rules.add(rule);
+ }
+ }
+ catch (final SchemaException e)
+ {
+ removeDITStructureRule(rule);
+ warnings.add(ERR_DSR_VALIDATION_FAIL.get(rule.toString(), e
+ .toString()));
+ }
+ }
+
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/SchemaCompatOptions.java b/sdk/src/org/opends/sdk/schema/SchemaCompatOptions.java
new file mode 100644
index 0000000..e4bfc0e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/SchemaCompatOptions.java
@@ -0,0 +1,164 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+/**
+ * This class provides various schema compatibility options which may be
+ * used to facilitate interoperability with legacy LDAP applications.
+ */
+public final class SchemaCompatOptions
+{
+ /**
+ * Creates a copy of the provided schema compatibility options.
+ *
+ * @param options
+ * The options to be copied.
+ * @return The copy of the provided schema compatibility options.
+ */
+ public static SchemaCompatOptions copyOf(SchemaCompatOptions options)
+ {
+ return defaultOptions().assign(options);
+ }
+
+
+
+ /**
+ * Creates a new set of schema compatibility options with default
+ * settings.
+ *
+ * @return The new schema compatibility options.
+ */
+ public static SchemaCompatOptions defaultOptions()
+ {
+ return new SchemaCompatOptions();
+ }
+
+ private boolean isTelephoneNumberSyntaxStrict = false;
+
+ private boolean isZeroLengthDirectoryStringsAllowed = false;
+
+
+
+ // Prevent direct instantiation.
+ private SchemaCompatOptions()
+ {
+ // Nothing to do.
+ }
+
+
+
+ /**
+ * Indicates whether or not the Telephone Number syntax should ensure
+ * that all values conform to the E.123 international telephone number
+ * format. By default this compatibility option is set to {@code
+ * false}.
+ *
+ * @return {@code true} if the Telephone Number syntax should ensure
+ * that all values conform to the E.123 international
+ * telephone number format, or {@code false} if not.
+ */
+ public boolean isTelephoneNumberSyntaxStrict()
+ {
+ return isTelephoneNumberSyntaxStrict;
+ }
+
+
+
+ /**
+ * Indicates whether or not zero-length values will be allowed by the
+ * Directory String syntax. This is technically forbidden by the LDAP
+ * specification, but it was allowed in earlier versions of the
+ * server, and the discussion of the directory string syntax in RFC
+ * 2252 does not explicitly state that they are not allowed. By
+ * default this compatibility option is set to {@code false}.
+ *
+ * @return {@code true} if zero-length values will be allowed by the
+ * Directory String syntax, or {@code false} if not.
+ */
+ public boolean isZeroLengthDirectoryStringsAllowed()
+ {
+ return isZeroLengthDirectoryStringsAllowed;
+ }
+
+
+
+ /**
+ * Indicates whether or not the Telephone Number syntax should ensure
+ * that all values conform to the E.123 international telephone number
+ * format. By default this compatibility option is set to {@code
+ * false}.
+ *
+ * @param isStrict
+ * {@code true} if the Telephone Number syntax should ensure
+ * that all values conform to the E.123 international
+ * telephone number format, or {@code false} if not.
+ * @return A reference to this {@code SchemaCompat}.
+ */
+ public SchemaCompatOptions setTelephoneNumberSyntaxStrict(
+ boolean isStrict)
+ {
+ this.isTelephoneNumberSyntaxStrict = isStrict;
+ return this;
+ }
+
+
+
+ /**
+ * Specifies whether or not zero-length values will be allowed by the
+ * Directory String syntax. This is technically forbidden by the LDAP
+ * specification, but it was allowed in earlier versions of the
+ * server, and the discussion of the directory string syntax in RFC
+ * 2252 does not explicitly state that they are not allowed. By
+ * default this compatibility option is set to {@code false}.
+ *
+ * @param isAllowed
+ * {@code true} if zero-length values will be allowed by the
+ * Directory String syntax, or {@code false} if not.
+ * @return A reference to this {@code SchemaCompat}.
+ */
+ public SchemaCompatOptions setZeroLengthDirectoryStringsAllowed(
+ boolean isAllowed)
+ {
+ this.isZeroLengthDirectoryStringsAllowed = isAllowed;
+ return this;
+ }
+
+
+
+ // Assigns the provided options to this set of options.
+ SchemaCompatOptions assign(SchemaCompatOptions options)
+ {
+ return setTelephoneNumberSyntaxStrict(
+ options.isTelephoneNumberSyntaxStrict)
+ .setZeroLengthDirectoryStringsAllowed(
+ options.isZeroLengthDirectoryStringsAllowed);
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/schema/SchemaConstants.java b/sdk/src/org/opends/sdk/schema/SchemaConstants.java
new file mode 100644
index 0000000..e97549b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/SchemaConstants.java
@@ -0,0 +1,1623 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines a number of constants used by Directory Server
+ * schema elements, like matching rules, syntaxes, attribute types, and
+ * objectclasses.
+ */
+class SchemaConstants
+{
+ /**
+ * The IANA-assigned base OID for all things under the OpenDS
+ * umbrella.
+ */
+ public static final String OID_OPENDS_BASE = "1.3.6.1.4.1.26027";
+
+ /**
+ * The base OID that will be used for the OpenDS Directory Server
+ * project.
+ */
+ public static final String OID_OPENDS_SERVER_BASE =
+ OID_OPENDS_BASE + ".1";
+
+ /**
+ * The base OID that will be used for OpenDS Directory Server
+ * attribute type definitions.
+ */
+ public static final String OID_OPENDS_SERVER_ATTRIBUTE_TYPE_BASE =
+ OID_OPENDS_SERVER_BASE + ".1";
+
+ /**
+ * The base OID that will be used for OpenDS Directory Server object
+ * class definitions.
+ */
+ public static final String OID_OPENDS_SERVER_OBJECT_CLASS_BASE =
+ OID_OPENDS_SERVER_BASE + ".2";
+
+ /**
+ * The base OID that will be used for OpenDS Directory Server
+ * attribute syntax definitions.
+ */
+ public static final String OID_OPENDS_SERVER_ATTRIBUTE_SYNTAX_BASE =
+ OID_OPENDS_SERVER_BASE + ".3";
+
+ /**
+ * The base OID that will be used for OpenDS Directory Server matching
+ * rule definitions.
+ */
+ public static final String OID_OPENDS_SERVER_MATCHING_RULE_BASE =
+ OID_OPENDS_SERVER_BASE + ".4";
+
+ /**
+ * The base OID that will be used for OpenDS Directory Server control
+ * definitions.
+ */
+ public static final String OID_OPENDS_SERVER_CONTROL_BASE =
+ OID_OPENDS_SERVER_BASE + ".5";
+
+ /**
+ * The base OID that will be used for OpenDS Directory Server extended
+ * operation definitions.
+ */
+ public static final String OID_OPENDS_SERVER_EXTENDED_OPERATION_BASE =
+ OID_OPENDS_SERVER_BASE + ".6";
+
+ /**
+ * The base OID that will be used for general-purpose (i.e., "other")
+ * types of OIDs that need to be allocated for the OpenDS Directory
+ * Server.
+ */
+ public static final String OID_OPENDS_SERVER_GENERAL_USE_BASE =
+ OID_OPENDS_SERVER_BASE + ".9";
+
+ /**
+ * The base OID that will be used for temporary or experimental OIDs
+ * within the OpenDS Directory Server.
+ */
+ public static final String OID_OPENDS_SERVER_EXPERIMENTAL_BASE =
+ OID_OPENDS_SERVER_BASE + ".999";
+
+ /**
+ * The description for the doubleMetaphoneApproximateMatch approximate
+ * matching rule.
+ */
+ public static final String AMR_DOUBLE_METAPHONE_DESCRIPTION =
+ "Double Metaphone Approximate Match";
+
+ /**
+ * The name for the doubleMetaphoneApproximateMatch approximate
+ * matching rule.
+ */
+ public static final String AMR_DOUBLE_METAPHONE_NAME =
+ "ds-mr-double-metaphone-approx";
+
+ /**
+ * The OID for the doubleMetaphoneApproximateMatch approximate
+ * matching rule.
+ */
+ public static final String AMR_DOUBLE_METAPHONE_OID =
+ OID_OPENDS_SERVER_MATCHING_RULE_BASE + ".1";
+
+ /**
+ * The description for the authPasswordExactMatch matching rule.
+ */
+ public static final String EMR_AUTH_PASSWORD_EXACT_DESCRIPTION =
+ "authentication password exact matching rule";
+
+ /**
+ * The name for the authPasswordExactMatch equality matching rule.
+ */
+ public static final String EMR_AUTH_PASSWORD_EXACT_NAME =
+ "authPasswordExactMatch";
+
+ /**
+ * The OID for the authPasswordExactMatch equality matching rule.
+ */
+ public static final String EMR_AUTH_PASSWORD_EXACT_OID =
+ "1.3.6.1.4.1.4203.1.2.2";
+
+ /**
+ * The description for the authPasswordMatch matching rule.
+ */
+ public static final String EMR_AUTH_PASSWORD_DESCRIPTION =
+ "authentication password matching rule";
+
+ /**
+ * The name for the authPasswordMatch equality matching rule.
+ */
+ public static final String EMR_AUTH_PASSWORD_NAME =
+ "authPasswordMatch";
+
+ /**
+ * The OID for the authPasswordMatch equality matching rule.
+ */
+ public static final String EMR_AUTH_PASSWORD_OID =
+ "1.3.6.1.4.1.4203.1.2.3";
+
+ /**
+ * The name for the bitStringMatch equality matching rule.
+ */
+ public static final String EMR_BIT_STRING_NAME = "bitStringMatch";
+
+ /**
+ * The OID for the bitStringMatch equality matching rule.
+ */
+ public static final String EMR_BIT_STRING_OID = "2.5.13.16";
+
+ /**
+ * The name for the booleanMatch equality matching rule.
+ */
+ public static final String EMR_BOOLEAN_NAME = "booleanMatch";
+
+ /**
+ * The OID for the booleanMatch equality matching rule.
+ */
+ public static final String EMR_BOOLEAN_OID = "2.5.13.13";
+
+ /**
+ * The name for the caseExactMatch equality matching rule.
+ */
+ public static final String EMR_CASE_EXACT_NAME = "caseExactMatch";
+
+ /**
+ * The OID for the caseExactMatch equality matching rule.
+ */
+ public static final String EMR_CASE_EXACT_OID = "2.5.13.5";
+
+ /**
+ * The name for the caseExactIA5Match equality matching rule.
+ */
+ public static final String EMR_CASE_EXACT_IA5_NAME =
+ "caseExactIA5Match";
+
+ /**
+ * The OID for the caseExactIA5Match equality matching rule.
+ */
+ public static final String EMR_CASE_EXACT_IA5_OID =
+ "1.3.6.1.4.1.1466.109.114.1";
+
+ /**
+ * The name for the caseIgnoreMatch equality matching rule.
+ */
+ public static final String EMR_CASE_IGNORE_NAME = "caseIgnoreMatch";
+
+ /**
+ * The OID for the caseIgnoreMatch equality matching rule.
+ */
+ public static final String EMR_CASE_IGNORE_OID = "2.5.13.2";
+
+ /**
+ * The name for the caseIgnoreIA5Match equality matching rule.
+ */
+ public static final String EMR_CASE_IGNORE_IA5_NAME =
+ "caseIgnoreIA5Match";
+
+ /**
+ * The OID for the caseIgnoreIA5Match equality matching rule.
+ */
+ public static final String EMR_CASE_IGNORE_IA5_OID =
+ "1.3.6.1.4.1.1466.109.114.2";
+
+ /**
+ * The name for the caseIgnoreListMatch equality matching rule.
+ */
+ public static final String EMR_CASE_IGNORE_LIST_NAME =
+ "caseIgnoreListMatch";
+
+ /**
+ * The OID for the caseIgnoreListMatch equality matching rule.
+ */
+ public static final String EMR_CASE_IGNORE_LIST_OID = "2.5.13.11";
+
+ /**
+ * The name for the directoryStringFirstComponentMatch equality
+ * matching rule.
+ */
+ public static final String EMR_DIRECTORY_STRING_FIRST_COMPONENT_NAME =
+ "directoryStringFirstComponentMatch";
+
+ /**
+ * The OID for the directoryStringFirstComponentMatch equality
+ * matching rule.
+ */
+ public static final String EMR_DIRECTORY_STRING_FIRST_COMPONENT_OID =
+ "2.5.13.31";
+
+ /**
+ * The name for the distinguishedNameMatch equality matching rule.
+ */
+ public static final String EMR_DN_NAME = "distinguishedNameMatch";
+
+ /**
+ * The OID for the distinguishedNameMatch equality matching rule.
+ */
+ public static final String EMR_DN_OID = "2.5.13.1";
+
+ /**
+ * The name for the generalizedTimeMatch equality matching rule.
+ */
+ public static final String EMR_GENERALIZED_TIME_NAME =
+ "generalizedTimeMatch";
+
+ /**
+ * The OID for the generalizedTimeMatch equality matching rule.
+ */
+ public static final String EMR_GENERALIZED_TIME_OID = "2.5.13.27";
+
+ /**
+ * The name for the integerMatch equality matching rule.
+ */
+ public static final String EMR_INTEGER_NAME = "integerMatch";
+
+ /**
+ * The OID for the integerMatch equality matching rule.
+ */
+ public static final String EMR_INTEGER_OID = "2.5.13.14";
+
+ /**
+ * The name for the integerFirstComponentMatch equality matching rule.
+ */
+ public static final String EMR_INTEGER_FIRST_COMPONENT_NAME =
+ "integerFirstComponentMatch";
+
+ /**
+ * The OID for the integerFirstComponentMatch equality matching rule.
+ */
+ public static final String EMR_INTEGER_FIRST_COMPONENT_OID =
+ "2.5.13.29";
+
+ /**
+ * The name for the keywordMatch equality matching rule.
+ */
+ public static final String EMR_KEYWORD_NAME = "keywordMatch";
+
+ /**
+ * The OID for the keywordMatch equality matching rule.
+ */
+ public static final String EMR_KEYWORD_OID = "2.5.13.33";
+
+ /**
+ * The name for the numericStringMatch equality matching rule.
+ */
+ public static final String EMR_NUMERIC_STRING_NAME =
+ "numericStringMatch";
+
+ /**
+ * The OID for the numericStringMatch equality matching rule.
+ */
+ public static final String EMR_NUMERIC_STRING_OID = "2.5.13.8";
+
+ /**
+ * The name for the octetStringMatch equality matching rule.
+ */
+ public static final String EMR_OCTET_STRING_NAME = "octetStringMatch";
+
+ /**
+ * The OID for the octetStringMatch equality matching rule.
+ */
+ public static final String EMR_OCTET_STRING_OID = "2.5.13.17";
+
+ /**
+ * The name for the objectIdentifierMatch equality matching rule.
+ */
+ public static final String EMR_OID_NAME = "objectIdentifierMatch";
+
+ /**
+ * The OID for the objectIdentifierMatch equality matching rule.
+ */
+ public static final String EMR_OID_OID = "2.5.13.0";
+
+ /**
+ * The name for the objectIdentifierFirstComponentMatch equality
+ * matching rule.
+ */
+ public static final String EMR_OID_FIRST_COMPONENT_NAME =
+ "objectIdentifierFirstComponentMatch";
+
+ /**
+ * The OID for the objectIdentifierFirstComponentMatch equality
+ * matching rule.
+ */
+ public static final String EMR_OID_FIRST_COMPONENT_OID = "2.5.13.30";
+
+ /**
+ * The name for the presentationAddressMatch equality matching rule.
+ */
+ public static final String EMR_PRESENTATION_ADDRESS_NAME =
+ "presentationAddressMatch";
+
+ /**
+ * The OID for the presentationAddressMatch equality matching rule.
+ */
+ public static final String EMR_PRESENTATION_ADDRESS_OID = "2.5.13.22";
+
+ /**
+ * The name for the protocolInformationMatch equality matching rule.
+ */
+ public static final String EMR_PROTOCOL_INFORMATION_NAME =
+ "protocolInformationMatch";
+
+ /**
+ * The OID for the protocolInformationMatch equality matching rule.
+ */
+ public static final String EMR_PROTOCOL_INFORMATION_OID = "2.5.13.24";
+
+ /**
+ * The name for the telephoneNumberMatch equality matching rule.
+ */
+ public static final String EMR_TELEPHONE_NAME =
+ "telephoneNumberMatch";
+
+ /**
+ * The OID for the telephoneNumberMatch equality matching rule.
+ */
+ public static final String EMR_TELEPHONE_OID = "2.5.13.20";
+
+ /**
+ * The name for the uniqueMemberMatch equality matching rule.
+ */
+ public static final String EMR_UNIQUE_MEMBER_NAME =
+ "uniqueMemberMatch";
+
+ /**
+ * The OID for the uniqueMemberMatch equality matching rule.
+ */
+ public static final String EMR_UNIQUE_MEMBER_OID = "2.5.13.23";
+
+ /**
+ * The description for the userPasswordExactMatch matching rule.
+ */
+ public static final String EMR_USER_PASSWORD_EXACT_DESCRIPTION =
+ "user password exact matching rule";
+
+ /**
+ * The name for the userPasswordExactMatch equality matching rule.
+ */
+ public static final String EMR_USER_PASSWORD_EXACT_NAME =
+ "ds-mr-user-password-exact";
+
+ /**
+ * The OID for the userPasswordExactMatch equality matching rule.
+ */
+ public static final String EMR_USER_PASSWORD_EXACT_OID =
+ OID_OPENDS_SERVER_MATCHING_RULE_BASE + ".2";
+
+ /**
+ * The description for the userPasswordMatch matching rule.
+ */
+ public static final String EMR_USER_PASSWORD_DESCRIPTION =
+ "user password matching rule";
+
+ /**
+ * The name for the userPasswordMatch equality matching rule.
+ */
+ public static final String EMR_USER_PASSWORD_NAME =
+ "ds-mr-user-password-equality";
+
+ /**
+ * The OID for the userPasswordMatch equality matching rule.
+ */
+ public static final String EMR_USER_PASSWORD_OID =
+ OID_OPENDS_SERVER_MATCHING_RULE_BASE + ".3";
+
+ /**
+ * The name for the uuidMatch equality matching rule.
+ */
+ public static final String EMR_UUID_NAME = "uuidMatch";
+
+ /**
+ * The OID for the uuidMatch equality matching rule.
+ */
+ public static final String EMR_UUID_OID = "1.3.6.1.1.16.2";
+
+ /**
+ * The name for the wordMatch equality matching rule.
+ */
+ public static final String EMR_WORD_NAME = "wordMatch";
+
+ /**
+ * The OID for the wordMatch equality matching rule.
+ */
+ public static final String EMR_WORD_OID = "2.5.13.32";
+
+ /**
+ * The name for the caseExactOrderingMatch ordering matching rule.
+ */
+ public static final String OMR_CASE_EXACT_NAME =
+ "caseExactOrderingMatch";
+
+ /**
+ * The OID for the caseExactOrderingMatch ordering matching rule.
+ */
+ public static final String OMR_CASE_EXACT_OID = "2.5.13.6";
+
+ /**
+ * The name for the caseIgnoreOrderingMatch ordering matching rule.
+ */
+ public static final String OMR_CASE_IGNORE_NAME =
+ "caseIgnoreOrderingMatch";
+
+ /**
+ * The OID for the caseIgnoreOrderingMatch ordering matching rule.
+ */
+ public static final String OMR_CASE_IGNORE_OID = "2.5.13.3";
+
+ /**
+ * The name for the generalizedTimeOrderingMatch ordering matching
+ * rule.
+ */
+ public static final String OMR_GENERALIZED_TIME_NAME =
+ "generalizedTimeOrderingMatch";
+
+ /**
+ * The OID for the generalizedTimeOrderingMatch ordering matching
+ * rule.
+ */
+ public static final String OMR_GENERALIZED_TIME_OID = "2.5.13.28";
+
+ /**
+ * The name for the integerOrderingMatch ordering matching rule.
+ */
+ public static final String OMR_INTEGER_NAME = "integerOrderingMatch";
+
+ /**
+ * The OID for the integerOrderingMatch ordering matching rule.
+ */
+ public static final String OMR_INTEGER_OID = "2.5.13.15";
+
+ /**
+ * The name for the numericStringOrderingMatch ordering matching rule.
+ */
+ public static final String OMR_NUMERIC_STRING_NAME =
+ "numericStringOrderingMatch";
+
+ /**
+ * The OID for the numericStringOrderingMatch ordering matching rule.
+ */
+ public static final String OMR_NUMERIC_STRING_OID = "2.5.13.9";
+
+ /**
+ * The name for the octetStringOrderingMatch ordering matching rule.
+ */
+ public static final String OMR_OCTET_STRING_NAME =
+ "octetStringOrderingMatch";
+
+ /**
+ * The OID for the octetStringOrderingMatch ordering matching rule.
+ */
+ public static final String OMR_OCTET_STRING_OID = "2.5.13.18";
+
+ /**
+ * The name for the uuidOrderingMatch ordering matching rule.
+ */
+ public static final String OMR_UUID_NAME = "uuidOrderingMatch";
+
+ /**
+ * The OID for the uuidOrderingMatch ordering matching rule.
+ */
+ public static final String OMR_UUID_OID = "1.3.6.1.1.16.3";
+
+ /**
+ * The name for the enumOrderingMatch ordering matching rule.
+ */
+ public static final String OMR_GENERIC_ENUM_NAME =
+ "enumOrderingMatch";
+
+ /**
+ * The oid for the generic enum syntax ordering matching rule.
+ */
+ public static final String OMR_OID_GENERIC_ENUM =
+ "1.3.6.1.4.1.26027.1.4.8";
+
+ /**
+ * The name for the caseExactSubstringsMatch substring matching rule.
+ */
+ public static final String SMR_CASE_EXACT_NAME =
+ "caseExactSubstringsMatch";
+
+ /**
+ * The OID for the caseExactSubstringsMatch substring matching rule.
+ */
+ public static final String SMR_CASE_EXACT_OID = "2.5.13.7";
+
+ /**
+ * The name for the caseExactIA5SubstringsMatch substring matching
+ * rule.
+ */
+ public static final String SMR_CASE_EXACT_IA5_NAME =
+ "caseExactIA5SubstringsMatch";
+
+ /**
+ * The OID for the caseExactIA5SubstringsMatch substring matching
+ * rule. // FIXME -- This needs to be updated once a real OID is
+ * assigned.
+ */
+ public static final String SMR_CASE_EXACT_IA5_OID =
+ OID_OPENDS_SERVER_MATCHING_RULE_BASE + ".902";
+
+ /**
+ * The name for the caseIgnoreSubstringsMatch substring matching rule.
+ */
+ public static final String SMR_CASE_IGNORE_NAME =
+ "caseIgnoreSubstringsMatch";
+
+ /**
+ * The OID for the caseIgnoreSubstringsMatch substring matching rule.
+ */
+ public static final String SMR_CASE_IGNORE_OID = "2.5.13.4";
+
+ /**
+ * The name for the caseIgnoreIA5SubstringsMatch substring matching
+ * rule.
+ */
+ public static final String SMR_CASE_IGNORE_IA5_NAME =
+ "caseIgnoreIA5SubstringsMatch";
+
+ /**
+ * The OID for the caseIgnoreIA5SubstringsMatch substring matching
+ * rule.
+ */
+ public static final String SMR_CASE_IGNORE_IA5_OID =
+ "1.3.6.1.4.1.1466.109.114.3";
+
+ /**
+ * The name for the caseIgnoreListSubstringsMatch substring matching
+ * rule.
+ */
+ public static final String SMR_CASE_IGNORE_LIST_NAME =
+ "caseIgnoreListSubstringsMatch";
+
+ /**
+ * The OID for the caseIgnoreListSubstringsMatch substring matching
+ * rule.
+ */
+ public static final String SMR_CASE_IGNORE_LIST_OID = "2.5.13.12";
+
+ /**
+ * The name for the numericStringSubstringsMatch substring matching
+ * rule.
+ */
+ public static final String SMR_NUMERIC_STRING_NAME =
+ "numericStringSubstringsMatch";
+
+ /**
+ * The OID for the numericStringSubstringsMatch substring matching
+ * rule.
+ */
+ public static final String SMR_NUMERIC_STRING_OID = "2.5.13.10";
+
+ /**
+ * The name for the octetStringSubstringsMatch substring matching
+ * rule.
+ */
+ public static final String SMR_OCTET_STRING_NAME =
+ "octetStringSubstringsMatch";
+
+ /**
+ * The OID for the octetStringSubstringsMatch substring matching rule.
+ */
+ public static final String SMR_OCTET_STRING_OID = "2.5.13.19";
+
+ /**
+ * The name for the telephoneNumberSubstringsMatch substring matching
+ * rule.
+ */
+ public static final String SMR_TELEPHONE_NAME =
+ "telephoneNumberSubstringsMatch";
+
+ /**
+ * The OID for the telephoneNumberSubstringsMatch substring matching
+ * rule.
+ */
+ public static final String SMR_TELEPHONE_OID = "2.5.13.21";
+
+ /**
+ * The OID for the absolute subtree specification attribute syntax.
+ */
+ public static final String SYNTAX_ABSOLUTE_SUBTREE_SPECIFICATION_OID =
+ OID_OPENDS_SERVER_ATTRIBUTE_SYNTAX_BASE + ".3";
+
+ /**
+ * The description for the absolute subtree specification attribute
+ * syntax.
+ */
+ public static final String SYNTAX_ABSOLUTE_SUBTREE_SPECIFICATION_DESCRIPTION =
+ "Absolute Subtree Specification";
+
+ /**
+ * The name for the absolute subtree specification attribute syntax.
+ */
+ public static final String SYNTAX_ABSOLUTE_SUBTREE_SPECIFICATION_NAME =
+ "ds-absolute-subtree-specification";
+
+ /**
+ * The OID for the aci attribute syntax.
+ */
+ public static final String SYNTAX_ACI_OID =
+ OID_OPENDS_SERVER_ATTRIBUTE_SYNTAX_BASE + ".4";
+
+ /**
+ * The description for aci attribute syntax.
+ */
+ public static final String SYNTAX_ACI_DESCRIPTION =
+ "Sun-defined Access Control Information";
+
+ /**
+ * The name for the aci attribute syntax.
+ */
+ public static final String SYNTAX_ACI_NAME =
+ "ds-syntax-dseecompat-aci";
+
+ /**
+ * The description for the attribute type description attribute
+ * syntax.
+ */
+ public static final String SYNTAX_ATTRIBUTE_TYPE_DESCRIPTION =
+ "Attribute Type Description";
+
+ /**
+ * The name for the attribute type description attribute syntax.
+ */
+ public static final String SYNTAX_ATTRIBUTE_TYPE_NAME =
+ "AttributeTypeDescription";
+
+ /**
+ * The OID for the attribute type description attribute syntax.
+ */
+ public static final String SYNTAX_ATTRIBUTE_TYPE_OID =
+ "1.3.6.1.4.1.1466.115.121.1.3";
+
+ /**
+ * The description for the auth password attribute syntax.
+ */
+ public static final String SYNTAX_AUTH_PASSWORD_DESCRIPTION =
+ "Authentication Password Syntax";
+
+ /**
+ * The name for the auth password attribute syntax.
+ */
+ public static final String SYNTAX_AUTH_PASSWORD_NAME =
+ "AuthenticationPasswordSyntax";
+
+ /**
+ * The OID for the auth password attribute syntax.
+ */
+ public static final String SYNTAX_AUTH_PASSWORD_OID =
+ "1.3.6.1.4.1.4203.1.1.2";
+
+ /**
+ * The description for the binary attribute syntax.
+ */
+ public static final String SYNTAX_BINARY_DESCRIPTION = "Binary";
+
+ /**
+ * The name for the binary attribute syntax.
+ */
+ public static final String SYNTAX_BINARY_NAME = "Binary";
+
+ /**
+ * The OID for the binary attribute syntax.
+ */
+ public static final String SYNTAX_BINARY_OID =
+ "1.3.6.1.4.1.1466.115.121.1.5";
+
+ /**
+ * The description for the bit string attribute syntax.
+ */
+ public static final String SYNTAX_BIT_STRING_DESCRIPTION =
+ "Bit String";
+
+ /**
+ * The name for the bit string attribute syntax.
+ */
+ public static final String SYNTAX_BIT_STRING_NAME = "BitString";
+
+ /**
+ * The OID for the bit string attribute syntax.
+ */
+ public static final String SYNTAX_BIT_STRING_OID =
+ "1.3.6.1.4.1.1466.115.121.1.6";
+
+ /**
+ * The description for the Boolean attribute syntax.
+ */
+ public static final String SYNTAX_BOOLEAN_DESCRIPTION = "Boolean";
+
+ /**
+ * The name for the Boolean attribute syntax.
+ */
+ public static final String SYNTAX_BOOLEAN_NAME = "Boolean";
+
+ /**
+ * The OID for the Boolean attribute syntax.
+ */
+ public static final String SYNTAX_BOOLEAN_OID =
+ "1.3.6.1.4.1.1466.115.121.1.7";
+
+ /**
+ * The description for the certificate attribute syntax.
+ */
+ public static final String SYNTAX_CERTIFICATE_DESCRIPTION =
+ "Certificate";
+
+ /**
+ * The name for the certificate attribute syntax.
+ */
+ public static final String SYNTAX_CERTIFICATE_NAME = "Certificate";
+
+ /**
+ * The OID for the certificate attribute syntax.
+ */
+ public static final String SYNTAX_CERTIFICATE_OID =
+ "1.3.6.1.4.1.1466.115.121.1.8";
+
+ /**
+ * The description for the certificate list attribute syntax.
+ */
+ public static final String SYNTAX_CERTLIST_DESCRIPTION =
+ "Certificate List";
+
+ /**
+ * The name for the certificate list attribute syntax.
+ */
+ public static final String SYNTAX_CERTLIST_NAME = "CertificateList";
+
+ /**
+ * The OID for the certificate list attribute syntax.
+ */
+ public static final String SYNTAX_CERTLIST_OID =
+ "1.3.6.1.4.1.1466.115.121.1.9";
+
+ /**
+ * The description for the certificate pair attribute syntax.
+ */
+ public static final String SYNTAX_CERTPAIR_DESCRIPTION =
+ "Certificate Pair";
+
+ /**
+ * The name for the certificate pair attribute syntax.
+ */
+ public static final String SYNTAX_CERTPAIR_NAME = "CertificatePair";
+
+ /**
+ * The OID for the certificate pair attribute syntax.
+ */
+ public static final String SYNTAX_CERTPAIR_OID =
+ "1.3.6.1.4.1.1466.115.121.1.10";
+
+ /**
+ * The description for the country string attribute syntax.
+ */
+ public static final String SYNTAX_COUNTRY_STRING_DESCRIPTION =
+ "Country String";
+
+ /**
+ * The name for the country string attribute syntax.
+ */
+ public static final String SYNTAX_COUNTRY_STRING_NAME =
+ "CountryString";
+
+ /**
+ * The OID for the country string attribute syntax.
+ */
+ public static final String SYNTAX_COUNTRY_STRING_OID =
+ "1.3.6.1.4.1.1466.115.121.1.11";
+
+ /**
+ * The description for the delivery method attribute syntax.
+ */
+ public static final String SYNTAX_DELIVERY_METHOD_DESCRIPTION =
+ "Delivery Method";
+
+ /**
+ * The name for the delivery method attribute syntax.
+ */
+ public static final String SYNTAX_DELIVERY_METHOD_NAME =
+ "DeliveryMethod";
+
+ /**
+ * The OID for the delivery method attribute syntax.
+ */
+ public static final String SYNTAX_DELIVERY_METHOD_OID =
+ "1.3.6.1.4.1.1466.115.121.1.14";
+
+ /**
+ * The description for the Directory String attribute syntax.
+ */
+ public static final String SYNTAX_DIRECTORY_STRING_DESCRIPTION =
+ "Directory String";
+
+ /**
+ * The name for the Directory String attribute syntax.
+ */
+ public static final String SYNTAX_DIRECTORY_STRING_NAME =
+ "DirectoryString";
+
+ /**
+ * The OID for the Directory String attribute syntax.
+ */
+ public static final String SYNTAX_DIRECTORY_STRING_OID =
+ "1.3.6.1.4.1.1466.115.121.1.15";
+
+ /**
+ * The description for the DIT content rule description attribute
+ * syntax.
+ */
+ public static final String SYNTAX_DIT_CONTENT_RULE_DESCRIPTION =
+ "DIT Content Rule Description";
+
+ /**
+ * The name for the DIT content rule description attribute syntax.
+ */
+ public static final String SYNTAX_DIT_CONTENT_RULE_NAME =
+ "DITContentRuleDescription";
+
+ /**
+ * The OID for the DIT content rule description attribute syntax.
+ */
+ public static final String SYNTAX_DIT_CONTENT_RULE_OID =
+ "1.3.6.1.4.1.1466.115.121.1.16";
+
+ /**
+ * The description for the DIT structure rule description attribute
+ * syntax.
+ */
+ public static final String SYNTAX_DIT_STRUCTURE_RULE_DESCRIPTION =
+ "DIT Structure Rule Description";
+
+ /**
+ * The name for the DIT structure rule description attribute syntax.
+ */
+ public static final String SYNTAX_DIT_STRUCTURE_RULE_NAME =
+ "DITStructureRuleDescription";
+
+ /**
+ * The OID for the DIT structure rule description attribute syntax.
+ */
+ public static final String SYNTAX_DIT_STRUCTURE_RULE_OID =
+ "1.3.6.1.4.1.1466.115.121.1.17";
+
+ /**
+ * The description for the distinguished name attribute syntax.
+ */
+ public static final String SYNTAX_DN_DESCRIPTION = "DN";
+
+ /**
+ * The name for the distinguished name attribute syntax.
+ */
+ public static final String SYNTAX_DN_NAME = "DN";
+
+ /**
+ * The OID for the distinguished name attribute syntax.
+ */
+ public static final String SYNTAX_DN_OID =
+ "1.3.6.1.4.1.1466.115.121.1.12";
+
+ /**
+ * The description for the enhanced guide attribute syntax.
+ */
+ public static final String SYNTAX_ENHANCED_GUIDE_DESCRIPTION =
+ "Enhanced Guide";
+
+ /**
+ * The name for the enhanced guide attribute syntax.
+ */
+ public static final String SYNTAX_ENHANCED_GUIDE_NAME =
+ "EnhancedGuide";
+
+ /**
+ * The OID for the enhanced guide attribute syntax.
+ */
+ public static final String SYNTAX_ENHANCED_GUIDE_OID =
+ "1.3.6.1.4.1.1466.115.121.1.21";
+
+ /**
+ * The description for the facsimile telephone number attribute
+ * syntax.
+ */
+ public static final String SYNTAX_FAXNUMBER_DESCRIPTION =
+ "Facsimile Telephone Number";
+
+ /**
+ * The name for the facsimile telephone number attribute syntax.
+ */
+ public static final String SYNTAX_FAXNUMBER_NAME =
+ "FacsimileTelephoneNumber";
+
+ /**
+ * The OID for the facsimile telephone number attribute syntax.
+ */
+ public static final String SYNTAX_FAXNUMBER_OID =
+ "1.3.6.1.4.1.1466.115.121.1.22";
+
+ /**
+ * The description for the fax attribute syntax.
+ */
+ public static final String SYNTAX_FAX_DESCRIPTION = "Fax";
+
+ /**
+ * The name for the fax attribute syntax.
+ */
+ public static final String SYNTAX_FAX_NAME = "Fax";
+
+ /**
+ * The OID for the fax attribute syntax.
+ */
+ public static final String SYNTAX_FAX_OID =
+ "1.3.6.1.4.1.1466.115.121.1.23";
+
+ /**
+ * The description for the generalized time attribute syntax.
+ */
+ public static final String SYNTAX_GENERALIZED_TIME_DESCRIPTION =
+ "Generalized Time";
+
+ /**
+ * The name for the generalized time attribute syntax.
+ */
+ public static final String SYNTAX_GENERALIZED_TIME_NAME =
+ "GeneralizedTime";
+
+ /**
+ * The OID for the generalized time attribute syntax.
+ */
+ public static final String SYNTAX_GENERALIZED_TIME_OID =
+ "1.3.6.1.4.1.1466.115.121.1.24";
+
+ /**
+ * The description for the guide attribute syntax.
+ */
+ public static final String SYNTAX_GUIDE_DESCRIPTION = "Guide";
+
+ /**
+ * The name for the guide attribute syntax.
+ */
+ public static final String SYNTAX_GUIDE_NAME = "Guide";
+
+ /**
+ * The OID for the guide attribute syntax.
+ */
+ public static final String SYNTAX_GUIDE_OID =
+ "1.3.6.1.4.1.1466.115.121.1.25";
+
+ /**
+ * The description for the IA5 string attribute syntax.
+ */
+ public static final String SYNTAX_IA5_STRING_DESCRIPTION =
+ "IA5 String";
+
+ /**
+ * The name for the IA5 string attribute syntax.
+ */
+ public static final String SYNTAX_IA5_STRING_NAME = "IA5String";
+
+ /**
+ * The OID for the IA5 string attribute syntax.
+ */
+ public static final String SYNTAX_IA5_STRING_OID =
+ "1.3.6.1.4.1.1466.115.121.1.26";
+
+ /**
+ * The description for the integer attribute syntax.
+ */
+ public static final String SYNTAX_INTEGER_DESCRIPTION = "Integer";
+
+ /**
+ * The name for the integer attribute syntax.
+ */
+ public static final String SYNTAX_INTEGER_NAME = "Integer";
+
+ /**
+ * The OID for the integer attribute syntax.
+ */
+ public static final String SYNTAX_INTEGER_OID =
+ "1.3.6.1.4.1.1466.115.121.1.27";
+
+ /**
+ * The description for the JPEG attribute syntax.
+ */
+ public static final String SYNTAX_JPEG_DESCRIPTION = "JPEG";
+
+ /**
+ * The name for the JPEG attribute syntax.
+ */
+ public static final String SYNTAX_JPEG_NAME = "JPEG";
+
+ /**
+ * The OID for the JPEG attribute syntax.
+ */
+ public static final String SYNTAX_JPEG_OID =
+ "1.3.6.1.4.1.1466.115.121.1.28";
+
+ /**
+ * The description for the LDAP syntax description attribute syntax.
+ */
+ public static final String SYNTAX_LDAP_SYNTAX_DESCRIPTION =
+ "LDAP Syntax Description";
+
+ /**
+ * The name for the LDAP syntax description attribute syntax.
+ */
+ public static final String SYNTAX_LDAP_SYNTAX_NAME =
+ "LDAPSyntaxDescription";
+
+ /**
+ * The OID for the LDAP syntax description attribute syntax.
+ */
+ public static final String SYNTAX_LDAP_SYNTAX_OID =
+ "1.3.6.1.4.1.1466.115.121.1.54";
+
+ /**
+ * The description for the matching rule description attribute syntax.
+ */
+ public static final String SYNTAX_MATCHING_RULE_DESCRIPTION =
+ "Matching Rule Description";
+
+ /**
+ * The name for the matching rule description attribute syntax.
+ */
+ public static final String SYNTAX_MATCHING_RULE_NAME =
+ "MatchingRuleDescription";
+
+ /**
+ * The OID for the matching rule description attribute syntax.
+ */
+ public static final String SYNTAX_MATCHING_RULE_OID =
+ "1.3.6.1.4.1.1466.115.121.1.30";
+
+ /**
+ * The description for the matching rule use description attribute
+ * syntax.
+ */
+ public static final String SYNTAX_MATCHING_RULE_USE_DESCRIPTION =
+ "Matching Rule Use Description";
+
+ /**
+ * The name for the matching rule use description attribute syntax.
+ */
+ public static final String SYNTAX_MATCHING_RULE_USE_NAME =
+ "MatchingRuleUseDescription";
+
+ /**
+ * The OID for the matching rule use description attribute syntax.
+ */
+ public static final String SYNTAX_MATCHING_RULE_USE_OID =
+ "1.3.6.1.4.1.1466.115.121.1.31";
+
+ /**
+ * The description for the name and optional uid attribute syntax.
+ */
+ public static final String SYNTAX_NAME_AND_OPTIONAL_UID_DESCRIPTION =
+ "Name and Optional UID";
+
+ /**
+ * The name for the name and optional uid attribute syntax.
+ */
+ public static final String SYNTAX_NAME_AND_OPTIONAL_UID_NAME =
+ "NameAndOptionalUID";
+
+ /**
+ * The OID for the name and optional uid attribute syntax.
+ */
+ public static final String SYNTAX_NAME_AND_OPTIONAL_UID_OID =
+ "1.3.6.1.4.1.1466.115.121.1.34";
+
+ /**
+ * The description for the name form description attribute syntax.
+ */
+ public static final String SYNTAX_NAME_FORM_DESCRIPTION =
+ "Name Form Description";
+
+ /**
+ * The name for the name form description attribute syntax.
+ */
+ public static final String SYNTAX_NAME_FORM_NAME =
+ "NameFormDescription";
+
+ /**
+ * The OID for the name form description attribute syntax.
+ */
+ public static final String SYNTAX_NAME_FORM_OID =
+ "1.3.6.1.4.1.1466.115.121.1.35";
+
+ /**
+ * The description for the numeric string attribute syntax.
+ */
+ public static final String SYNTAX_NUMERIC_STRING_DESCRIPTION =
+ "Numeric String";
+
+ /**
+ * The name for the numeric string attribute syntax.
+ */
+ public static final String SYNTAX_NUMERIC_STRING_NAME =
+ "NumericString";
+
+ /**
+ * The OID for the numeric string attribute syntax.
+ */
+ public static final String SYNTAX_NUMERIC_STRING_OID =
+ "1.3.6.1.4.1.1466.115.121.1.36";
+
+ /**
+ * The description for the object class description attribute syntax.
+ */
+ public static final String SYNTAX_OBJECTCLASS_DESCRIPTION =
+ "Object Class Description";
+
+ /**
+ * The name for the object class description attribute syntax.
+ */
+ public static final String SYNTAX_OBJECTCLASS_NAME =
+ "ObjectClassDescription";
+
+ /**
+ * The OID for the object class description attribute syntax.
+ */
+ public static final String SYNTAX_OBJECTCLASS_OID =
+ "1.3.6.1.4.1.1466.115.121.1.37";
+
+ /**
+ * The description for the octet string attribute syntax.
+ */
+ public static final String SYNTAX_OCTET_STRING_DESCRIPTION =
+ "Octet String";
+
+ /**
+ * The name for the octet string attribute syntax.
+ */
+ public static final String SYNTAX_OCTET_STRING_NAME = "OctetString";
+
+ /**
+ * The OID for the octet string attribute syntax.
+ */
+ public static final String SYNTAX_OCTET_STRING_OID =
+ "1.3.6.1.4.1.1466.115.121.1.40";
+
+ /**
+ * The description for the object identifier attribute syntax.
+ */
+ public static final String SYNTAX_OID_DESCRIPTION = "OID";
+
+ /**
+ * The name for the object identifier attribute syntax.
+ */
+ public static final String SYNTAX_OID_NAME = "OID";
+
+ /**
+ * The OID for the object identifier attribute syntax.
+ */
+ public static final String SYNTAX_OID_OID =
+ "1.3.6.1.4.1.1466.115.121.1.38";
+
+ /**
+ * The description for the other mailbox attribute syntax.
+ */
+ public static final String SYNTAX_OTHER_MAILBOX_DESCRIPTION =
+ "Other Mailbox";
+
+ /**
+ * The name for the other mailbox attribute syntax.
+ */
+ public static final String SYNTAX_OTHER_MAILBOX_NAME = "OtherMailbox";
+
+ /**
+ * The OID for the other mailbox attribute syntax.
+ */
+ public static final String SYNTAX_OTHER_MAILBOX_OID =
+ "1.3.6.1.4.1.1466.115.121.1.39";
+
+ /**
+ * The description for the postal address attribute syntax.
+ */
+ public static final String SYNTAX_POSTAL_ADDRESS_DESCRIPTION =
+ "Postal Address";
+
+ /**
+ * The name for the postal address attribute syntax.
+ */
+ public static final String SYNTAX_POSTAL_ADDRESS_NAME =
+ "PostalAddress";
+
+ /**
+ * The OID for the postal address attribute syntax.
+ */
+ public static final String SYNTAX_POSTAL_ADDRESS_OID =
+ "1.3.6.1.4.1.1466.115.121.1.41";
+
+ /**
+ * The description for the presentation address attribute syntax.
+ */
+ public static final String SYNTAX_PRESENTATION_ADDRESS_DESCRIPTION =
+ "Presentation Address";
+
+ /**
+ * The name for the presentation address attribute syntax.
+ */
+ public static final String SYNTAX_PRESENTATION_ADDRESS_NAME =
+ "PresentationAddress";
+
+ /**
+ * The OID for the presentation address attribute syntax.
+ */
+ public static final String SYNTAX_PRESENTATION_ADDRESS_OID =
+ "1.3.6.1.4.1.1466.115.121.1.43";
+
+ /**
+ * The description for the printable string attribute syntax.
+ */
+ public static final String SYNTAX_PRINTABLE_STRING_DESCRIPTION =
+ "Printable String";
+
+ /**
+ * The name for the printable string attribute syntax.
+ */
+ public static final String SYNTAX_PRINTABLE_STRING_NAME =
+ "PrintableString";
+
+ /**
+ * The OID for the printable string attribute syntax.
+ */
+ public static final String SYNTAX_PRINTABLE_STRING_OID =
+ "1.3.6.1.4.1.1466.115.121.1.44";
+
+ /**
+ * The description for the protocol information attribute syntax.
+ */
+ public static final String SYNTAX_PROTOCOL_INFORMATION_DESCRIPTION =
+ "Protocol Information";
+
+ /**
+ * The name for the protocol information attribute syntax.
+ */
+ public static final String SYNTAX_PROTOCOL_INFORMATION_NAME =
+ "ProtocolInformation";
+
+ /**
+ * The OID for the protocol information attribute syntax.
+ */
+ public static final String SYNTAX_PROTOCOL_INFORMATION_OID =
+ "1.3.6.1.4.1.1466.115.121.1.42";
+
+ /**
+ * The OID for the relative subtree specification attribute syntax.
+ */
+ public static final String SYNTAX_RELATIVE_SUBTREE_SPECIFICATION_OID =
+ OID_OPENDS_SERVER_ATTRIBUTE_SYNTAX_BASE + ".2";
+
+ /**
+ * The description for the relative subtree specification attribute
+ * syntax.
+ */
+ public static final String SYNTAX_RELATIVE_SUBTREE_SPECIFICATION_DESCRIPTION =
+ "Relative Subtree Specification";
+
+ /**
+ * The name for the relative subtree specification attribute syntax.
+ */
+ public static final String SYNTAX_RELATIVE_SUBTREE_SPECIFICATION_NAME =
+ "ds-relative-subtree-specification";
+
+ /**
+ * The OID for the RFC3672 subtree specification attribute syntax.
+ */
+ public static final String SYNTAX_RFC3672_SUBTREE_SPECIFICATION_OID =
+ "1.3.6.1.4.1.1466.115.121.1.45";
+
+ /**
+ * The description for the RFC3672 subtree specification attribute
+ * syntax.
+ */
+ public static final String SYNTAX_RFC3672_SUBTREE_SPECIFICATION_DESCRIPTION =
+ "RFC3672 Subtree Specification";
+
+ /**
+ * The name for the RFC3672 subtree specification attribute syntax.
+ */
+ public static final String SYNTAX_RFC3672_SUBTREE_SPECIFICATION_NAME =
+ "SubtreeSpecification";
+
+ /**
+ * The description for the substring assertion attribute syntax.
+ */
+ public static final String SYNTAX_SUBSTRING_ASSERTION_DESCRIPTION =
+ "Substring Assertion";
+
+ /**
+ * The name for the substring assertion attribute syntax.
+ */
+ public static final String SYNTAX_SUBSTRING_ASSERTION_NAME =
+ "SubstringAssertion";
+
+ /**
+ * The OID for the Substring Assertion syntax used for assertion
+ * values in extensible match filters.
+ */
+ public static final String SYNTAX_SUBSTRING_ASSERTION_OID =
+ "1.3.6.1.4.1.1466.115.121.1.58";
+
+ /**
+ * The description for the supported algorithm attribute syntax.
+ */
+ public static final String SYNTAX_SUPPORTED_ALGORITHM_DESCRIPTION =
+ "Supported Algorithm";
+
+ /**
+ * The name for the supported algorithm attribute syntax.
+ */
+ public static final String SYNTAX_SUPPORTED_ALGORITHM_NAME =
+ "SupportedAlgorithm";
+
+ /**
+ * The OID for the Substring Assertion syntax used for assertion
+ * values in extensible match filters.
+ */
+ public static final String SYNTAX_SUPPORTED_ALGORITHM_OID =
+ "1.3.6.1.4.1.1466.115.121.1.49";
+
+ /**
+ * The description for the telephone number attribute syntax.
+ */
+ public static final String SYNTAX_TELEPHONE_DESCRIPTION =
+ "Telephone Number";
+
+ /**
+ * The name for the telephone number attribute syntax.
+ */
+ public static final String SYNTAX_TELEPHONE_NAME = "TelephoneNumber";
+
+ /**
+ * The OID for the telephone number attribute syntax.
+ */
+ public static final String SYNTAX_TELEPHONE_OID =
+ "1.3.6.1.4.1.1466.115.121.1.50";
+
+ /**
+ * The description for the teletex terminal identifier attribute
+ * syntax.
+ */
+ public static final String SYNTAX_TELETEX_TERM_ID_DESCRIPTION =
+ "Teletex Terminal Identifier";
+
+ /**
+ * The name for the teletex terminal identifier attribute syntax.
+ */
+ public static final String SYNTAX_TELETEX_TERM_ID_NAME =
+ "TeletexTerminalIdentifier";
+
+ /**
+ * The OID for the teletex terminal identifier attribute syntax.
+ */
+ public static final String SYNTAX_TELETEX_TERM_ID_OID =
+ "1.3.6.1.4.1.1466.115.121.1.51";
+
+ /**
+ * The description for the telex number attribute syntax.
+ */
+ public static final String SYNTAX_TELEX_DESCRIPTION = "Telex Number";
+
+ /**
+ * The name for the telex number attribute syntax.
+ */
+ public static final String SYNTAX_TELEX_NAME = "TelexNumber";
+
+ /**
+ * The OID for the telex number attribute syntax.
+ */
+ public static final String SYNTAX_TELEX_OID =
+ "1.3.6.1.4.1.1466.115.121.1.52";
+
+ /**
+ * The description for the user password attribute syntax.
+ */
+ public static final String SYNTAX_USER_PASSWORD_DESCRIPTION =
+ "User Password";
+
+ /**
+ * The name for the user password attribute syntax.
+ */
+ public static final String SYNTAX_USER_PASSWORD_NAME =
+ "ds-syntax-user-password";
+
+ /**
+ * The OID for the user password attribute syntax.
+ */
+ public static final String SYNTAX_USER_PASSWORD_OID =
+ OID_OPENDS_SERVER_ATTRIBUTE_SYNTAX_BASE + ".1";
+
+ /**
+ * The description for the UTC time attribute syntax.
+ */
+ public static final String SYNTAX_UTC_TIME_DESCRIPTION = "UTC Time";
+
+ /**
+ * The name for the UTC time attribute syntax.
+ */
+ public static final String SYNTAX_UTC_TIME_NAME = "UTCTime";
+
+ /**
+ * The OID for the UTC time attribute syntax.
+ */
+ public static final String SYNTAX_UTC_TIME_OID =
+ "1.3.6.1.4.1.1466.115.121.1.53";
+
+ /**
+ * The description for the UUID attribute syntax.
+ */
+ public static final String SYNTAX_UUID_DESCRIPTION = "UUID";
+
+ /**
+ * The name for the UUID attribute syntax.
+ */
+ public static final String SYNTAX_UUID_NAME = "UUID";
+
+ /**
+ * The OID for the UUID attribute syntax.
+ */
+ public static final String SYNTAX_UUID_OID = "1.3.6.1.1.16.1";
+
+ /**
+ * The description for the "top" objectclass.
+ */
+ public static final String TOP_OBJECTCLASS_DESCRIPTION =
+ "Topmost ObjectClass";
+
+ /**
+ * The name of the "top" objectclass.
+ */
+ public static final String TOP_OBJECTCLASS_NAME = "top";
+
+ /**
+ * The OID for the "top" objectclass.
+ */
+ public static final String TOP_OBJECTCLASS_OID = "2.5.6.0";
+
+ /**
+ * The name for the relative time greater-than extensible ordering
+ * matching rule.
+ */
+ public static final String EXT_OMR_RELATIVE_TIME_GT_NAME =
+ "relativeTimeGTOrderingMatch";
+
+ /**
+ * The alternative name for the relative time greater-than extensible
+ * ordering matching rule.
+ */
+ public static final String EXT_OMR_RELATIVE_TIME_GT_ALT_NAME =
+ "relativeTimeOrderingMatch.gt";
+
+ /**
+ * The OID for the relative time greater-than extensible ordering
+ * matching rule.
+ */
+ public static final String EXT_OMR_RELATIVE_TIME_GT_OID =
+ "1.3.6.1.4.1.26027.1.4.5";
+
+ /**
+ * The name for the relative time less-than extensible ordering
+ * matching rule.
+ */
+ public static final String EXT_OMR_RELATIVE_TIME_LT_NAME =
+ "relativeTimeLTOrderingMatch";
+
+ /**
+ * The alternative name for the relative time less-than extensible
+ * ordering matching rule.
+ */
+ public static final String EXT_OMR_RELATIVE_TIME_LT_ALT_NAME =
+ "relativeTimeOrderingMatch.lt";
+
+ /**
+ * The OID for the relative time less-than extensible ordering
+ * matching rule.
+ */
+ public static final String EXT_OMR_RELATIVE_TIME_LT_OID =
+ "1.3.6.1.4.1.26027.1.4.6";
+
+ /**
+ * The OID for the partial date and time extensible matching rule.
+ */
+ public static final String EXT_PARTIAL_DATE_TIME_OID =
+ "1.3.6.1.4.1.26027.1.4.7";
+
+ /**
+ * The name for the partial date and time extensible rule.
+ */
+ public static final String EXT_PARTIAL_DATE_TIME_NAME =
+ "partialDateAndTimeMatchingRule";
+
+ /**
+ * The name of the schema extension that will be used to specify the
+ * approximate matching rule that should be used for a given attribute
+ * type.
+ */
+ public static final String SCHEMA_PROPERTY_APPROX_RULE = "X-APPROX";
+
+ /**
+ * The name of the schema property that will be used to specify the
+ * origin of a schema element.
+ */
+ public static final String SCHEMA_PROPERTY_ORIGIN = "X-ORIGIN";
+
+ /**
+ * The OID for the extensibleObject objectclass.
+ */
+ public static final String EXTENSIBLE_OBJECT_OBJECTCLASS_OID =
+ "1.3.6.1.4.1.1466.101.120.111";
+
+ /**
+ * The name for the extensibleObject objectclass.
+ */
+ public static final String EXTENSIBLE_OBJECT_OBJECTCLASS_NAME =
+ "extensibleObject";
+
+ /**
+ * The value representing just one space character.
+ */
+ public static final ByteString SINGLE_SPACE_VALUE =
+ ByteString.valueOf(" ");
+
+ /**
+ * The normalized true value.
+ */
+ public static final ByteString TRUE_VALUE =
+ ByteString.valueOf("TRUE");
+
+ /**
+ * The normalized false value.
+ */
+ public static final ByteString FALSE_VALUE =
+ ByteString.valueOf("FALSE");
+
+ /**
+ * The name of the time zone for universal coordinated time (UTC).
+ */
+ public static final String TIME_ZONE_UTC = "UTC";
+
+ /**
+ * The date format string that will be used to construct and parse
+ * dates represented using generalized time with a two-digit year. It
+ * is assumed that the provided date formatter will be set to UTC.
+ */
+ public static final String DATE_FORMAT_UTC_TIME = "yyMMddHHmmss'Z'";
+
+
+
+ // Prevent instantiation.
+ private SchemaConstants()
+ {
+ // Nothing to do.
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/schema/SchemaElement.java b/sdk/src/org/opends/sdk/schema/SchemaElement.java
new file mode 100644
index 0000000..59cdab7
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/SchemaElement.java
@@ -0,0 +1,185 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import java.util.List;
+import java.util.Map;
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * An abstract base class for LDAP schema definitions which contain an
+ * description, and an optional set of extra properties.
+ * <p>
+ * This class defines common properties and behaviour of the various
+ * types of schema definitions (e.g. object class definitions, and
+ * attribute type definitions).
+ */
+abstract class SchemaElement
+{
+ // The description for this definition.
+ final String description;
+
+ // The set of additional name-value pairs.
+ final Map<String, List<String>> extraProperties;
+
+
+
+ SchemaElement(String description,
+ Map<String, List<String>> extraProperties)
+ {
+ Validator.ensureNotNull(description, extraProperties);
+ this.description = description;
+ this.extraProperties = extraProperties;
+ }
+
+
+
+ /**
+ * Retrieves the description for this schema definition.
+ *
+ * @return The description for this schema definition.
+ */
+ public final String getDescription()
+ {
+
+ return description;
+ }
+
+
+
+ /**
+ * Retrieves an iterable over the value(s) of the specified "extra"
+ * property for this schema definition.
+ *
+ * @param name
+ * The name of the "extra" property for which to retrieve the
+ * value(s).
+ * @return Returns an iterable over the value(s) of the specified
+ * "extra" property for this schema definition, or
+ * <code>null</code> if no such property is defined.
+ */
+ public final Iterable<String> getExtraProperty(String name)
+ {
+
+ return extraProperties.get(name);
+ }
+
+
+
+ /**
+ * Retrieves an iterable over the names of "extra" properties
+ * associated with this schema definition.
+ *
+ * @return Returns an iterable over the names of "extra" properties
+ * associated with this schema definition.
+ */
+ public final Iterable<String> getExtraPropertyNames()
+ {
+
+ return extraProperties.keySet();
+ }
+
+
+
+ /**
+ * Builds a string representation of this schema definition in the
+ * form specified in RFC 2252.
+ *
+ * @return The string representation of this schema definition in the
+ * form specified in RFC 2252.
+ */
+ final String buildDefinition()
+ {
+ final StringBuilder buffer = new StringBuilder();
+
+ buffer.append("( ");
+
+ toStringContent(buffer);
+
+ if (!extraProperties.isEmpty())
+ {
+ for (final Map.Entry<String, List<String>> e : extraProperties
+ .entrySet())
+ {
+
+ final String property = e.getKey();
+
+ final List<String> valueList = e.getValue();
+
+ buffer.append(" ");
+ buffer.append(property);
+
+ if (valueList.size() == 1)
+ {
+ buffer.append(" '");
+ buffer.append(valueList.get(0));
+ buffer.append("'");
+ }
+ else
+ {
+ buffer.append(" ( ");
+
+ for (final String value : valueList)
+ {
+ buffer.append("'");
+ buffer.append(value);
+ buffer.append("' ");
+ }
+
+ buffer.append(")");
+ }
+ }
+ }
+
+ buffer.append(" )");
+
+ return buffer.toString();
+ }
+
+
+
+ /**
+ * Appends a string representation of this schema definition's
+ * non-generic properties to the provided buffer.
+ *
+ * @param buffer
+ * The buffer to which the information should be appended.
+ */
+ abstract void toStringContent(StringBuilder buffer);
+
+
+
+ abstract void validate(List<Message> warnings, Schema schema)
+ throws SchemaException;
+}
diff --git a/sdk/src/org/opends/sdk/schema/SchemaException.java b/sdk/src/org/opends/sdk/schema/SchemaException.java
new file mode 100644
index 0000000..4bd4ef1
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/SchemaException.java
@@ -0,0 +1,89 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.LocalizableException;
+
+
+
+/**
+ * Thrown when a schema could not be decoded or validated.
+ */
+@SuppressWarnings("serial")
+final class SchemaException extends Exception implements
+ LocalizableException
+{
+ // The I18N message associated with this exception.
+ private final Message message;
+
+
+
+ /**
+ * Creates a new schema exception with the provided message.
+ *
+ * @param message
+ * The message that explains the problem that occurred.
+ */
+ public SchemaException(Message message)
+ {
+ super(String.valueOf(message));
+ this.message = message;
+ }
+
+
+
+ /**
+ * Creates a new schema exception with the provided message and cause.
+ *
+ * @param message
+ * The message that explains the problem that occurred.
+ * @param cause
+ * The cause which may be later retrieved by the
+ * {@link #getCause} method. A {@code null} value is
+ * permitted, and indicates that the cause is nonexistent or
+ * unknown.
+ */
+ public SchemaException(Message message, Throwable cause)
+ {
+ super(String.valueOf(message), cause);
+ this.message = message;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Message getMessageObject()
+ {
+ return this.message;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/SchemaLocal.java b/sdk/src/org/opends/sdk/schema/SchemaLocal.java
new file mode 100644
index 0000000..748ea30
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/SchemaLocal.java
@@ -0,0 +1,130 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+/**
+ * This class provides schema-local variables. These variables differ
+ * from their normal counterparts in that each schema has its own
+ * independently initialized copy of the variable. {@code SchemaLocal}
+ * instances are typically private static fields in classes that wish to
+ * associate state with a schema (e.g., a schema dependent cache).
+ *
+ * @param <T>
+ * The type of the schema-local variable.
+ */
+public class SchemaLocal<T>
+{
+ /**
+ * Creates a schema-local variable.
+ */
+ public SchemaLocal()
+ {
+ // Nothing to do.
+ }
+
+
+
+ /**
+ * Returns the value in the provided schema's copy of this
+ * schema-local variable. If the variable has no value associated with
+ * the schema, it is first initialized to the value returned by an
+ * invocation of the {@link #initialValue} method.
+ *
+ * @param schema
+ * The schema whose copy of the schema-local variable is
+ * being requested.
+ * @return The schema-local value.
+ */
+ public final T get(Schema schema)
+ {
+ // Schema calls back to initialValue() if this is the first time.
+ return schema.getAttachment(this);
+ }
+
+
+
+ /**
+ * Removes the provided schema's value for this schema-local variable.
+ * If this schema-local variable is subsequently read, its value will
+ * be reinitialized by invoking its {@link #initialValue} method,
+ * unless its value is set in the interim. This may result in multiple
+ * invocations of the {@link #initialValue} method.
+ *
+ * @param schema
+ * The schema whose copy of the schema-local variable is
+ * being removed.
+ */
+ public final void remove(Schema schema)
+ {
+ schema.removeAttachment(this);
+ }
+
+
+
+ /**
+ * Sets the provided schema's copy of this schema-local variable to
+ * the specified value.
+ *
+ * @param schema
+ * The schema whose copy of the schema-local variable is
+ * being set.
+ * @param value
+ * The schema-local value.
+ */
+ public final void set(Schema schema, T value)
+ {
+ schema.setAttachment(this, value);
+ }
+
+
+
+ /**
+ * Returns the provided schema's "initial value" for this schema-local
+ * variable. This method will be invoked the first time the variable
+ * is accessed with the {@link #get} method for each schema, unless
+ * the {@link #set} method has been previously invoked, in which case
+ * the {@link #initialValue} method will not be invoked.
+ * <p>
+ * Normally, this method is invoked at most once per schema, but it
+ * may be invoked again in case of subsequent invocations of
+ * {@link #remove} followed by {@link #get}. This implementation
+ * simply returns {@code null}; if the programmer desires schema-local
+ * variables to have an initial value other than {@code null}, {@code
+ * SchemaLocal} must be subclassed, and this method overridden.
+ * Typically, an anonymous inner class will be used.
+ *
+ * @return The initial value for this schema-local.
+ */
+ protected T initialValue()
+ {
+ // Default implementation.
+ return null;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/SchemaNotFoundException.java b/sdk/src/org/opends/sdk/schema/SchemaNotFoundException.java
new file mode 100644
index 0000000..b4ceb1e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/SchemaNotFoundException.java
@@ -0,0 +1,93 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.LocalizableException;
+
+
+
+/**
+ * Thrown when a schema could not be decoded or validated.
+ * <p>
+ * TODO: is this needed? Should it be a sub-type of
+ * ErrorResultException?
+ */
+@SuppressWarnings("serial")
+public class SchemaNotFoundException extends Exception implements
+ LocalizableException
+{
+ // The I18N message associated with this exception.
+ private final Message message;
+
+
+
+ /**
+ * Creates a new schema not found exception with the provided message.
+ *
+ * @param message
+ * The message that explains the problem that occurred.
+ */
+ public SchemaNotFoundException(Message message)
+ {
+ super(String.valueOf(message));
+ this.message = message;
+ }
+
+
+
+ /**
+ * Creates a new schema not found exception with the provided message
+ * and cause.
+ *
+ * @param message
+ * The message that explains the problem that occurred.
+ * @param cause
+ * The cause which may be later retrieved by the
+ * {@link #getCause} method. A {@code null} value is
+ * permitted, and indicates that the cause is nonexistent or
+ * unknown.
+ */
+ public SchemaNotFoundException(Message message, Throwable cause)
+ {
+ super(String.valueOf(message), cause);
+ this.message = message;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Message getMessageObject()
+ {
+ return this.message;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/SchemaUtils.java b/sdk/src/org/opends/sdk/schema/SchemaUtils.java
new file mode 100644
index 0000000..7037b31
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/SchemaUtils.java
@@ -0,0 +1,883 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.util.StaticUtils.isAlpha;
+import static org.opends.sdk.util.StaticUtils.isDigit;
+
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * Schema utility methods.
+ */
+final class SchemaUtils
+{
+ /**
+ * Reads the value for an "extra" parameter. It will handle a single
+ * unquoted word (which is technically illegal, but we'll allow it), a
+ * single quoted string, or an open parenthesis followed by a
+ * space-delimited set of quoted strings or unquoted words followed by
+ * a close parenthesis.
+ *
+ * @param reader
+ * The string representation of the definition.
+ * @return The "extra" parameter value that was read.
+ * @throws DecodeException
+ * If a problem occurs while attempting to read the value.
+ */
+ static List<String> readExtensions(SubstringReader reader)
+ throws DecodeException
+ {
+ int length = 0;
+ List<String> values;
+
+ // Skip over any leading spaces.
+ reader.skipWhitespaces();
+ reader.mark();
+
+ try
+ {
+ // Look at the next character. If it is a quote, then parse until
+ // the next quote and end. If it is an open parenthesis, then
+ // parse individual values until the close parenthesis and end.
+ // Otherwise, parse until the next space and end.
+ char c = reader.read();
+ if (c == '\'')
+ {
+ reader.mark();
+ // Parse until the closing quote.
+ while (reader.read() != '\'')
+ {
+ length++;
+ }
+
+ reader.reset();
+ values = Collections.singletonList(reader.read(length));
+ reader.read();
+ }
+ else if (c == '(')
+ {
+ // Skip over any leading spaces;
+ reader.skipWhitespaces();
+ reader.mark();
+
+ c = reader.read();
+ if (c == ')')
+ {
+ values = Collections.emptyList();
+ }
+ else
+ {
+ values = new ArrayList<String>();
+ do
+ {
+ reader.reset();
+ values.add(readQuotedString(reader));
+ reader.skipWhitespaces();
+ reader.mark();
+ }
+ while (reader.read() != ')');
+ }
+ }
+ else
+ {
+ // Parse until the next space.
+ do
+ {
+ length++;
+ }
+ while (reader.read() != ' ');
+
+ reader.reset();
+ values = Collections.singletonList(reader.read(length));
+ }
+
+ return values;
+ }
+ catch (final StringIndexOutOfBoundsException e)
+ {
+ final Message message = ERR_ATTR_SYNTAX_TRUNCATED_VALUE.get();
+ throw DecodeException.error(message);
+ }
+ }
+
+
+
+ static List<String> readNameDescriptors(SubstringReader reader)
+ throws DecodeException
+ {
+ int length = 0;
+ List<String> values;
+
+ // Skip over any spaces at the beginning of the value.
+ reader.skipWhitespaces();
+
+ try
+ {
+ char c = reader.read();
+ if (c == '\'')
+ {
+ reader.mark();
+ // Parse until the closing quote.
+ while (reader.read() != '\'')
+ {
+ length++;
+ }
+
+ reader.reset();
+ values = Collections.singletonList(reader.read(length));
+ reader.read();
+ }
+ else if (c == '(')
+ {
+ // Skip over any leading spaces;
+ reader.skipWhitespaces();
+ reader.mark();
+
+ c = reader.read();
+ if (c == ')')
+ {
+ values = Collections.emptyList();
+ }
+ else
+ {
+ values = new LinkedList<String>();
+ do
+ {
+ reader.reset();
+ values.add(readQuotedDescriptor(reader));
+ reader.skipWhitespaces();
+ reader.mark();
+ }
+ while (reader.read() != ')');
+ }
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_ILLEGAL_CHAR_IN_STRING_OID.get(String
+ .valueOf(c), reader.pos() - 1);
+ throw DecodeException.error(message);
+ }
+
+ return values;
+ }
+ catch (final StringIndexOutOfBoundsException e)
+ {
+ final Message message = ERR_ATTR_SYNTAX_TRUNCATED_VALUE.get();
+ throw DecodeException.error(message);
+ }
+ }
+
+
+
+ /**
+ * Reads the next OID from the definition, skipping over any leading
+ * spaces.
+ *
+ * @param reader
+ * The string representation of the definition.
+ * @return The OID read from the definition.
+ * @throws DecodeException
+ * If a problem is encountered while reading the token name.
+ */
+ static String readNumericOID(SubstringReader reader)
+ throws DecodeException
+ {
+ // This must be a numeric OID. In that case, we will accept
+ // only digits and periods, but not consecutive periods.
+ boolean lastWasPeriod = false;
+ int length = 0;
+
+ // Skip over any spaces at the beginning of the value.
+ reader.skipWhitespaces();
+ reader.mark();
+
+ try
+ {
+ char c;
+ while ((c = reader.read()) != ' ' && c != '\'')
+ {
+ if (c == '.')
+ {
+ if (lastWasPeriod)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_OID_CONSECUTIVE_PERIODS.get(reader
+ .getString(), reader.pos() - 1);
+ throw DecodeException.error(message);
+ }
+ else
+ {
+ lastWasPeriod = true;
+ }
+ }
+ else if (!isDigit(c))
+ {
+ // Technically, this must be an illegal character. However, it
+ // is possible that someone just got sloppy and did not
+ // include a space between the name/OID and a closing
+ // parenthesis. In that case, we'll assume it's the end of the
+ // value.
+ if (c == ')')
+ {
+ break;
+ }
+
+ // This must have been an illegal character.
+ final Message message =
+ ERR_ATTR_SYNTAX_OID_ILLEGAL_CHARACTER.get(reader
+ .getString(), reader.pos() - 1);
+ throw DecodeException.error(message);
+ }
+ else
+ {
+ lastWasPeriod = false;
+ }
+ length++;
+ }
+
+ if (length == 0)
+ {
+ final Message message = ERR_ATTR_SYNTAX_OID_NO_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ reader.reset();
+
+ return reader.read(length);
+ }
+ catch (final StringIndexOutOfBoundsException e)
+ {
+ final Message message = ERR_ATTR_SYNTAX_TRUNCATED_VALUE.get();
+ throw DecodeException.error(message);
+ }
+ }
+
+
+
+ /**
+ * Reads the attribute description or numeric OID, skipping over any
+ * leading or trailing spaces.
+ *
+ * @param reader
+ * The string representation of the definition.
+ * @return The attribute description or numeric OID read from the
+ * definition.
+ * @throws DecodeException
+ * If a problem is encountered while reading the name or
+ * OID.
+ */
+ static String readOID(SubstringReader reader) throws DecodeException
+ {
+ int length = 1;
+ boolean enclosingQuote = false;
+ String oid;
+
+ // Skip over any spaces at the beginning of the value.
+ reader.skipWhitespaces();
+ reader.mark();
+
+ try
+ {
+ // The next character must be either numeric (for an OID) or
+ // alphabetic (for an attribute description).
+ char c = reader.read();
+ if (c == '\'')
+ {
+ enclosingQuote = true;
+ reader.mark();
+ c = reader.read();
+ }
+ if (isDigit(c))
+ {
+ reader.reset();
+ oid = readNumericOID(reader);
+ }
+
+ else if (isAlpha(c))
+ {
+ // This must be an attribute description. In this case, we will
+ // only accept alphabetic characters, numeric digits, and the
+ // hyphen.
+ while (reader.remaining() > 0 && (c = reader.read()) != ' '
+ && c != ')' && !(c == '\'' && enclosingQuote))
+ {
+ if (length == 0 && !isAlpha(c))
+ {
+ // This is an illegal character.
+ final Message message =
+ ERR_ATTR_SYNTAX_ILLEGAL_CHAR_IN_STRING_OID.get(String
+ .valueOf(c), reader.pos() - 1);
+ throw DecodeException.error(message);
+ }
+
+ if (!isAlpha(c) && !isDigit(c) && c != '-' && c != '.'
+ && c != '_')
+ {
+ // This is an illegal character.
+ final Message message =
+ ERR_ATTR_SYNTAX_ILLEGAL_CHAR_IN_STRING_OID.get(String
+ .valueOf(c), reader.pos() - 1);
+ throw DecodeException.error(message);
+ }
+
+ length++;
+ }
+
+ reader.reset();
+
+ // Return the position of the first non-space character after
+ // the token.
+ oid = reader.read(length);
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_ILLEGAL_CHAR_IN_STRING_OID.get(String
+ .valueOf(c), reader.pos() - 1);
+ throw DecodeException.error(message);
+ }
+
+ if (enclosingQuote)
+ {
+ reader.read();
+ }
+ return oid;
+ }
+ catch (final StringIndexOutOfBoundsException e)
+ {
+ final Message message = ERR_ATTR_SYNTAX_TRUNCATED_VALUE.get();
+ throw DecodeException.error(message);
+ }
+ }
+
+
+
+ /**
+ * Reads the next OID from the definition, skipping over any leading
+ * spaces. The OID may be followed by a integer length in brackets.
+ *
+ * @param reader
+ * The string representation of the definition.
+ * @return The OID read from the definition.
+ * @throws DecodeException
+ * If a problem is encountered while reading the token name.
+ */
+ static String readOIDLen(SubstringReader reader)
+ throws DecodeException
+ {
+ int length = 1;
+ boolean enclosingQuote = false;
+
+ // Skip over any spaces at the beginning of the value.
+ reader.skipWhitespaces();
+ reader.mark();
+
+ try
+ {
+ // The next character must be either numeric (for an OID) or
+ // alphabetic (for an attribute description).
+ char c = reader.read();
+ if (c == '\'')
+ {
+ enclosingQuote = true;
+ reader.mark();
+ c = reader.read();
+ }
+ if (isDigit(c))
+ {
+ boolean lastWasPeriod = false;
+ while ((c = reader.read()) != ' ' && c != '{'
+ && !(c == '\'' && enclosingQuote))
+ {
+ if (c == '.')
+ {
+ if (lastWasPeriod)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_OID_CONSECUTIVE_PERIODS.get(reader
+ .getString(), reader.pos() - 1);
+ throw DecodeException.error(message);
+ }
+ else
+ {
+ lastWasPeriod = true;
+ }
+ }
+ else if (!isDigit(c))
+ {
+ // Technically, this must be an illegal character. However,
+ // it is possible that someone just got sloppy and did not
+ // include a space between the name/OID and a closing
+ // parenthesis. In that case, we'll assume it's the end of
+ // the value.
+ if (c == ')')
+ {
+ break;
+ }
+
+ // This must have been an illegal character.
+ final Message message =
+ ERR_ATTR_SYNTAX_OID_ILLEGAL_CHARACTER.get(reader
+ .getString(), reader.pos() - 1);
+ throw DecodeException.error(message);
+ }
+ else
+ {
+ lastWasPeriod = false;
+ }
+ length++;
+ }
+
+ if (length == 0)
+ {
+ final Message message = ERR_ATTR_SYNTAX_OID_NO_VALUE.get();
+ throw DecodeException.error(message);
+ }
+ }
+
+ else if (isAlpha(c))
+ {
+ // This must be an attribute description. In this case, we will
+ // only accept alphabetic characters, numeric digits, and the
+ // hyphen.
+ while ((c = reader.read()) != ' ' && c != ')' && c != '{'
+ && !(c == '\'' && enclosingQuote))
+ {
+ if (length == 0 && !isAlpha(c))
+ {
+ // This is an illegal character.
+ final Message message =
+ ERR_ATTR_SYNTAX_ILLEGAL_CHAR_IN_STRING_OID.get(String
+ .valueOf(c), reader.pos() - 1);
+ throw DecodeException.error(message);
+ }
+
+ if (!isAlpha(c) && !isDigit(c) && c != '-' && c != '.'
+ && c != '_')
+ {
+ // This is an illegal character.
+ final Message message =
+ ERR_ATTR_SYNTAX_ILLEGAL_CHAR_IN_STRING_OID.get(String
+ .valueOf(c), reader.pos() - 1);
+ throw DecodeException.error(message);
+ }
+
+ length++;
+ }
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_ILLEGAL_CHAR_IN_STRING_OID.get(String
+ .valueOf(c), reader.pos() - 1);
+ throw DecodeException.error(message);
+ }
+
+ reader.reset();
+
+ // Return the position of the first non-space character after the
+ // token.
+ final String oid = reader.read(length);
+
+ reader.mark();
+ if ((c = reader.read()) == '{')
+ {
+ reader.mark();
+ // The only thing we'll allow here will be numeric digits and
+ // the closing curly brace.
+ while ((c = reader.read()) != '}')
+ {
+ if (!isDigit(c))
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_OID_ILLEGAL_CHARACTER.get(reader
+ .getString(), reader.pos() - 1);
+ throw DecodeException.error(message);
+ }
+ }
+ }
+ else if (c == '\'')
+ {
+ reader.mark();
+ }
+ else
+ {
+ reader.reset();
+ }
+
+ return oid;
+ }
+ catch (final StringIndexOutOfBoundsException e)
+ {
+ final Message message = ERR_ATTR_SYNTAX_TRUNCATED_VALUE.get();
+ throw DecodeException.error(message);
+ }
+ }
+
+
+
+ static Set<String> readOIDs(SubstringReader reader)
+ throws DecodeException
+ {
+ Set<String> values;
+
+ // Skip over any spaces at the beginning of the value.
+ reader.skipWhitespaces();
+ reader.mark();
+
+ try
+ {
+ final char c = reader.read();
+ if (c == '(')
+ {
+ values = new HashSet<String>();
+
+ do
+ {
+ values.add(readOID(reader));
+
+ // Skip over any trailing spaces;
+ reader.skipWhitespaces();
+ }
+ while (reader.read() != ')');
+ }
+ else
+ {
+ reader.reset();
+ values = Collections.singleton(readOID(reader));
+ }
+
+ return values;
+ }
+ catch (final StringIndexOutOfBoundsException e)
+ {
+ final Message message = ERR_ATTR_SYNTAX_TRUNCATED_VALUE.get();
+ throw DecodeException.error(message);
+ }
+ }
+
+
+
+ /**
+ * Reads the value of a string enclosed in single quotes, skipping
+ * over the quotes and any leading spaces.
+ *
+ * @param reader
+ * The string representation of the definition.
+ * @return The string value read from the definition.
+ * @throws DecodeException
+ * If a problem is encountered while reading the quoted
+ * string.
+ */
+ static String readQuotedDescriptor(SubstringReader reader)
+ throws DecodeException
+ {
+ int length = 0;
+
+ // Skip over any spaces at the beginning of the value.
+ reader.skipWhitespaces();
+
+ try
+ {
+ // The next character must be a single quote.
+ char c = reader.read();
+ if (c != '\'')
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_EXPECTED_QUOTE_AT_POS.get(reader.pos() - 1,
+ String.valueOf(c));
+ throw DecodeException.error(message);
+ }
+
+ // Read until we find the closing quote.
+ reader.mark();
+ while ((c = reader.read()) != '\'')
+ {
+ if (length == 0 && !isAlpha(c))
+ {
+ // This is an illegal character.
+ final Message message =
+ ERR_ATTR_SYNTAX_ILLEGAL_CHAR_IN_STRING_OID.get(String
+ .valueOf(c), reader.pos() - 1);
+ throw DecodeException.error(message);
+ }
+
+ if (!isAlpha(c) && !isDigit(c) && c != '-' && c != '_'
+ && c != '.')
+ {
+ // This is an illegal character.
+ final Message message =
+ ERR_ATTR_SYNTAX_ILLEGAL_CHAR_IN_STRING_OID.get(String
+ .valueOf(c), reader.pos() - 1);
+ throw DecodeException.error(message);
+ }
+
+ length++;
+ }
+
+ reader.reset();
+
+ final String descr = reader.read(length);
+ reader.read();
+ return descr;
+ }
+ catch (final StringIndexOutOfBoundsException e)
+ {
+ final Message message = ERR_ATTR_SYNTAX_TRUNCATED_VALUE.get();
+ throw DecodeException.error(message);
+ }
+ }
+
+
+
+ /**
+ * Reads the value of a string enclosed in single quotes, skipping
+ * over the quotes and any leading spaces.
+ *
+ * @param reader
+ * The string representation of the definition.
+ * @return The string value read from the definition.
+ * @throws DecodeException
+ * If a problem is encountered while reading the quoted
+ * string.
+ */
+ static String readQuotedString(SubstringReader reader)
+ throws DecodeException
+ {
+ int length = 0;
+
+ // Skip over any spaces at the beginning of the value.
+ reader.skipWhitespaces();
+
+ try
+ {
+ // The next character must be a single quote.
+ final char c = reader.read();
+ if (c != '\'')
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_EXPECTED_QUOTE_AT_POS.get(reader.pos() - 1,
+ String.valueOf(c));
+ throw DecodeException.error(message);
+ }
+
+ // Read until we find the closing quote.
+ reader.mark();
+ while (reader.read() != '\'')
+ {
+ length++;
+ }
+
+ reader.reset();
+
+ final String str = reader.read(length);
+ reader.read();
+ return str;
+ }
+ catch (final StringIndexOutOfBoundsException e)
+ {
+ final Message message = ERR_ATTR_SYNTAX_TRUNCATED_VALUE.get();
+ throw DecodeException.error(message);
+ }
+ }
+
+
+
+ /**
+ * Reads the next ruleid from the definition, skipping over any
+ * leading spaces.
+ *
+ * @param reader
+ * The string representation of the definition.
+ * @return The ruleid read from the definition.
+ * @throws DecodeException
+ * If a problem is encountered while reading the token name.
+ */
+ static Integer readRuleID(SubstringReader reader)
+ throws DecodeException
+ {
+ // This must be a ruleid. In that case, we will accept
+ // only digits.
+ int length = 0;
+
+ // Skip over any spaces at the beginning of the value.
+ reader.skipWhitespaces();
+ reader.mark();
+
+ try
+ {
+ while (reader.read() != ' ')
+ {
+ length++;
+ }
+
+ if (length == 0)
+ {
+ final Message message = ERR_ATTR_SYNTAX_RULE_ID_NO_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ reader.reset();
+ final String ruleID = reader.read(length);
+
+ try
+ {
+ return Integer.valueOf(ruleID);
+ }
+ catch (final NumberFormatException e)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_RULE_ID_INVALID.get(ruleID);
+ throw DecodeException.error(message);
+ }
+ }
+ catch (final StringIndexOutOfBoundsException e)
+ {
+ final Message message = ERR_ATTR_SYNTAX_TRUNCATED_VALUE.get();
+ throw DecodeException.error(message);
+ }
+ }
+
+
+
+ static Set<Integer> readRuleIDs(SubstringReader reader)
+ throws DecodeException
+ {
+ Set<Integer> values;
+
+ // Skip over any spaces at the beginning of the value.
+ reader.skipWhitespaces();
+ reader.mark();
+
+ try
+ {
+ final char c = reader.read();
+ if (c == '(')
+ {
+ values = new HashSet<Integer>();
+
+ do
+ {
+ values.add(readRuleID(reader));
+
+ // Skip over any trailing spaces;
+ reader.skipWhitespaces();
+ }
+ while (reader.read() != ')');
+ }
+ else
+ {
+ reader.reset();
+ values = Collections.singleton(readRuleID(reader));
+ }
+
+ return values;
+ }
+ catch (final StringIndexOutOfBoundsException e)
+ {
+ final Message message = ERR_ATTR_SYNTAX_TRUNCATED_VALUE.get();
+ throw DecodeException.error(message);
+ }
+ }
+
+
+
+ /**
+ * Reads the next token name from the definition, skipping over any
+ * leading or trailing spaces or <code>null</code> if there are no
+ * moretokens to read.
+ *
+ * @param reader
+ * The string representation of the definition.
+ * @return The token name read from the definition or
+ * <code>null</code> .
+ * @throws DecodeException
+ * If a problem is encountered while reading the token name.
+ */
+ static String readTokenName(SubstringReader reader)
+ throws DecodeException
+ {
+ String token = null;
+ int length = 0;
+ // Skip over any spaces at the beginning of the value.
+ reader.skipWhitespaces();
+ reader.mark();
+
+ try
+ {
+ // Read until we find the next space.
+ char c;
+ while ((c = reader.read()) != ' ' && c != ')')
+ {
+ length++;
+ }
+
+ if (length > 0)
+ {
+ reader.reset();
+ token = reader.read(length);
+ }
+
+ // Skip over any trailing spaces after the value.
+ reader.skipWhitespaces();
+
+ if (token == null && reader.remaining() > 0)
+ {
+ reader.reset();
+ final Message message =
+ ERR_ATTR_SYNTAX_UNEXPECTED_CLOSE_PARENTHESIS.get(length);
+ throw DecodeException.error(message);
+ }
+
+ return token;
+ }
+ catch (final StringIndexOutOfBoundsException e)
+ {
+ final Message message = ERR_ATTR_SYNTAX_TRUNCATED_VALUE.get();
+ throw DecodeException.error(message);
+ }
+ }
+
+
+
+ // Prevent instantiation.
+ private SchemaUtils()
+ {
+ // Nothing to do.
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/SubstringAssertionSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/SubstringAssertionSyntaxImpl.java
new file mode 100644
index 0000000..0f9e446
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/SubstringAssertionSyntaxImpl.java
@@ -0,0 +1,150 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.EMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_SUBSTRING_ASSERTION_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.messages.SchemaMessages;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class defines the substring assertion attribute syntax, which
+ * contains one or more substring components, as used in a substring
+ * search filter. For the purposes of matching, it will be treated like
+ * a Directory String syntax except that approximate matching will not
+ * be allowed.
+ */
+final class SubstringAssertionSyntaxImpl extends AbstractSyntaxImpl
+{
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_SUBSTRING_ASSERTION_NAME;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_CASE_IGNORE_OID;
+ }
+
+
+
+ @Override
+ public String getSubstringMatchingRule()
+ {
+ return SMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // Get the string representation of the value and check its length.
+ // A zero-length value is acceptable. A one-length value is
+ // acceptable as long as it is not an asterisk. For all other
+ // lengths, just ensure that there are no consecutive wildcards.
+ final String valueString = value.toString();
+ final int valueLength = valueString.length();
+ if (valueLength == 0)
+ {
+ return true;
+ }
+ else if (valueLength == 1)
+ {
+ if (valueString.charAt(0) == '*')
+ {
+ invalidReason
+ .append(SchemaMessages.WARN_ATTR_SYNTAX_SUBSTRING_ONLY_WILDCARD
+ .get());
+
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+ else
+ {
+ for (int i = 1; i < valueLength; i++)
+ {
+ if (valueString.charAt(i) == '*'
+ && valueString.charAt(i - 1) == '*')
+ {
+ invalidReason
+ .append(SchemaMessages.WARN_ATTR_SYNTAX_SUBSTRING_CONSECUTIVE_WILDCARDS
+ .get(valueString, i));
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/SupportedAlgorithmSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/SupportedAlgorithmSyntaxImpl.java
new file mode 100644
index 0000000..732fd66
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/SupportedAlgorithmSyntaxImpl.java
@@ -0,0 +1,108 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.EMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_SUPPORTED_ALGORITHM_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the supported algorithm attribute syntax. This
+ * should be restricted to holding only X.509 supported algorithms, but
+ * we will accept any set of bytes. It will be treated much like the
+ * octet string attribute syntax.
+ */
+final class SupportedAlgorithmSyntaxImpl extends AbstractSyntaxImpl
+{
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_OCTET_STRING_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_SUPPORTED_ALGORITHM_NAME;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_OCTET_STRING_OID;
+ }
+
+
+
+ @Override
+ public boolean isBEREncodingRequired()
+ {
+ return true;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // All values will be acceptable for the supported algorithm syntax.
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/Syntax.java b/sdk/src/org/opends/sdk/schema/Syntax.java
new file mode 100644
index 0000000..efb0392
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/Syntax.java
@@ -0,0 +1,439 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class defines a data structure for storing and interacting with
+ * an LDAP syntaxes, which constrain the structure of attribute values
+ * stored in an LDAP directory, and determine the representation of
+ * attribute and assertion values transferred in the LDAP protocol.
+ * <p>
+ * Syntax implementations must extend the
+ * <code>SyntaxImplementation</code> class so they can be used by OpenDS
+ * to validate attribute values.
+ * <p>
+ * Where ordered sets of names, or extra properties are provided, the
+ * ordering will be preserved when the associated fields are accessed
+ * via their getters or via the {@link #toString()} methods.
+ */
+public final class Syntax extends SchemaElement
+{
+ private final String oid;
+ private final String definition;
+ private MatchingRule equalityMatchingRule;
+ private MatchingRule orderingMatchingRule;
+ private MatchingRule substringMatchingRule;
+ private MatchingRule approximateMatchingRule;
+ private Schema schema;
+ private SyntaxImpl impl;
+
+
+
+ Syntax(String oid, String description,
+ Map<String, List<String>> extraProperties, String definition,
+ SyntaxImpl implementation)
+ {
+ super(description, extraProperties);
+
+ Validator.ensureNotNull(oid);
+ this.oid = oid;
+
+ if (definition != null)
+ {
+ this.definition = definition;
+ }
+ else
+ {
+ this.definition = buildDefinition();
+ }
+ this.impl = implementation;
+ }
+
+ Syntax(String oid)
+ {
+ super("", Collections.singletonMap("X-SUBST",
+ Collections.singletonList(Schema.getDefaultSyntax().getOID())));
+
+ Validator.ensureNotNull(oid);
+ this.oid = oid;
+ this.definition = buildDefinition();
+ this.impl = Schema.getDefaultSyntax().impl;
+ }
+
+
+
+ /**
+ * Retrieves the default approximate matching rule that will be used
+ * for attributes with this syntax.
+ *
+ * @return The default approximate matching rule that will be used for
+ * attributes with this syntax, or {@code null} if approximate
+ * matches will not be allowed for this type by default.
+ */
+ public MatchingRule getApproximateMatchingRule()
+ {
+ return approximateMatchingRule;
+ }
+
+
+
+ /**
+ * Retrieves the default equality matching rule that will be used for
+ * attributes with this syntax.
+ *
+ * @return The default equality matching rule that will be used for
+ * attributes with this syntax, or {@code null} if equality
+ * matches will not be allowed for this type by default.
+ */
+ public MatchingRule getEqualityMatchingRule()
+ {
+ return equalityMatchingRule;
+ }
+
+
+
+ /**
+ * Retrieves the OID for this attribute syntax.
+ *
+ * @return The OID for this attribute syntax.
+ */
+ public String getOID()
+ {
+ return oid;
+ }
+
+
+
+ /**
+ * Retrieves the default ordering matching rule that will be used for
+ * attributes with this syntax.
+ *
+ * @return The default ordering matching rule that will be used for
+ * attributes with this syntax, or {@code null} if ordering
+ * matches will not be allowed for this type by default.
+ */
+ public MatchingRule getOrderingMatchingRule()
+ {
+ return orderingMatchingRule;
+ }
+
+
+
+ /**
+ * Retrieves the default substring matching rule that will be used for
+ * attributes with this syntax.
+ *
+ * @return The default substring matching rule that will be used for
+ * attributes with this syntax, or {@code null} if substring
+ * matches will not be allowed for this type by default.
+ */
+ public MatchingRule getSubstringMatchingRule()
+ {
+ return substringMatchingRule;
+ }
+
+
+
+ /**
+ * Retrieves the hash code for this schema element. It will be
+ * calculated as the sum of the characters in the OID.
+ *
+ * @return The hash code for this attribute syntax.
+ */
+ @Override
+ public int hashCode()
+ {
+ return getOID().hashCode();
+ }
+
+
+
+ /**
+ * Indicates whether this attribute syntax requires that values must
+ * be encoded using the Basic Encoding Rules (BER) used by X.500
+ * directories and always include the {@code binary} attribute
+ * description option.
+ *
+ * @return {@code true} this attribute syntax requires that values
+ * must be BER encoded and always include the {@code binary}
+ * attribute description option, or {@code false} if not.
+ * @see <a href="http://tools.ietf.org/html/rfc4522">RFC 4522 -
+ * Lightweight Directory Access Protocol (LDAP): The Binary
+ * Encoding Option </a>
+ */
+ public boolean isBEREncodingRequired()
+ {
+ return impl.isBEREncodingRequired();
+ }
+
+
+
+ /**
+ * Indicates whether this attribute syntax would likely be a human
+ * readable string.
+ *
+ * @return {@code true} if this attribute syntax would likely be a
+ * human readable string or {@code false} if not.
+ */
+ public boolean isHumanReadable()
+ {
+ return impl.isHumanReadable();
+ }
+
+
+
+ /**
+ * Retrieves a string representation of this attribute syntax in the
+ * format defined in RFC 2252.
+ *
+ * @return A string representation of this attribute syntax in the
+ * format defined in RFC 2252.
+ */
+ @Override
+ public String toString()
+ {
+ return definition;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return {@code true} if the provided value is acceptable for use
+ * with this syntax, or {@code false} if not.
+ */
+ public boolean valueIsAcceptable(ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ return impl.valueIsAcceptable(schema, value, invalidReason);
+ }
+
+
+
+ Syntax duplicate()
+ {
+ return new Syntax(oid, description, extraProperties, definition,
+ impl);
+ }
+
+
+
+ @Override
+ void toStringContent(StringBuilder buffer)
+ {
+ buffer.append(oid);
+
+ if (description != null && description.length() > 0)
+ {
+ buffer.append(" DESC '");
+ buffer.append(description);
+ buffer.append("'");
+ }
+ }
+
+
+
+ @Override
+ void validate(List<Message> warnings, Schema schema)
+ throws SchemaException
+ {
+ this.schema = schema;
+ if (impl == null)
+ {
+ // See if we need to override the implementation of the syntax
+ for (final Map.Entry<String, List<String>> property : extraProperties
+ .entrySet())
+ {
+ // Enums are handled in the schema builder.
+ if (property.getKey().equalsIgnoreCase("x-subst"))
+ {
+ /**
+ * One unimplemented syntax can be substituted by another
+ * defined syntax. A substitution syntax is an
+ * LDAPSyntaxDescriptionSyntax with X-SUBST extension.
+ */
+ final Iterator<String> values =
+ property.getValue().iterator();
+ if (values.hasNext())
+ {
+ final String value = values.next();
+ if (value.equals(oid))
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_CYCLIC_SUB_SYNTAX.get(oid);
+ throw new SchemaException(message);
+ }
+ if (!schema.hasSyntax(value))
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_UNKNOWN_SUB_SYNTAX.get(oid, value);
+ throw new SchemaException(message);
+ }
+ final Syntax subSyntax = schema.getSyntax(value);
+ if (subSyntax.impl == null)
+ {
+ // The substitution syntax was never validated.
+ subSyntax.validate(warnings, schema);
+ }
+ impl = subSyntax.impl;
+ }
+ }
+ else if (property.getKey().equalsIgnoreCase("x-pattern"))
+ {
+ final Iterator<String> values =
+ property.getValue().iterator();
+ if (values.hasNext())
+ {
+ final String value = values.next();
+ try
+ {
+ final Pattern pattern = Pattern.compile(value);
+ impl = new RegexSyntaxImpl(pattern);
+ }
+ catch (final Exception e)
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_LDAPSYNTAX_REGEX_INVALID_PATTERN
+ .get(oid, value);
+ throw new SchemaException(message);
+ }
+ }
+ }
+ }
+
+ // Try to find an implementation in the core schema
+ if (impl == null && Schema.getDefaultSchema().hasSyntax(oid))
+ {
+ impl = Schema.getDefaultSchema().getSyntax(oid).impl;
+ }
+ if (impl == null && Schema.getCoreSchema().hasSyntax(oid))
+ {
+ impl = Schema.getCoreSchema().getSyntax(oid).impl;
+ }
+
+ if (impl == null)
+ {
+ impl = Schema.getDefaultSyntax().impl;
+ final Message message =
+ WARN_ATTR_SYNTAX_NOT_IMPLEMENTED.get(oid, Schema
+ .getDefaultSyntax().getOID());
+ warnings.add(message);
+ }
+ }
+
+ // Get references to the default matching rules. It will be ok
+ // if we can't find some. Just warn.
+ if (impl.getEqualityMatchingRule() != null)
+ {
+ if (schema.hasMatchingRule(impl.getEqualityMatchingRule()))
+ {
+ equalityMatchingRule =
+ schema.getMatchingRule(impl.getEqualityMatchingRule());
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_UNKNOWN_EQUALITY_MATCHING_RULE.get(impl
+ .getEqualityMatchingRule(), impl.getName());
+ warnings.add(message);
+ }
+ }
+
+ if (impl.getOrderingMatchingRule() != null)
+ {
+ if (schema.hasMatchingRule(impl.getOrderingMatchingRule()))
+ {
+ orderingMatchingRule =
+ schema.getMatchingRule(impl.getOrderingMatchingRule());
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_UNKNOWN_ORDERING_MATCHING_RULE.get(impl
+ .getOrderingMatchingRule(), impl.getName());
+ warnings.add(message);
+ }
+ }
+
+ if (impl.getSubstringMatchingRule() != null)
+ {
+ if (schema.hasMatchingRule(impl.getSubstringMatchingRule()))
+ {
+ substringMatchingRule =
+ schema.getMatchingRule(impl.getSubstringMatchingRule());
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_UNKNOWN_SUBSTRING_MATCHING_RULE.get(impl
+ .getSubstringMatchingRule(), impl.getName());
+ warnings.add(message);
+ }
+ }
+
+ if (impl.getApproximateMatchingRule() != null)
+ {
+ if (schema.hasMatchingRule(impl.getApproximateMatchingRule()))
+ {
+ approximateMatchingRule =
+ schema.getMatchingRule(impl.getApproximateMatchingRule());
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_UNKNOWN_APPROXIMATE_MATCHING_RULE.get(impl
+ .getApproximateMatchingRule(), impl.getName());
+ warnings.add(message);
+ }
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/SyntaxImpl.java b/sdk/src/org/opends/sdk/schema/SyntaxImpl.java
new file mode 100644
index 0000000..a232461
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/SyntaxImpl.java
@@ -0,0 +1,144 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class defines the set of methods and structures that must be
+ * implemented to define a new attribute syntax.
+ */
+public interface SyntaxImpl
+{
+ /**
+ * Retrieves the default approximate matching rule that will be used
+ * for attributes with this syntax.
+ *
+ * @return The default approximate matching rule that will be used for
+ * attributes with this syntax, or {@code null} if approximate
+ * matches will not be allowed for this type by default.
+ */
+ public String getApproximateMatchingRule();
+
+
+
+ /**
+ * Retrieves the default equality matching rule that will be used for
+ * attributes with this syntax.
+ *
+ * @return The default equality matching rule that will be used for
+ * attributes with this syntax, or {@code null} if equality
+ * matches will not be allowed for this type by default.
+ */
+ public String getEqualityMatchingRule();
+
+
+
+ /**
+ * Retrieves the common name for this attribute syntax.
+ *
+ * @return The common name for this attribute syntax.
+ */
+ public String getName();
+
+
+
+ /**
+ * Retrieves the default ordering matching rule that will be used for
+ * attributes with this syntax.
+ *
+ * @return The default ordering matching rule that will be used for
+ * attributes with this syntax, or {@code null} if ordering
+ * matches will not be allowed for this type by default.
+ */
+ public String getOrderingMatchingRule();
+
+
+
+ /**
+ * Retrieves the default substring matching rule that will be used for
+ * attributes with this syntax.
+ *
+ * @return The default substring matching rule that will be used for
+ * attributes with this syntax, or {@code null} if substring
+ * matches will not be allowed for this type by default.
+ */
+ public String getSubstringMatchingRule();
+
+
+
+ /**
+ * Indicates whether this attribute syntax requires that values must
+ * be encoded using the Basic Encoding Rules (BER) used by X.500
+ * directories and always include the {@code binary} attribute
+ * description option.
+ *
+ * @return {@code true} this attribute syntax requires that values
+ * must be BER encoded and always include the {@code binary}
+ * attribute description option, or {@code false} if not.
+ * @see <a href="http://tools.ietf.org/html/rfc4522">RFC 4522 -
+ * Lightweight Directory Access Protocol (LDAP): The Binary
+ * Encoding Option </a>
+ */
+ public boolean isBEREncodingRequired();
+
+
+
+ /**
+ * Indicates whether this attribute syntax would likely be a human
+ * readable string.
+ *
+ * @return {@code true} if this attribute syntax would likely be a
+ * human readable string or {@code false} if not.
+ */
+ boolean isHumanReadable();
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return {@code true} if the provided value is acceptable for use
+ * with this syntax, or {@code false} if not.
+ */
+ boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason);
+}
diff --git a/sdk/src/org/opends/sdk/schema/TelephoneNumberEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/TelephoneNumberEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..9c33ad0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/TelephoneNumberEqualityMatchingRuleImpl.java
@@ -0,0 +1,67 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * This class implements the telephoneNumberMatch matching rule defined
+ * in X.520 and referenced in RFC 2252. Note that although the
+ * specification calls for a very rigorous format, this is widely
+ * ignored so this matching will compare only numeric digits and strip
+ * out everything else.
+ */
+final class TelephoneNumberEqualityMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value)
+ {
+ final String valueString = value.toString();
+ final int valueLength = valueString.length();
+ final StringBuilder buffer = new StringBuilder(valueLength);
+
+ // Iterate through the characters in the value and filter out
+ // everything that isn't a digit.
+ for (int i = 0; i < valueLength; i++)
+ {
+ final char c = valueString.charAt(i);
+ if (StaticUtils.isDigit(c))
+ {
+ buffer.append(c);
+ }
+ }
+
+ return ByteString.valueOf(buffer.toString());
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/TelephoneNumberSubstringMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/TelephoneNumberSubstringMatchingRuleImpl.java
new file mode 100644
index 0000000..3d52f58
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/TelephoneNumberSubstringMatchingRuleImpl.java
@@ -0,0 +1,67 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * This class implements the telephoneNumberSubstringsMatch matching
+ * rule defined in X.520 and referenced in RFC 2252. Note that although
+ * the specification calls for a very rigorous format, this is widely
+ * ignored so this matching will compare only numeric digits and strip
+ * out everything else.
+ */
+final class TelephoneNumberSubstringMatchingRuleImpl extends
+ AbstractSubstringMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value)
+ {
+ final String valueString = value.toString();
+ final int valueLength = valueString.length();
+ final StringBuilder buffer = new StringBuilder(valueLength);
+
+ // Iterate through the characters in the value and filter out
+ // everything that isn't a digit.
+ for (int i = 0; i < valueLength; i++)
+ {
+ final char c = valueString.charAt(i);
+ if (StaticUtils.isDigit(c))
+ {
+ buffer.append(c);
+ }
+ }
+
+ return ByteString.valueOf(buffer.toString());
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/TelephoneNumberSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/TelephoneNumberSyntaxImpl.java
new file mode 100644
index 0000000..37aad35
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/TelephoneNumberSyntaxImpl.java
@@ -0,0 +1,203 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_TELEPHONE_EMPTY;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_TELEPHONE_ILLEGAL_CHAR;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_TELEPHONE_NO_DIGITS;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_TELEPHONE_NO_PLUS;
+import static org.opends.sdk.schema.SchemaConstants.EMR_TELEPHONE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_TELEPHONE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_TELEPHONE_NAME;
+import static org.opends.sdk.util.StaticUtils.isDigit;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the telephone number attribute syntax, which is
+ * defined in RFC 2252. Note that this can have two modes of operation,
+ * depending on its configuration. Most of the time, it will be very
+ * lenient when deciding what to accept, and will allow anything but
+ * only pay attention to the digits. However, it can also be configured
+ * in a "strict" mode, in which case it will only accept values in the
+ * E.123 international telephone number format.
+ */
+final class TelephoneNumberSyntaxImpl extends AbstractSyntaxImpl
+{
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_TELEPHONE_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_TELEPHONE_NAME;
+ }
+
+
+
+ @Override
+ public String getSubstringMatchingRule()
+ {
+ return SMR_TELEPHONE_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // No matter what, the value can't be empty or null.
+ String valueStr;
+ if (value == null
+ || (valueStr = value.toString().trim()).length() == 0)
+ {
+ invalidReason.append(ERR_ATTR_SYNTAX_TELEPHONE_EMPTY.get());
+ return false;
+ }
+
+ final int length = valueStr.length();
+
+ if (schema.getSchemaCompatOptions().isTelephoneNumberSyntaxStrict())
+ {
+ // If the value does not start with a plus sign, then that's not
+ // acceptable.
+ if (valueStr.charAt(0) != '+')
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_TELEPHONE_NO_PLUS.get(valueStr);
+ invalidReason.append(message);
+ return false;
+ }
+
+ // Iterate through the remaining characters in the value. There
+ // must be at least one digit, and it must contain only valid
+ // digits and separator characters.
+ boolean digitSeen = false;
+ for (int i = 1; i < length; i++)
+ {
+ final char c = valueStr.charAt(i);
+ if (isDigit(c))
+ {
+ digitSeen = true;
+ }
+ else if (!isSeparator(c))
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_TELEPHONE_ILLEGAL_CHAR.get(valueStr,
+ String.valueOf(c), i);
+ invalidReason.append(message);
+ return false;
+ }
+ }
+
+ if (!digitSeen)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_TELEPHONE_NO_DIGITS.get(valueStr);
+ invalidReason.append(message);
+ return false;
+ }
+
+ // If we've gotten here, then we'll consider it acceptable.
+ return true;
+ }
+ else
+ {
+ // If we are not in strict mode, then all non-empty values
+ // containing at least one digit will be acceptable.
+ for (int i = 0; i < length; i++)
+ {
+ if (isDigit(valueStr.charAt(i)))
+ {
+ return true;
+ }
+ }
+
+ // If we made it here, then we didn't find any digits.
+ final Message message =
+ ERR_ATTR_SYNTAX_TELEPHONE_NO_DIGITS.get(valueStr);
+ invalidReason.append(message);
+ return false;
+ }
+ }
+
+
+
+ /**
+ * Indicates whether the provided character is a valid separator for
+ * telephone number components when operating in strict mode.
+ *
+ * @param c
+ * The character for which to make the determination.
+ * @return <CODE>true</CODE> if the provided character is a valid
+ * separator, or <CODE>false</CODE> if it is not.
+ */
+ private boolean isSeparator(char c)
+ {
+ switch (c)
+ {
+ case ' ':
+ case '-':
+ return true;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/TeletexTerminalIdentifierSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/TeletexTerminalIdentifierSyntaxImpl.java
new file mode 100644
index 0000000..32f6fe5
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/TeletexTerminalIdentifierSyntaxImpl.java
@@ -0,0 +1,271 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.schema.SchemaConstants.EMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_TELETEX_TERM_ID_NAME;
+
+import java.util.HashSet;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the teletex terminal identifier attribute
+ * syntax, which contains a printable string (the terminal identifier)
+ * followed by zero or more parameters, which start with a dollar sign
+ * and are followed by a parameter name, a colon, and a value. The
+ * parameter value should consist of any string of bytes (the dollar
+ * sign and backslash must be escaped with a preceding backslash), and
+ * the parameter name must be one of the following strings:
+ * <UL>
+ * <LI>graphic</LI>
+ * <LI>control</LI>
+ * <LI>misc</LI>
+ * <LI>page</LI>
+ * <LI>private</LI>
+ * </UL>
+ */
+final class TeletexTerminalIdentifierSyntaxImpl extends
+ AbstractSyntaxImpl
+{
+ /**
+ * The set of allowed fax parameter values, formatted entirely in
+ * lowercase characters.
+ */
+ private static final HashSet<String> ALLOWED_TTX_PARAMETERS =
+ new HashSet<String>(5);
+
+ static
+ {
+ ALLOWED_TTX_PARAMETERS.add("graphic");
+ ALLOWED_TTX_PARAMETERS.add("control");
+ ALLOWED_TTX_PARAMETERS.add("misc");
+ ALLOWED_TTX_PARAMETERS.add("page");
+ ALLOWED_TTX_PARAMETERS.add("private");
+ }
+
+
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_TELETEX_TERM_ID_NAME;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_CASE_IGNORE_OID;
+ }
+
+
+
+ @Override
+ public String getSubstringMatchingRule()
+ {
+ return SMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // Get a lowercase string representation of the value and find its
+ // length.
+ final String valueString = value.toString();
+ final int valueLength = valueString.length();
+
+ // The value must contain at least one character.
+ if (valueLength == 0)
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_TELETEXID_EMPTY.get());
+ return false;
+ }
+
+ // The first character must be a printable string character.
+ char c = valueString.charAt(0);
+ if (!PrintableStringSyntaxImpl.isPrintableCharacter(c))
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_TELETEXID_NOT_PRINTABLE.get(
+ valueString, String.valueOf(c), 0));
+ return false;
+ }
+
+ // Continue reading until we find a dollar sign or the end of the
+ // string. Every intermediate character must be a printable string
+ // character.
+ int pos = 1;
+ for (; pos < valueLength; pos++)
+ {
+ c = valueString.charAt(pos);
+ if (c == '$')
+ {
+ pos++;
+ break;
+ }
+ else
+ {
+ if (!PrintableStringSyntaxImpl.isPrintableCharacter(c))
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_TELETEXID_NOT_PRINTABLE
+ .get(valueString, String.valueOf(c), pos));
+ }
+ }
+ }
+
+ if (pos >= valueLength)
+ {
+ // We're at the end of the value, so it must be valid unless the
+ // last character was a dollar sign.
+ if (c == '$')
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_TELETEXID_END_WITH_DOLLAR
+ .get(valueString));
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ // Continue reading until we find the end of the string. Each
+ // substring must be a valid teletex terminal identifier parameter
+ // followed by a colon and the value. Dollar signs must be escaped
+ int paramStartPos = pos;
+ boolean escaped = false;
+ while (pos < valueLength)
+ {
+ if (escaped)
+ {
+ pos++;
+ continue;
+ }
+
+ c = valueString.charAt(pos++);
+ if (c == '\\')
+ {
+ escaped = true;
+ continue;
+ }
+ else if (c == '$')
+ {
+ final String paramStr =
+ valueString.substring(paramStartPos, pos);
+
+ final int colonPos = paramStr.indexOf(':');
+ if (colonPos < 0)
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_TELETEXID_PARAM_NO_COLON
+ .get(valueString));
+ return false;
+ }
+
+ final String paramName = paramStr.substring(0, colonPos);
+ if (!ALLOWED_TTX_PARAMETERS.contains(paramName))
+ {
+
+ invalidReason
+ .append(ERR_ATTR_SYNTAX_TELETEXID_ILLEGAL_PARAMETER.get(
+ valueString, paramName));
+ return false;
+ }
+
+ paramStartPos = pos;
+ }
+ }
+
+ // We must be at the end of the value. Read the last parameter and
+ // make sure it is valid.
+ final String paramStr = valueString.substring(paramStartPos);
+ final int colonPos = paramStr.indexOf(':');
+ if (colonPos < 0)
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_TELETEXID_PARAM_NO_COLON
+ .get(valueString));
+ return false;
+ }
+
+ final String paramName = paramStr.substring(0, colonPos);
+ if (!ALLOWED_TTX_PARAMETERS.contains(paramName))
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_TELETEXID_ILLEGAL_PARAMETER
+ .get(valueString, paramName));
+ return false;
+ }
+
+ // If we've gotten here, then the value must be valid.
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/TelexNumberSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/TelexNumberSyntaxImpl.java
new file mode 100644
index 0000000..b171593
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/TelexNumberSyntaxImpl.java
@@ -0,0 +1,230 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_TELEX_ILLEGAL_CHAR;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_TELEX_NOT_PRINTABLE;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_TELEX_TOO_SHORT;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_TELEX_TRUNCATED;
+import static org.opends.sdk.schema.SchemaConstants.EMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_TELEX_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the telex number attribute syntax, which
+ * contains three printable strings separated by dollar sign characters.
+ * Equality, ordering, and substring matching will be allowed by
+ * default.
+ */
+final class TelexNumberSyntaxImpl extends AbstractSyntaxImpl
+{
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_TELEX_NAME;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_CASE_IGNORE_OID;
+ }
+
+
+
+ @Override
+ public String getSubstringMatchingRule()
+ {
+ return SMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // Get a string representation of the value and find its length.
+ final String valueString = value.toString();
+ final int valueLength = valueString.length();
+
+ if (valueLength < 5)
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_TELEX_TOO_SHORT
+ .get(valueString));
+ return false;
+ }
+
+ // The first character must be a printable string character.
+ char c = valueString.charAt(0);
+ if (!PrintableStringSyntaxImpl.isPrintableCharacter(c))
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_TELEX_NOT_PRINTABLE.get(
+ valueString, String.valueOf(c), 0));
+ return false;
+ }
+
+ // Continue reading until we find a dollar sign. Every intermediate
+ // character must be a printable string character.
+ int pos = 1;
+ for (; pos < valueLength; pos++)
+ {
+ c = valueString.charAt(pos);
+ if (c == '$')
+ {
+ pos++;
+ break;
+ }
+ else
+ {
+ if (!PrintableStringSyntaxImpl.isPrintableCharacter(c))
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_TELEX_ILLEGAL_CHAR.get(
+ valueString, String.valueOf(c), pos));
+ }
+ }
+ }
+
+ if (pos >= valueLength)
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_TELEX_TRUNCATED
+ .get(valueString));
+ return false;
+ }
+
+ // The next character must be a printable string character.
+ c = valueString.charAt(pos++);
+ if (!PrintableStringSyntaxImpl.isPrintableCharacter(c))
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_TELEX_NOT_PRINTABLE.get(
+ valueString, String.valueOf(c), (pos - 1)));
+ return false;
+ }
+
+ // Continue reading until we find another dollar sign. Every
+ // intermediate character must be a printable string character.
+ for (; pos < valueLength; pos++)
+ {
+ c = valueString.charAt(pos);
+ if (c == '$')
+ {
+ pos++;
+ break;
+ }
+ else
+ {
+ if (!PrintableStringSyntaxImpl.isPrintableCharacter(c))
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_TELEX_ILLEGAL_CHAR.get(
+ valueString, String.valueOf(c), pos));
+ return false;
+ }
+ }
+ }
+
+ if (pos >= valueLength)
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_TELEX_TRUNCATED
+ .get(valueString));
+ return false;
+ }
+
+ // The next character must be a printable string character.
+ c = valueString.charAt(pos++);
+ if (!PrintableStringSyntaxImpl.isPrintableCharacter(c))
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_TELEX_NOT_PRINTABLE.get(
+ valueString, String.valueOf(c), (pos - 1)));
+ return false;
+ }
+
+ // Continue reading until the end of the value. Every intermediate
+ // character must be a printable string character.
+ for (; pos < valueLength; pos++)
+ {
+ c = valueString.charAt(pos);
+ if (!PrintableStringSyntaxImpl.isPrintableCharacter(c))
+ {
+
+ invalidReason.append(ERR_ATTR_SYNTAX_TELEX_ILLEGAL_CHAR.get(
+ valueString, String.valueOf(c), pos));
+ return false;
+ }
+ }
+
+ // If we've gotten here, then we're at the end of the value and it
+ // is acceptable.
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/UTCTimeSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/UTCTimeSyntaxImpl.java
new file mode 100644
index 0000000..7cf9295
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/UTCTimeSyntaxImpl.java
@@ -0,0 +1,767 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.schema.SchemaConstants.*;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * This class implements the UTC time attribute syntax. This is very
+ * similar to the generalized time syntax (and actually has been
+ * deprecated in favor of that), but requires that the minute be
+ * provided and does not allow for sub-second times. All matching will
+ * be performed using the generalized time matching rules, and equality,
+ * ordering, and substring matching will be allowed.
+ */
+final class UTCTimeSyntaxImpl extends AbstractSyntaxImpl
+{
+
+ /**
+ * The lock that will be used to provide threadsafe access to the date
+ * formatter.
+ */
+ private final static Object dateFormatLock;
+
+ /**
+ * The date formatter that will be used to convert dates into UTC time
+ * values. Note that all interaction with it must be synchronized.
+ */
+ private final static SimpleDateFormat dateFormat;
+
+ /*
+ * Create the date formatter that will be used to construct and parse
+ * normalized UTC time values.
+ */
+ static
+ {
+ dateFormat = new SimpleDateFormat(DATE_FORMAT_UTC_TIME);
+ dateFormat.setLenient(false);
+ dateFormat.setTimeZone(TimeZone.getTimeZone(TIME_ZONE_UTC));
+
+ dateFormatLock = new Object();
+ }
+
+
+
+ /**
+ * Retrieves an string containing a UTC time representation of the
+ * provided date.
+ *
+ * @param d
+ * The date for which to retrieve the UTC time value.
+ * @return The attribute value created from the date.
+ */
+ static String createUTCTimeValue(Date d)
+ {
+ synchronized (dateFormatLock)
+ {
+ return dateFormat.format(d);
+ }
+ }
+
+
+
+ /**
+ * Decodes the provided normalized value as a UTC time value and
+ * retrieves a Java <CODE>Date</CODE> object containing its
+ * representation.
+ *
+ * @param valueString
+ * The normalized UTC time value to decode to a Java
+ * <CODE>Date</CODE>.
+ * @return The Java <CODE>Date</CODE> created from the provided UTC
+ * time value.
+ * @throws DecodeException
+ * If the provided value cannot be parsed as a valid UTC
+ * time string.
+ */
+ static Date decodeUTCTimeValue(String valueString)
+ throws DecodeException
+ {
+ try
+ {
+ synchronized (dateFormatLock)
+ {
+ return dateFormat.parse(valueString);
+ }
+ }
+ catch (final Exception e)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_CANNOT_PARSE.get(valueString, String
+ .valueOf(e));
+ final DecodeException de = DecodeException.error(message, e);
+ StaticUtils.DEBUG_LOG.throwing("UTCTimeSyntax",
+ "decodeUTCTimeValue", de);
+ throw de;
+ }
+ }
+
+
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_GENERALIZED_TIME_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_UTC_TIME_NAME;
+ }
+
+
+
+ @Override
+ public String getOrderingMatchingRule()
+ {
+ return OMR_GENERALIZED_TIME_OID;
+ }
+
+
+
+ @Override
+ public String getSubstringMatchingRule()
+ {
+ return SMR_CASE_IGNORE_OID;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // Get the value as a string and verify that it is at least long
+ // enough for "YYYYMMDDhhmmZ", which is the shortest allowed value.
+ final String valueString = value.toString().toUpperCase();
+ final int length = valueString.length();
+ if (length < 11)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_TOO_SHORT.get(valueString);
+ invalidReason.append(message);
+ return false;
+ }
+
+ // The first two characters are the year, and they must be numeric
+ // digits between 0 and 9.
+ for (int i = 0; i < 2; i++)
+ {
+ switch (valueString.charAt(i))
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ // These are all fine.
+ break;
+ default:
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_YEAR.get(valueString,
+ String.valueOf(valueString.charAt(i)));
+ invalidReason.append(message);
+ return false;
+ }
+ }
+
+ // The next two characters are the month, and they must form the
+ // string representation of an integer between 01 and 12.
+ char m1 = valueString.charAt(2);
+ final char m2 = valueString.charAt(3);
+ switch (m1)
+ {
+ case '0':
+ // m2 must be a digit between 1 and 9.
+ switch (m2)
+ {
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ // These are all fine.
+ break;
+ default:
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_MONTH.get(valueString,
+ valueString.substring(2, 4));
+ invalidReason.append(message);
+ return false;
+ }
+ break;
+ case '1':
+ // m2 must be a digit between 0 and 2.
+ switch (m2)
+ {
+ case '0':
+ case '1':
+ case '2':
+ // These are all fine.
+ break;
+ default:
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_MONTH.get(valueString,
+ valueString.substring(2, 4));
+ invalidReason.append(message);
+ return false;
+ }
+ break;
+ default:
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_MONTH.get(valueString,
+ valueString.substring(2, 4));
+ invalidReason.append(message);
+ return false;
+ }
+
+ // The next two characters should be the day of the month, and they
+ // must form the string representation of an integer between 01 and
+ // 31. This doesn't do any validation against the year or month, so
+ // it will allow dates like April 31, or February 29 in a non-leap
+ // year, but we'll let those slide.
+ final char d1 = valueString.charAt(4);
+ final char d2 = valueString.charAt(5);
+ switch (d1)
+ {
+ case '0':
+ // d2 must be a digit between 1 and 9.
+ switch (d2)
+ {
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ // These are all fine.
+ break;
+ default:
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_DAY.get(valueString,
+ valueString.substring(4, 6));
+ invalidReason.append(message);
+ return false;
+ }
+ break;
+ case '1':
+ // Treated the same as '2'.
+ case '2':
+ // d2 must be a digit between 0 and 9.
+ switch (d2)
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ // These are all fine.
+ break;
+ default:
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_DAY.get(valueString,
+ valueString.substring(4, 6));
+ invalidReason.append(message);
+ return false;
+ }
+ break;
+ case '3':
+ // d2 must be either 0 or 1.
+ switch (d2)
+ {
+ case '0':
+ case '1':
+ // These are all fine.
+ break;
+ default:
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_DAY.get(valueString,
+ valueString.substring(4, 6));
+ invalidReason.append(message);
+ return false;
+ }
+ break;
+ default:
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_DAY.get(valueString,
+ valueString.substring(4, 6));
+ invalidReason.append(message);
+ return false;
+ }
+
+ // The next two characters must be the hour, and they must form the
+ // string representation of an integer between 00 and 23.
+ final char h1 = valueString.charAt(6);
+ final char h2 = valueString.charAt(7);
+ switch (h1)
+ {
+ case '0':
+ // This is treated the same as '1'.
+ case '1':
+ switch (h2)
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ // These are all fine.
+ break;
+ default:
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_HOUR.get(valueString,
+ valueString.substring(6, 8));
+ invalidReason.append(message);
+ return false;
+ }
+ break;
+ case '2':
+ // This must be a digit between 0 and 3.
+ switch (h2)
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ // These are all fine.
+ break;
+ default:
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_HOUR.get(valueString,
+ valueString.substring(6, 8));
+ invalidReason.append(message);
+ return false;
+ }
+ break;
+ default:
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_HOUR.get(valueString,
+ valueString.substring(6, 8));
+ invalidReason.append(message);
+ return false;
+ }
+
+ // Next, there should be two digits comprising an integer between 00
+ // and 59 for the minute.
+ m1 = valueString.charAt(8);
+ switch (m1)
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ // There must be at least two more characters, and the next one
+ // must be a digit between 0 and 9.
+ if (length < 11)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(valueString,
+ String.valueOf(m1), 8);
+ invalidReason.append(message);
+ return false;
+ }
+
+ switch (valueString.charAt(9))
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ // These are all fine.
+ break;
+ default:
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_MINUTE.get(valueString,
+ valueString.substring(8, 10));
+ invalidReason.append(message);
+ return false;
+ }
+
+ break;
+
+ default:
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(valueString, String
+ .valueOf(m1), 8);
+ invalidReason.append(message);
+ return false;
+ }
+
+ // Next, there should be either two digits comprising an integer
+ // between 00 and 60 (for the second, including a possible leap
+ // second), a letter 'Z' (for the UTC specifier), or a plus or minus
+ // sign followed by four digits (for the UTC offset).
+ final char s1 = valueString.charAt(10);
+ switch (s1)
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ // There must be at least two more characters, and the next one
+ // must be a digit between 0 and 9.
+ if (length < 13)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(valueString,
+ String.valueOf(s1), 10);
+ invalidReason.append(message);
+ return false;
+ }
+
+ switch (valueString.charAt(11))
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ // These are all fine.
+ break;
+ default:
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_SECOND.get(valueString,
+ valueString.substring(10, 12));
+ invalidReason.append(message);
+ return false;
+ }
+
+ break;
+ case '6':
+ // There must be at least two more characters and the next one
+ // must be a 0.
+ if (length < 13)
+ {
+
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(valueString,
+ String.valueOf(s1), 10);
+ invalidReason.append(message);
+ return false;
+ }
+
+ if (valueString.charAt(11) != '0')
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_SECOND.get(valueString,
+ valueString.substring(10, 12));
+ invalidReason.append(message);
+ return false;
+ }
+
+ break;
+ case 'Z':
+ // This is fine only if we are at the end of the value.
+ if (length == 11)
+ {
+ return true;
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(valueString,
+ String.valueOf(s1), 10);
+ invalidReason.append(message);
+ return false;
+ }
+
+ case '+':
+ case '-':
+ // These are fine only if there are exactly four more digits that
+ // specify a valid offset.
+ if (length == 15)
+ {
+ return hasValidOffset(valueString, 11, invalidReason);
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(valueString,
+ String.valueOf(s1), 10);
+ invalidReason.append(message);
+ return false;
+ }
+
+ default:
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(valueString, String
+ .valueOf(s1), 10);
+ invalidReason.append(message);
+ return false;
+ }
+
+ // The last element should be either a letter 'Z' (for the UTC
+ // specifier), or a plus or minus sign followed by four digits (for
+ // the UTC offset).
+ switch (valueString.charAt(12))
+ {
+ case 'Z':
+ // This is fine only if we are at the end of the value.
+ if (length == 13)
+ {
+ return true;
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(valueString,
+ String.valueOf(valueString.charAt(12)), 12);
+ invalidReason.append(message);
+ return false;
+ }
+
+ case '+':
+ case '-':
+ // These are fine only if there are four or two more digits that
+ // specify a valid offset.
+ if (length == 17 || length == 15)
+ {
+ return hasValidOffset(valueString, 13, invalidReason);
+ }
+ else
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(valueString,
+ String.valueOf(valueString.charAt(12)), 12);
+ invalidReason.append(message);
+ return false;
+ }
+
+ default:
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(valueString, String
+ .valueOf(valueString.charAt(12)), 12);
+ invalidReason.append(message);
+ return false;
+ }
+ }
+
+
+
+ /**
+ * Indicates whether the provided string contains a valid set of two
+ * or four UTC offset digits. The provided string must have either two
+ * or four characters from the provided start position to the end of
+ * the value.
+ *
+ * @param value
+ * The whole value, including the offset.
+ * @param startPos
+ * The position of the first character that is contained in
+ * the offset.
+ * @param invalidReason
+ * The buffer to which the invalid reason may be appended if
+ * the string does not contain a valid set of UTC offset
+ * digits.
+ * @return <CODE>true</CODE> if the provided offset string is valid,
+ * or <CODE>false</CODE> if it is not.
+ */
+ private boolean hasValidOffset(String value, int startPos,
+ MessageBuilder invalidReason)
+ {
+ final int offsetLength = value.length() - startPos;
+ if (offsetLength < 2)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_TOO_SHORT.get(value);
+ invalidReason.append(message);
+ return false;
+ }
+
+ // The first two characters must be an integer between 00 and 23.
+ switch (value.charAt(startPos))
+ {
+ case '0':
+ case '1':
+ switch (value.charAt(startPos + 1))
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ // These are all fine.
+ break;
+ default:
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_OFFSET.get(value, value
+ .substring(startPos, startPos + offsetLength));
+ invalidReason.append(message);
+ return false;
+ }
+ break;
+ case '2':
+ switch (value.charAt(startPos + 1))
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ // These are all fine.
+ break;
+ default:
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_OFFSET.get(value, value
+ .substring(startPos, startPos + offsetLength));
+ invalidReason.append(message);
+ return false;
+ }
+ break;
+ default:
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_OFFSET.get(value, value
+ .substring(startPos, startPos + offsetLength));
+ invalidReason.append(message);
+ return false;
+ }
+
+ // If there are two more characters, then they must be an integer
+ // between 00 and 59.
+ if (offsetLength == 4)
+ {
+ switch (value.charAt(startPos + 2))
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ switch (value.charAt(startPos + 3))
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ // These are all fine.
+ break;
+ default:
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_OFFSET.get(value, value
+ .substring(startPos, startPos + offsetLength));
+ invalidReason.append(message);
+ return false;
+ }
+ break;
+ default:
+ final Message message =
+ ERR_ATTR_SYNTAX_UTC_TIME_INVALID_OFFSET.get(value, value
+ .substring(startPos, startPos + offsetLength));
+ invalidReason.append(message);
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/UUIDEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/UUIDEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..6d739b5
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/UUIDEqualityMatchingRuleImpl.java
@@ -0,0 +1,135 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_UUID_EXPECTED_DASH;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_UUID_EXPECTED_HEX;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_UUID_INVALID_LENGTH;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the uuidMatch matching rule defined in RFC 4530.
+ * It will be used as the default equality matching rule for the UUID
+ * syntax.
+ */
+final class UUIDEqualityMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value) throws DecodeException
+ {
+ if (value.length() != 36)
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_UUID_INVALID_LENGTH.get(value.toString(),
+ value.length());
+ throw DecodeException.error(message);
+ }
+
+ final StringBuilder builder = new StringBuilder(36);
+ char c;
+ for (int i = 0; i < 36; i++)
+ {
+ // The 9th, 14th, 19th, and 24th characters must be dashes. All
+ // others must be hex. Convert all uppercase hex characters to
+ // lowercase.
+ c = (char) value.byteAt(i);
+ switch (i)
+ {
+ case 8:
+ case 13:
+ case 18:
+ case 23:
+ if (c != '-')
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_UUID_EXPECTED_DASH.get(value.toString(),
+ i, String.valueOf(c));
+ throw DecodeException.error(message);
+ }
+ builder.append(c);
+ break;
+ default:
+ switch (c)
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ // These are all fine.
+ builder.append(c);
+ break;
+ case 'A':
+ builder.append('a');
+ break;
+ case 'B':
+ builder.append('b');
+ break;
+ case 'C':
+ builder.append('c');
+ break;
+ case 'D':
+ builder.append('d');
+ break;
+ case 'E':
+ builder.append('e');
+ break;
+ case 'F':
+ builder.append('f');
+ break;
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_UUID_EXPECTED_HEX.get(value.toString(),
+ i, String.valueOf(value.byteAt(i)));
+ throw DecodeException.error(message);
+ }
+ }
+ }
+
+ return ByteString.valueOf(builder.toString());
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/UUIDOrderingMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/UUIDOrderingMatchingRuleImpl.java
new file mode 100644
index 0000000..2ce5503
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/UUIDOrderingMatchingRuleImpl.java
@@ -0,0 +1,135 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_UUID_EXPECTED_DASH;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_UUID_EXPECTED_HEX;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_UUID_INVALID_LENGTH;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the uuidOrderingMatch matching rule defined in RFC
+ * 4530. This will be the default ordering matching rule for the UUID
+ * syntax.
+ */
+final class UUIDOrderingMatchingRuleImpl extends
+ AbstractOrderingMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value) throws DecodeException
+ {
+ if (value.length() != 36)
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_UUID_INVALID_LENGTH.get(value.toString(),
+ value.length());
+ throw DecodeException.error(message);
+ }
+
+ final StringBuilder builder = new StringBuilder(36);
+ char c;
+ for (int i = 0; i < 36; i++)
+ {
+ // The 9th, 14th, 19th, and 24th characters must be dashes. All
+ // others must be hex. Convert all uppercase hex characters to
+ // lowercase.
+ c = (char) value.byteAt(i);
+ switch (i)
+ {
+ case 8:
+ case 13:
+ case 18:
+ case 23:
+ if (c != '-')
+ {
+ final Message message =
+ WARN_ATTR_SYNTAX_UUID_EXPECTED_DASH.get(value.toString(),
+ i, String.valueOf(c));
+ throw DecodeException.error(message);
+ }
+ builder.append(c);
+ break;
+ default:
+ switch (c)
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ // These are all fine.
+ builder.append(c);
+ break;
+ case 'A':
+ builder.append('a');
+ break;
+ case 'B':
+ builder.append('b');
+ break;
+ case 'C':
+ builder.append('c');
+ break;
+ case 'D':
+ builder.append('d');
+ break;
+ case 'E':
+ builder.append('e');
+ break;
+ case 'F':
+ builder.append('f');
+ break;
+ default:
+ final Message message =
+ WARN_ATTR_SYNTAX_UUID_EXPECTED_HEX.get(value.toString(),
+ i, String.valueOf(value.byteAt(i)));
+ throw DecodeException.error(message);
+ }
+ }
+ }
+
+ return ByteString.valueOf(builder.toString());
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/UUIDSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/UUIDSyntaxImpl.java
new file mode 100644
index 0000000..c8df196
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/UUIDSyntaxImpl.java
@@ -0,0 +1,150 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_UUID_EXPECTED_DASH;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_UUID_EXPECTED_HEX;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_UUID_INVALID_LENGTH;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_UUID_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the UUID syntax, which is defined in RFC 4530.
+ * Equality and ordering matching will be allowed by default.
+ */
+final class UUIDSyntaxImpl extends AbstractSyntaxImpl
+{
+ public String getName()
+ {
+ return SYNTAX_UUID_NAME;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in an
+ * attribute with this syntax. If it is not, then the reason may be
+ * appended to the provided buffer.
+ *
+ * @param schema
+ * The schema in which this syntax is defined.
+ * @param value
+ * The value for which to make the determination.
+ * @param invalidReason
+ * The buffer to which the invalid reason should be appended.
+ * @return <CODE>true</CODE> if the provided value is acceptable for
+ * use with this syntax, or <CODE>false</CODE> if not.
+ */
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // We will only accept values that look like valid UUIDs. This means
+ // that all values must be in the form
+ // HHHHHHHH-HHHH-HHHH-HHHH-HHHHHHHHHHHH, where "H" represents a
+ // hexadecimal digit. First, make sure that the value is exactly 36
+ // bytes long.
+ final String valueString = value.toString();
+ if (valueString.length() != 36)
+ {
+
+ invalidReason.append(WARN_ATTR_SYNTAX_UUID_INVALID_LENGTH.get(
+ valueString, valueString.length()));
+ return false;
+ }
+
+ // Next, iterate through each character. Make sure that the 9th,
+ // 14th, 19th, and 24th characters are dashes and the rest are hex
+ // digits.
+ for (int i = 0; i < 36; i++)
+ {
+ switch (i)
+ {
+ case 8:
+ case 13:
+ case 18:
+ case 23:
+ if (valueString.charAt(i) != '-')
+ {
+
+ invalidReason.append(WARN_ATTR_SYNTAX_UUID_EXPECTED_DASH.get(
+ valueString, i, String.valueOf(valueString.charAt(i))));
+ return false;
+ }
+ break;
+ default:
+ switch (valueString.charAt(i))
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ break;
+ default:
+
+ invalidReason.append(WARN_ATTR_SYNTAX_UUID_EXPECTED_HEX.get(
+ valueString, i, String.valueOf(valueString.charAt(i))));
+ return false;
+ }
+ }
+ }
+
+ // If we've gotten here, then the value is acceptable.
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/UniqueMemberEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/UniqueMemberEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..ec975da
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/UniqueMemberEqualityMatchingRuleImpl.java
@@ -0,0 +1,50 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the uniqueMemberMatch matching rule defined in
+ * X.520 and referenced in RFC 2252. It is based on the name and
+ * optional UID syntax, and will compare values with a distinguished
+ * name and optional bit string suffix.
+ */
+final class UniqueMemberEqualityMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value)
+ {
+ return value.toByteString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/UnknownSchemaElementException.java b/sdk/src/org/opends/sdk/schema/UnknownSchemaElementException.java
new file mode 100644
index 0000000..2849440
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/UnknownSchemaElementException.java
@@ -0,0 +1,56 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * Thrown when a schema query fails because the requested schema element
+ * could not be found or is ambiguous.
+ */
+@SuppressWarnings("serial")
+public class UnknownSchemaElementException extends
+ LocalizedIllegalArgumentException
+{
+ /**
+ * Creates a new unknown schema element exception with the provided
+ * message.
+ *
+ * @param message
+ * The message that explains the problem that occurred.
+ */
+ public UnknownSchemaElementException(Message message)
+ {
+ super(message);
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/UserPasswordExactEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/UserPasswordExactEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..43c03cd
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/UserPasswordExactEqualityMatchingRuleImpl.java
@@ -0,0 +1,78 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * This class implements the userPasswordExactMatch matching rule, which
+ * will simply compare encoded hashed password values to see if they are
+ * exactly equal to each other.
+ */
+final class UserPasswordExactEqualityMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value) throws DecodeException
+ {
+ // The normalized form of this matching rule is exactly equal to the
+ // non-normalized form, except that the scheme needs to be converted
+ // to lowercase (if there is one).
+
+ if (UserPasswordSyntaxImpl.isEncoded(value))
+ {
+ final StringBuilder builder = new StringBuilder(value.length());
+ int closingBracePos = -1;
+ for (int i = 1; i < value.length(); i++)
+ {
+ if (value.byteAt(i) == '}')
+ {
+ closingBracePos = i;
+ break;
+ }
+ }
+ final ByteSequence seq1 =
+ value.subSequence(0, closingBracePos + 1);
+ final ByteSequence seq2 =
+ value.subSequence(closingBracePos + 1, value.length());
+ StaticUtils.toLowerCase(seq1, builder);
+ builder.append(seq2);
+ return ByteString.valueOf(builder.toString());
+ }
+ else
+ {
+ return value.toByteString();
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/schema/UserPasswordSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/UserPasswordSyntaxImpl.java
new file mode 100644
index 0000000..8c7dc4d
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/UserPasswordSyntaxImpl.java
@@ -0,0 +1,202 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_USERPW_NO_CLOSING_BRACE;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_USERPW_NO_OPENING_BRACE;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_USERPW_NO_SCHEME;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_USERPW_NO_VALUE;
+import static org.opends.sdk.schema.SchemaConstants.EMR_USER_PASSWORD_EXACT_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_USER_PASSWORD_NAME;
+import static org.opends.sdk.util.StaticUtils.toLowerCase;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class defines an attribute syntax used for storing values that
+ * have been encoded using a password storage scheme. The format for
+ * attribute values with this syntax is the concatenation of the
+ * following elements in the given order: <BR>
+ * <UL>
+ * <LI>An opening curly brace ("{") character.</LI>
+ * <LI>The name of the storage scheme used to encode the value.</LI>
+ * <LI>A closing curly brace ("}") character.</LI>
+ * <LI>The encoded value.</LI>
+ * </UL>
+ */
+final class UserPasswordSyntaxImpl extends AbstractSyntaxImpl
+{
+ /**
+ * Decodes the provided user password value into its component parts.
+ *
+ * @param userPasswordValue
+ * The user password value to be decoded.
+ * @return A two-element string array whose elements are the storage
+ * scheme name (in all lowercase characters) and the encoded
+ * value, in that order.
+ * @throws DecodeException
+ * If a problem is encountered while attempting to decode
+ * the value.
+ */
+ static String[] decodeUserPassword(String userPasswordValue)
+ throws DecodeException
+ {
+ // Make sure that there actually is a value to decode.
+ if (userPasswordValue == null || userPasswordValue.length() == 0)
+ {
+ final Message message = ERR_ATTR_SYNTAX_USERPW_NO_VALUE.get();
+ throw DecodeException.error(message);
+ }
+
+ // The first character of an encoded value must be an opening curly
+ // brace.
+ if (userPasswordValue.charAt(0) != '{')
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_USERPW_NO_OPENING_BRACE.get();
+ throw DecodeException.error(message);
+ }
+
+ // There must be a corresponding closing brace.
+ final int closePos = userPasswordValue.indexOf('}');
+ if (closePos < 0)
+ {
+ final Message message =
+ ERR_ATTR_SYNTAX_USERPW_NO_CLOSING_BRACE.get();
+ throw DecodeException.error(message);
+ }
+
+ // Get the storage scheme name and encoded value.
+ final String schemeName = userPasswordValue.substring(1, closePos);
+ final String encodedValue =
+ userPasswordValue.substring(closePos + 1);
+
+ if (schemeName.length() == 0)
+ {
+ final Message message = ERR_ATTR_SYNTAX_USERPW_NO_SCHEME.get();
+ throw DecodeException.error(message);
+ }
+
+ return new String[] { toLowerCase(schemeName), encodedValue };
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is encoded using the user
+ * password syntax.
+ *
+ * @param value
+ * The value for which to make the determination.
+ * @return <CODE>true</CODE> if the value appears to be encoded using
+ * the user password syntax, or <CODE>false</CODE> if not.
+ */
+ static boolean isEncoded(ByteSequence value)
+ {
+ // If the value is null or empty, then it's not.
+ if (value == null || value.length() == 0)
+ {
+ return false;
+ }
+
+ // If the value doesn't start with an opening curly brace, then it's
+ // not.
+ if (value.byteAt(0) != '{')
+ {
+ return false;
+ }
+
+ // There must be a corresponding closing curly brace, and there must
+ // be at least one character inside the brace.
+ int closingBracePos = -1;
+ for (int i = 1; i < value.length(); i++)
+ {
+ if (value.byteAt(i) == '}')
+ {
+ closingBracePos = i;
+ break;
+ }
+ }
+
+ if (closingBracePos < 0 || closingBracePos == 1)
+ {
+ return false;
+ }
+
+ // The closing curly brace must not be the last character of the
+ // password.
+ if (closingBracePos == value.length() - 1)
+ {
+ return false;
+ }
+
+ // If we've gotten here, then it looks to be encoded.
+ return true;
+ }
+
+
+
+ @Override
+ public String getEqualityMatchingRule()
+ {
+ return EMR_USER_PASSWORD_EXACT_OID;
+ }
+
+
+
+ public String getName()
+ {
+ return SYNTAX_USER_PASSWORD_NAME;
+ }
+
+
+
+ public boolean isHumanReadable()
+ {
+ return true;
+ }
+
+
+
+ public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+ MessageBuilder invalidReason)
+ {
+ // We have to accept any value here because in many cases the value
+ // will not have been encoded by the time this method is called.
+ // TODO: Is this correct for client-side use?
+ return true;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/schema/WordEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/WordEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..077a7c3
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/WordEqualityMatchingRuleImpl.java
@@ -0,0 +1,184 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.Assertion;
+import org.opends.sdk.ConditionResult;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the wordMatch matching rule defined in X.520.
+ * That document defines "word" as implementation-specific, but in this
+ * case we will consider it a match if the assertion value is contained
+ * within the attribute value and is bounded by the edge of the value or
+ * any of the following characters: <BR>
+ * <UL>
+ * <LI>A space</LI>
+ * <LI>A period</LI>
+ * <LI>A comma</LI>
+ * <LI>A slash</LI>
+ * <LI>A dollar sign</LI>
+ * <LI>A plus sign</LI>
+ * <LI>A dash</LI>
+ * <LI>An underscore</LI>
+ * <LI>An octothorpe</LI>
+ * <LI>An equal sign</LI>
+ * </UL>
+ */
+final class WordEqualityMatchingRuleImpl extends
+ AbstractMatchingRuleImpl
+{
+ @Override
+ public Assertion getAssertion(Schema schema, ByteSequence value)
+ throws DecodeException
+ {
+ final String normalStr = normalize(value);
+
+ return new Assertion()
+ {
+ public ConditionResult matches(ByteSequence attributeValue)
+ {
+ // See if the assertion value is contained in the attribute
+ // value. If not, then it isn't a match.
+ final String valueStr1 = attributeValue.toString();
+
+ final int pos = valueStr1.indexOf(normalStr);
+ if (pos < 0)
+ {
+ return ConditionResult.FALSE;
+ }
+
+ if (pos > 0)
+ {
+ final char c = valueStr1.charAt(pos - 1);
+ switch (c)
+ {
+ case ' ':
+ case '.':
+ case ',':
+ case '/':
+ case '$':
+ case '+':
+ case '-':
+ case '_':
+ case '#':
+ case '=':
+ // These are all acceptable.
+ break;
+
+ default:
+ // Anything else is not.
+ return ConditionResult.FALSE;
+ }
+ }
+
+ if (valueStr1.length() > pos + normalStr.length())
+ {
+ final char c = valueStr1.charAt(pos + normalStr.length());
+ switch (c)
+ {
+ case ' ':
+ case '.':
+ case ',':
+ case '/':
+ case '$':
+ case '+':
+ case '-':
+ case '_':
+ case '#':
+ case '=':
+ // These are all acceptable.
+ break;
+
+ default:
+ // Anything else is not.
+ return ConditionResult.FALSE;
+ }
+ }
+
+ // If we've gotten here, then we can assume it is a match.
+ return ConditionResult.TRUE;
+ }
+ };
+ }
+
+
+
+ public ByteString normalizeAttributeValue(Schema schema,
+ ByteSequence value)
+ {
+ return ByteString.valueOf(normalize(value));
+ }
+
+
+
+ private String normalize(ByteSequence value)
+ {
+ final StringBuilder buffer = new StringBuilder();
+ prepareUnicode(buffer, value, TRIM, CASE_FOLD);
+
+ final int bufferLength = buffer.length();
+ if (bufferLength == 0)
+ {
+ if (value.length() > 0)
+ {
+ // This should only happen if the value is composed entirely of
+ // spaces. In that case, the normalized value is a single space.
+ return " ".intern();
+ }
+ else
+ {
+ // The value is empty, so it is already normalized.
+ return "".intern();
+ }
+ }
+
+ // Replace any consecutive spaces with a single space.
+ for (int pos = bufferLength - 1; pos > 0; pos--)
+ {
+ if (buffer.charAt(pos) == ' ')
+ {
+ if (buffer.charAt(pos - 1) == ' ')
+ {
+ buffer.delete(pos, pos + 1);
+ }
+ }
+ }
+
+ return buffer.toString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/tools/Argument.java b/sdk/src/org/opends/sdk/tools/Argument.java
new file mode 100644
index 0000000..15206eb
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/Argument.java
@@ -0,0 +1,808 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2008 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.UtilityMessages.*;
+import static org.opends.sdk.util.StaticUtils.toLowerCase;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+
+
+
+/**
+ * This class defines a generic argument that may be used in the
+ * argument list for an application. This is an abstract class that must
+ * be subclassed in order to provide specific functionality.
+ */
+abstract class Argument
+{
+ // Indicates whether this argument should be hidden in the usage
+ // information.
+ private boolean isHidden;
+
+ // Indicates whether this argument may be specified more than once for
+ // multiple values.
+ private boolean isMultiValued;
+
+ // Indicates whether this argument was provided in the set of
+ // command-line
+ // arguments.
+ private boolean isPresent;
+
+ // Indicates whether this argument is required to have a value.
+ private boolean isRequired;
+
+ // Indicates whether this argument requires a value.
+ private boolean needsValue;
+
+ // The single-character identifier for this argument.
+ private Character shortIdentifier;
+
+ // The unique ID of the description for this argument.
+ private Message description;
+
+ // The set of values for this argument.
+ private LinkedList<String> values;
+
+ // The default value for the argument if none other is provided.
+ private String defaultValue;
+
+ // The long identifier for this argument.
+ private String longIdentifier;
+
+ // The generic name that will be used to refer to this argument.
+ private String name;
+
+ // The name of the property that can be used to set the default value.
+ private String propertyName;
+
+ // The value placeholder for this argument, which will be used in
+ // usage
+ // information.
+ private Message valuePlaceholder;
+
+ // Indicates whether this argument was provided in the set of
+ // properties
+ // found is a properties file.
+ private boolean isValueSetByProperty;
+
+
+
+ /**
+ * Creates a new argument with the provided information.
+ *
+ * @param name
+ * The generic name that should be used to refer to this
+ * argument.
+ * @param shortIdentifier
+ * The single-character identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param longIdentifier
+ * The long identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param isRequired
+ * Indicates whether this argument must be specified on the
+ * command line.
+ * @param isMultiValued
+ * Indicates whether this argument may be specified more than
+ * once to provide multiple values.
+ * @param needsValue
+ * Indicates whether this argument requires a value.
+ * @param valuePlaceholder
+ * The placeholder for the argument value that will be
+ * displayed in usage information, or <CODE>null</CODE> if
+ * this argument does not require a value.
+ * @param defaultValue
+ * The default value that should be used for this argument if
+ * none is provided in a properties file or on the command
+ * line. This may be <CODE>null</CODE> if there is no generic
+ * default.
+ * @param propertyName
+ * The name of the property in a property file that may be
+ * used to override the default value but will be overridden
+ * by a command-line argument.
+ * @param description
+ * Message for the description of this argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to
+ * create this argument.
+ */
+ protected Argument(String name, Character shortIdentifier,
+ String longIdentifier, boolean isRequired, boolean isMultiValued,
+ boolean needsValue, Message valuePlaceholder,
+ String defaultValue, String propertyName, Message description)
+ throws ArgumentException
+ {
+ this.name = name;
+ this.shortIdentifier = shortIdentifier;
+ this.longIdentifier = longIdentifier;
+ this.isRequired = isRequired;
+ this.isMultiValued = isMultiValued;
+ this.needsValue = needsValue;
+ this.valuePlaceholder = valuePlaceholder;
+ this.defaultValue = defaultValue;
+ this.propertyName = propertyName;
+ this.description = description;
+ this.isValueSetByProperty = false;
+
+ if ((shortIdentifier == null) && (longIdentifier == null))
+ {
+ Message message = ERR_ARG_NO_IDENTIFIER.get(name);
+ throw new ArgumentException(message);
+ }
+
+ if (needsValue && (valuePlaceholder == null))
+ {
+ Message message = ERR_ARG_NO_VALUE_PLACEHOLDER.get(name);
+ throw new ArgumentException(message);
+ }
+
+ values = new LinkedList<String>();
+ isPresent = false;
+ isHidden = false;
+ }
+
+
+
+ /**
+ * Retrieves the generic name that will be used to refer to this
+ * argument.
+ *
+ * @return The generic name that will be used to refer to this
+ * argument.
+ */
+ public String getName()
+ {
+ return name;
+ }
+
+
+
+ /**
+ * Retrieves the single-character identifier that may be used to
+ * specify the value of this argument.
+ *
+ * @return The single-character identifier that may be used to specify
+ * the value of this argument, or <CODE>null</CODE> if there
+ * is none.
+ */
+ public Character getShortIdentifier()
+ {
+ return shortIdentifier;
+ }
+
+
+
+ /**
+ * Retrieves the long (multi-character) identifier that may be used to
+ * specify the value of this argument.
+ *
+ * @return The long (multi-character) identifier that may be used to
+ * specify the value of this argument.
+ */
+ public String getLongIdentifier()
+ {
+ return longIdentifier;
+ }
+
+
+
+ /**
+ * Indicates whether this argument is required to have at least one
+ * value.
+ *
+ * @return <CODE>true</CODE> if this argument is required to have at
+ * least one value, or <CODE>false</CODE> if it does not need
+ * to have a value.
+ */
+ public boolean isRequired()
+ {
+ return isRequired;
+ }
+
+
+
+ /**
+ * Specifies whether this argument is required to have at least one
+ * value.
+ *
+ * @param isRequired
+ * Indicates whether this argument is required to have at
+ * least one value.
+ */
+ public void setRequired(boolean isRequired)
+ {
+ this.isRequired = isRequired;
+ }
+
+
+
+ /**
+ * Indicates whether this argument is present in the parsed set of
+ * command-line arguments.
+ *
+ * @return <CODE>true</CODE> if this argument is present in the parsed
+ * set of command-line arguments, or <CODE>false</CODE> if
+ * not.
+ */
+ public boolean isPresent()
+ {
+ return isPresent;
+ }
+
+
+
+ /**
+ * Specifies whether this argument is present in the parsed set of
+ * command-line arguments.
+ *
+ * @param isPresent
+ * Indicates whether this argument is present in the set of
+ * command-line arguments.
+ */
+ public void setPresent(boolean isPresent)
+ {
+ this.isPresent = isPresent;
+ }
+
+
+
+ /**
+ * Indicates whether this argument should be hidden from the usage
+ * information.
+ *
+ * @return <CODE>true</CODE> if this argument should be hidden from
+ * the usage information, or <CODE>false</CODE> if not.
+ */
+ public boolean isHidden()
+ {
+ return isHidden;
+ }
+
+
+
+ /**
+ * Specifies whether this argument should be hidden from the usage
+ * information.
+ *
+ * @param isHidden
+ * Indicates whether this argument should be hidden from the
+ * usage information.
+ */
+ public void setHidden(boolean isHidden)
+ {
+ this.isHidden = isHidden;
+ }
+
+
+
+ /**
+ * Indicates whether this argument may be provided more than once on
+ * the command line to specify multiple values.
+ *
+ * @return <CODE>true</CODE> if this argument may be provided more
+ * than once on the command line to specify multiple values,
+ * or <CODE>false</CODE> if it may have at most one value.
+ */
+ public boolean isMultiValued()
+ {
+ return isMultiValued;
+ }
+
+
+
+ /**
+ * Specifies whether this argument may be provided more than once on
+ * the command line to specify multiple values.
+ *
+ * @param isMultiValued
+ * Indicates whether this argument may be provided more than
+ * once on the command line to specify multiple values.
+ */
+ public void setMultiValued(boolean isMultiValued)
+ {
+ this.isMultiValued = isMultiValued;
+ }
+
+
+
+ /**
+ * Indicates whether a value must be provided with this argument if it
+ * is present.
+ *
+ * @return <CODE>true</CODE> if a value must be provided with the
+ * argument if it is present, or <CODE>false</CODE> if the
+ * argument does not take a value and the presence of the
+ * argument identifier itself is sufficient to convey the
+ * necessary information.
+ */
+ public boolean needsValue()
+ {
+ return needsValue;
+ }
+
+
+
+ /**
+ * Specifies whether a value must be provided with this argument if it
+ * is present. If this is changed from <CODE>false</CODE> to
+ * <CODE>true</CODE>, then a value placeholder must also be provided.
+ *
+ * @param needsValue
+ * Indicates whether a value must be provided with this
+ * argument if it is present.
+ */
+ public void setNeedsValue(boolean needsValue)
+ {
+ this.needsValue = needsValue;
+ }
+
+
+
+ /**
+ * Retrieves the value placeholder that will be displayed for this
+ * argument in the generated usage information.
+ *
+ * @return The value placeholder that will be displayed for this
+ * argument in the generated usage information, or
+ * <CODE>null</CODE> if there is none.
+ */
+ public Message getValuePlaceholder()
+ {
+ return valuePlaceholder;
+ }
+
+
+
+ /**
+ * Specifies the value placeholder that will be displayed for this
+ * argument in the generated usage information. It may be
+ * <CODE>null</CODE> only if <CODE>needsValue()</CODE> returns
+ * <CODE>false</CODE>.
+ *
+ * @param valuePlaceholder
+ * The value placeholder that will be displayed for this
+ * argument in the generated usage information.
+ */
+ public void setValuePlaceholder(Message valuePlaceholder)
+ {
+ this.valuePlaceholder = valuePlaceholder;
+ }
+
+
+
+ /**
+ * Retrieves the default value that will be used for this argument if
+ * it is not specified on the command line and it is not set from a
+ * properties file.
+ *
+ * @return The default value that will be used for this argument if it
+ * is not specified on the command line and it is not set from
+ * a properties file, or <CODE>null</CODE> if there is no
+ * default value.
+ */
+ public String getDefaultValue()
+ {
+ return defaultValue;
+ }
+
+
+
+ /**
+ * Specifies the default value that will be used for this argument if
+ * it is not specified on the command line and it is not set from a
+ * properties file.
+ *
+ * @param defaultValue
+ * The default value that will be used for this argument if
+ * it is not specified on the command line and it is not set
+ * from a properties file.
+ */
+ public void setDefaultValue(String defaultValue)
+ {
+ this.defaultValue = defaultValue;
+ }
+
+
+
+ /**
+ * Retrieves the name of a property in a properties file that may be
+ * used to set the default value for this argument if it is present. A
+ * value read from a properties file will override the default value
+ * returned from the <CODE>getDefaultValue</CODE>, but the properties
+ * file value will be overridden by a value supplied on the command
+ * line.
+ *
+ * @return The name of a property in a properties file that may be
+ * used to set the default value for this argument if it is
+ * present.
+ */
+ public String getPropertyName()
+ {
+ return propertyName;
+ }
+
+
+
+ /**
+ * Specifies the name of a property in a properties file that may be
+ * used to set the default value for this argument if it is present.
+ *
+ * @param propertyName
+ * The name of a property in a properties file that may be
+ * used to set the default value for this argument if it is
+ * present.
+ */
+ public void setPropertyName(String propertyName)
+ {
+ this.propertyName = propertyName;
+ }
+
+
+
+ /**
+ * Indicates whether this argument was provided in the set of
+ * properties found is a properties file.
+ *
+ * @return <CODE>true</CODE> if this argument was provided in the set
+ * of properties found is a properties file, or
+ * <CODE>false</CODE> if not.
+ */
+ public boolean isValueSetByProperty()
+ {
+ return isValueSetByProperty;
+ }
+
+
+
+ /**
+ * Specifies whether this argument was provided in the set of
+ * properties found is a properties file.
+ *
+ * @param isValueSetByProperty
+ * Specify whether this argument was provided in the set of
+ * properties found is a properties file.
+ */
+ public void setValueSetByProperty(boolean isValueSetByProperty)
+ {
+ this.isValueSetByProperty = isValueSetByProperty;
+ }
+
+
+
+ /**
+ * Retrieves the human-readable description for this argument.
+ *
+ * @return The human-readable description for this argument.
+ */
+ public Message getDescription()
+ {
+ return description != null ? description : Message.EMPTY;
+ }
+
+
+
+ /**
+ * Indicates whether this argument has at least one value.
+ *
+ * @return <CODE>true</CODE> if this argument has at least one value,
+ * or <CODE>false</CODE> if it does not have any values.
+ */
+ public boolean hasValue()
+ {
+ return (!values.isEmpty());
+ }
+
+
+
+ /**
+ * Retrieves the string vale for this argument. If it has multiple
+ * values, then the first will be returned. If it does not have any
+ * values, then the default value will be returned.
+ *
+ * @return The string value for this argument, or <CODE>null</CODE> if
+ * there are no values and no default value has been given.
+ */
+ public String getValue()
+ {
+ if (values.isEmpty())
+ {
+ return defaultValue;
+ }
+
+ return values.getFirst();
+ }
+
+
+
+ /**
+ * Retrieves the set of string values for this argument.
+ *
+ * @return The set of string values for this argument.
+ */
+ public LinkedList<String> getValues()
+ {
+ return values;
+ }
+
+
+
+ /**
+ * Retrieves the value of this argument as an integer.
+ *
+ * @return The value of this argument as an integer.
+ * @throws ArgumentException
+ * If there are multiple values, or the value cannot be
+ * parsed as an integer.
+ */
+ public int getIntValue() throws ArgumentException
+ {
+ if (values.isEmpty())
+ {
+ Message message = ERR_ARG_NO_INT_VALUE.get(name);
+ throw new ArgumentException(message);
+ }
+
+ Iterator<String> iterator = values.iterator();
+ String valueString = iterator.next();
+
+ int intValue;
+ try
+ {
+ intValue = Integer.parseInt(valueString);
+ }
+ catch (Exception e)
+ {
+ Message message =
+ ERR_ARG_CANNOT_DECODE_AS_INT.get(valueString, name);
+ throw new ArgumentException(message, e);
+ }
+
+ if (iterator.hasNext())
+ {
+ Message message = ERR_ARG_INT_MULTIPLE_VALUES.get(name);
+ throw new ArgumentException(message);
+ }
+ else
+ {
+ return intValue;
+ }
+ }
+
+
+
+ /**
+ * Retrieves the set of values for this argument as a list of
+ * integers.
+ *
+ * @return A list of the integer representations of the values for
+ * this argument.
+ * @throws ArgumentException
+ * If any of the values cannot be parsed as an integer.
+ */
+ public LinkedList<Integer> getIntValues() throws ArgumentException
+ {
+ LinkedList<Integer> intList = new LinkedList<Integer>();
+
+ Iterator<String> iterator = values.iterator();
+ while (iterator.hasNext())
+ {
+ String valueString = iterator.next();
+
+ try
+ {
+ intList.add(Integer.valueOf(valueString));
+ }
+ catch (Exception e)
+ {
+ Message message =
+ ERR_ARG_CANNOT_DECODE_AS_INT.get(valueString, name);
+ throw new ArgumentException(message, e);
+ }
+ }
+
+ return intList;
+ }
+
+
+
+ /**
+ * Retrieves the value of this argument as an integer.
+ *
+ * @return The value of this argument as an integer.
+ * @throws ArgumentException
+ * If there are multiple values, or the value cannot be
+ * parsed as an integer.
+ */
+ public double getDoubleValue() throws ArgumentException
+ {
+ if (values.isEmpty())
+ {
+ Message message = ERR_ARG_NO_INT_VALUE.get(name);
+ throw new ArgumentException(message);
+ }
+
+ Iterator<String> iterator = values.iterator();
+ String valueString = iterator.next();
+
+ double intValue;
+ try
+ {
+ intValue = Double.parseDouble(valueString);
+ }
+ catch (Exception e)
+ {
+ Message message =
+ ERR_ARG_CANNOT_DECODE_AS_INT.get(valueString, name);
+ throw new ArgumentException(message, e);
+ }
+
+ if (iterator.hasNext())
+ {
+ Message message = ERR_ARG_INT_MULTIPLE_VALUES.get(name);
+ throw new ArgumentException(message);
+ }
+ else
+ {
+ return intValue;
+ }
+ }
+
+
+
+ /**
+ * Retrieves the set of values for this argument as a list of
+ * integers.
+ *
+ * @return A list of the integer representations of the values for
+ * this argument.
+ * @throws ArgumentException
+ * If any of the values cannot be parsed as an integer.
+ */
+ public LinkedList<Double> getDoubleValues() throws ArgumentException
+ {
+ LinkedList<Double> intList = new LinkedList<Double>();
+
+ Iterator<String> iterator = values.iterator();
+ while (iterator.hasNext())
+ {
+ String valueString = iterator.next();
+
+ try
+ {
+ intList.add(Double.valueOf(valueString));
+ }
+ catch (Exception e)
+ {
+ Message message =
+ ERR_ARG_CANNOT_DECODE_AS_INT.get(valueString, name);
+ throw new ArgumentException(message, e);
+ }
+ }
+
+ return intList;
+ }
+
+
+
+ /**
+ * Retrieves the value of this argument as a <CODE>Boolean</CODE>.
+ *
+ * @return The value of this argument as a <CODE>Boolean</CODE>.
+ * @throws ArgumentException
+ * If this argument cannot be interpreted as a Boolean
+ * value.
+ */
+ public boolean getBooleanValue() throws ArgumentException
+ {
+ if (values.isEmpty())
+ {
+ Message message = ERR_ARG_NO_BOOLEAN_VALUE.get(name);
+ throw new ArgumentException(message);
+ }
+
+ Iterator<String> iterator = values.iterator();
+ String valueString = toLowerCase(iterator.next());
+
+ boolean booleanValue;
+ if (valueString.equals("true") || valueString.equals("yes")
+ || valueString.equals("on") || valueString.equals("1"))
+ {
+ booleanValue = true;
+ }
+ else if (valueString.equals("false") || valueString.equals("no")
+ || valueString.equals("off") || valueString.equals("0"))
+ {
+ booleanValue = false;
+ }
+ else
+ {
+ Message message =
+ ERR_ARG_CANNOT_DECODE_AS_BOOLEAN.get(valueString, name);
+ throw new ArgumentException(message);
+ }
+
+ if (iterator.hasNext())
+ {
+ Message message = ERR_ARG_BOOLEAN_MULTIPLE_VALUES.get(name);
+ throw new ArgumentException(message);
+ }
+ else
+ {
+ return booleanValue;
+ }
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in this
+ * argument.
+ *
+ * @param valueString
+ * The value for which to make the determination.
+ * @param invalidReason
+ * A buffer into which the invalid reason may be written if
+ * the value is not acceptable.
+ * @return <CODE>true</CODE> if the value is acceptable, or
+ * <CODE>false</CODE> if it is not.
+ */
+ public abstract boolean valueIsAcceptable(String valueString,
+ MessageBuilder invalidReason);
+
+
+
+ /**
+ * Adds a value to the set of values for this argument. This should
+ * only be called if the value is allowed by the
+ * <CODE>valueIsAcceptable</CODE> method.
+ *
+ * @param valueString
+ * The string representation of the value to add to this
+ * argument.
+ */
+ public void addValue(String valueString)
+ {
+ values.add(valueString);
+ }
+
+
+
+ /**
+ * Clears the set of values assigned to this argument.
+ */
+ public void clearValues()
+ {
+ values.clear();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/tools/ArgumentException.java b/sdk/src/org/opends/sdk/tools/ArgumentException.java
new file mode 100644
index 0000000..6521e03
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/ArgumentException.java
@@ -0,0 +1,96 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2008 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.tools;
+
+
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.LocalizableException;
+
+
+
+/**
+ * This class defines an exception that may be thrown if there is a
+ * problem with an argument definition.
+ */
+final class ArgumentException extends Exception implements
+ LocalizableException
+{
+ /**
+ * The serial version identifier required to satisfy the compiler
+ * because this class extends <CODE>java.lang.Exception</CODE>, which
+ * implements the <CODE>java.io.Serializable</CODE> interface. This
+ * value was generated using the <CODE>serialver</CODE> command-line
+ * utility included with the Java SDK.
+ */
+ private static final long serialVersionUID = 5623155045312160730L;
+
+ // The I18N message associated with this exception.
+ private final Message message;
+
+
+
+ /**
+ * Creates a new argument exception with the provided message.
+ *
+ * @param message
+ * The message that explains the problem that occurred.
+ */
+ ArgumentException(Message message)
+ {
+ super(String.valueOf(message));
+ this.message = message;
+ }
+
+
+
+ /**
+ * Creates a new argument exception with the provided message and root
+ * cause.
+ *
+ * @param message
+ * The message that explains the problem that occurred.
+ * @param cause
+ * The exception that was caught to trigger this exception.
+ */
+ ArgumentException(Message message, Throwable cause)
+ {
+ super(String.valueOf(message), cause);
+ this.message = message;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Message getMessageObject()
+ {
+ return this.message;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/tools/ArgumentGroup.java b/sdk/src/org/opends/sdk/tools/ArgumentGroup.java
new file mode 100644
index 0000000..65b3394
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/ArgumentGroup.java
@@ -0,0 +1,209 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2008 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.tools;
+
+
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opends.messages.Message;
+
+
+
+/**
+ * Class for organizing options into logical groups when arguement usage
+ * is printed. To use an argument group, create an instance and use
+ * {@link org.opends.server.util.args.ArgumentParser #addArgument(Argument, ArgumentGroup)}
+ * when adding arguments for to the parser.
+ */
+final class ArgumentGroup implements Comparable<ArgumentGroup>
+{
+
+ // Description for this group of arguments
+ private Message description = null;
+
+ // List of arguments belonging to this group
+ private List<Argument> args = null;
+
+ // Governs groups position within usage statement
+ private Integer priority;
+
+
+
+ /**
+ * Creates a parameterized instance.
+ *
+ * @param description
+ * for options in this group that is printed before argument
+ * descriptions in usage output
+ * @param priority
+ * number governing the position of this group within the
+ * usage statement. Groups with higher priority values appear
+ * before groups with lower priority.
+ */
+ ArgumentGroup(Message description, int priority)
+ {
+ this.description = description;
+ this.priority = priority;
+ this.args = new LinkedList<Argument>();
+ }
+
+
+
+ /**
+ * Gets the description for this group of arguments.
+ *
+ * @return description for this argument group
+ */
+ Message getDescription()
+ {
+ return this.description;
+ }
+
+
+
+ /**
+ * Sets the description for this group of arguments.
+ *
+ * @param description
+ * for this argument group
+ */
+ void setDescription(Message description)
+ {
+ this.description = description;
+ }
+
+
+
+ /**
+ * Gets the list of arguments associated with this group.
+ *
+ * @return list of associated arguments
+ */
+ List<Argument> getArguments()
+ {
+ return Collections.unmodifiableList(args);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int compareTo(ArgumentGroup o)
+ {
+ // Groups with higher priority numbers appear before
+ // those with lower priority in the usage output
+ return -1 * priority.compareTo(o.priority);
+ }
+
+
+
+ /**
+ * Indicates whether this group contains any members.
+ *
+ * @return boolean where true means this group contains members
+ */
+ boolean containsArguments()
+ {
+ return this.args.size() > 0;
+ }
+
+
+
+ /**
+ * Indicates whether this group contains any non-hidden members.
+ *
+ * @return boolean where true means this group contains non-hidden
+ * members
+ */
+ boolean containsNonHiddenArguments()
+ {
+ for (Argument arg : args)
+ {
+ if (!arg.isHidden())
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+
+ /**
+ * Adds an argument to this group.
+ *
+ * @param arg
+ * to add
+ * @return boolean where true indicates the add was successful
+ */
+ boolean addArgument(Argument arg)
+ {
+ boolean success = false;
+ if (arg != null)
+ {
+ Character newShort = arg.getShortIdentifier();
+ String newLong = arg.getLongIdentifier();
+
+ // See if there is already an argument in this group that the
+ // new argument should replace
+ for (Iterator<Argument> it = this.args.iterator(); it.hasNext();)
+ {
+ Argument a = it.next();
+ if (newShort != null && newShort.equals(a.getShortIdentifier())
+ || newLong != null && newLong.equals(a.getLongIdentifier()))
+ {
+ it.remove();
+ break;
+ }
+ }
+
+ success = this.args.add(arg);
+ }
+ return success;
+ }
+
+
+
+ /**
+ * Removes an argument from this group.
+ *
+ * @param arg
+ * to remove
+ * @return boolean where true indicates the remove was successful
+ */
+ boolean removeArgument(Argument arg)
+ {
+ return this.args.remove(arg);
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/tools/ArgumentParser.java b/sdk/src/org/opends/sdk/tools/ArgumentParser.java
new file mode 100644
index 0000000..4c0a056
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/ArgumentParser.java
@@ -0,0 +1,2007 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.ToolMessages.INFO_DESCRIPTION_GENERAL_ARGS;
+import static org.opends.messages.ToolMessages.INFO_DESCRIPTION_IO_ARGS;
+import static org.opends.messages.ToolMessages.INFO_DESCRIPTION_LDAP_CONNECTION_ARGS;
+import static org.opends.messages.ToolMessages.INFO_DESCRIPTION_PRODUCT_VERSION;
+import static org.opends.messages.UtilityMessages.*;
+import static org.opends.server.tools.ToolConstants.*;
+import static org.opends.server.util.ServerConstants.EOL;
+import static org.opends.server.util.ServerConstants.PROPERTY_SCRIPT_NAME;
+import static org.opends.server.util.StaticUtils.getBytes;
+import static org.opends.server.util.StaticUtils.getExceptionMessage;
+import static org.opends.server.util.StaticUtils.toLowerCase;
+import static org.opends.server.util.StaticUtils.wrapText;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.server.util.SetupUtils;
+
+
+
+/**
+ * This class defines a utility that can be used to deal with
+ * command-line arguments for applications in a CLIP-compliant manner
+ * using either short one-character or longer word-based arguments. It
+ * is also integrated with the Directory Server message catalog so that
+ * it can display messages in an internationalizeable format, can
+ * automatically generate usage information, can detect conflicts
+ * between arguments, and can interact with a properties file to obtain
+ * default values for arguments there if they are not specified on the
+ * command-line.
+ */
+final class ArgumentParser
+{
+ /**
+ * The argument that will be used to indicate the file properties.
+ */
+ private StringArgument filePropertiesPathArgument;
+
+ /**
+ * The argument that will be used to indicate that we'll not look for
+ * default properties file.
+ */
+ private BooleanArgument noPropertiesFileArgument;
+
+ // The argument that will be used to trigger the display of usage
+ // information.
+ private Argument usageArgument;
+
+ // The argument that will be used to trigger the display of the OpenDS
+ // version.
+ private Argument versionArgument;
+
+ // The set of unnamed trailing arguments that were provided for this
+ // parser.
+ private ArrayList<String> trailingArguments;
+
+ // Indicates whether this parser will allow additional unnamed
+ // arguments at
+ // the end of the list.
+ private boolean allowsTrailingArguments;
+
+ // Indicates whether long arguments should be treated in a
+ // case-sensitive
+ // manner.
+ private boolean longArgumentsCaseSensitive;
+
+ // Indicates whether the usage or version information has been
+ // displayed.
+ private boolean usageOrVersionDisplayed;
+
+ // Indicates whether the version argument was provided.
+ private boolean versionPresent;
+
+ // The set of arguments defined for this parser, referenced by short
+ // ID.
+ private HashMap<Character, Argument> shortIDMap;
+
+ // The set of arguments defined for this parser, referenced by
+ // argument name.
+ private HashMap<String, Argument> argumentMap;
+
+ // The set of arguments defined for this parser, referenced by long
+ // ID.
+ private HashMap<String, Argument> longIDMap;
+
+ // The maximum number of unnamed trailing arguments that may be
+ // provided.
+ private int maxTrailingArguments;
+
+ // The minimum number of unnamed trailing arguments that may be
+ // provided.
+ private int minTrailingArguments;
+
+ // The total set of arguments defined for this parser.
+ private LinkedList<Argument> argumentList;
+
+ // The output stream to which usage information should be printed.
+ private OutputStream usageOutputStream;
+
+ // The fully-qualified name of the Java class that should be invoked
+ // to launch
+ // the program with which this argument parser is associated.
+ private String mainClassName;
+
+ // A human-readable description for the tool, which will be included
+ // when
+ // displaying usage information.
+ private Message toolDescription;
+
+ // The display name that will be used for the trailing arguments in
+ // the usage
+ // information.
+ private String trailingArgsDisplayName;
+
+ // The raw set of command-line arguments that were provided.
+ private String[] rawArguments;
+
+ /** Set of argument groups. */
+ private Set<ArgumentGroup> argumentGroups;
+
+ /**
+ * Group for arguments that have not been explicitly grouped. These
+ * will appear at the top of the usage statement without a header.
+ */
+ private ArgumentGroup defaultArgGroup =
+ new ArgumentGroup(Message.EMPTY, Integer.MAX_VALUE);
+
+ /**
+ * Group for arguments that are related to connection through LDAP.
+ * This includes options like the bind DN, the port, etc.
+ */
+ private ArgumentGroup ldapArgGroup =
+ new ArgumentGroup(INFO_DESCRIPTION_LDAP_CONNECTION_ARGS.get(),
+ Integer.MIN_VALUE + 2);
+
+ /**
+ * Group for arguments that are related to utility input/output like
+ * properties file, no-prompt etc. These will appear toward the bottom
+ * of the usage statement.
+ */
+ private ArgumentGroup ioArgGroup =
+ new ArgumentGroup(INFO_DESCRIPTION_IO_ARGS.get(),
+ Integer.MIN_VALUE + 1);
+
+ /**
+ * Group for arguments that are general like help, version etc. These
+ * will appear at the end of the usage statement.
+ */
+ private ArgumentGroup generalArgGroup =
+ new ArgumentGroup(INFO_DESCRIPTION_GENERAL_ARGS.get(),
+ Integer.MIN_VALUE);
+
+ private final static String INDENT = " ";
+ private final static int MAX_LENGTH =
+ SetupUtils.isWindows() ? 79 : 80;
+
+
+
+ /**
+ * Creates a new instance of this argument parser with no arguments.
+ * Unnamed trailing arguments will not be allowed.
+ *
+ * @param mainClassName
+ * The fully-qualified name of the Java class that should be
+ * invoked to launch the program with which this argument
+ * parser is associated.
+ * @param toolDescription
+ * A human-readable description for the tool, which will be
+ * included when displaying usage information.
+ * @param longArgumentsCaseSensitive
+ * Indicates whether long arguments should be treated in a
+ * case-sensitive manner.
+ */
+ public ArgumentParser(String mainClassName, Message toolDescription,
+ boolean longArgumentsCaseSensitive)
+ {
+ this.mainClassName = mainClassName;
+ this.toolDescription = toolDescription;
+ this.longArgumentsCaseSensitive = longArgumentsCaseSensitive;
+
+ argumentList = new LinkedList<Argument>();
+ argumentMap = new HashMap<String, Argument>();
+ shortIDMap = new HashMap<Character, Argument>();
+ longIDMap = new HashMap<String, Argument>();
+ allowsTrailingArguments = false;
+ usageOrVersionDisplayed = false;
+ versionPresent = false;
+ trailingArgsDisplayName = null;
+ maxTrailingArguments = 0;
+ minTrailingArguments = 0;
+ trailingArguments = new ArrayList<String>();
+ rawArguments = null;
+ usageArgument = null;
+ filePropertiesPathArgument = null;
+ noPropertiesFileArgument = null;
+ usageOutputStream = System.out;
+ initGroups();
+ }
+
+
+
+ /**
+ * Creates a new instance of this argument parser with no arguments
+ * that may or may not be allowed to have unnamed trailing arguments.
+ *
+ * @param mainClassName
+ * The fully-qualified name of the Java class that should be
+ * invoked to launch the program with which this argument
+ * parser is associated.
+ * @param toolDescription
+ * A human-readable description for the tool, which will be
+ * included when displaying usage information.
+ * @param longArgumentsCaseSensitive
+ * Indicates whether long arguments should be treated in a
+ * case-sensitive manner.
+ * @param allowsTrailingArguments
+ * Indicates whether this parser allows unnamed trailing
+ * arguments to be provided.
+ * @param minTrailingArguments
+ * The minimum number of unnamed trailing arguments that must
+ * be provided. A value less than or equal to zero indicates
+ * that no minimum will be enforced.
+ * @param maxTrailingArguments
+ * The maximum number of unnamed trailing arguments that may
+ * be provided. A value less than or equal to zero indicates
+ * that no maximum will be enforced.
+ * @param trailingArgsDisplayName
+ * The display name that should be used as a placeholder for
+ * unnamed trailing arguments in the generated usage
+ * information.
+ */
+ public ArgumentParser(String mainClassName, Message toolDescription,
+ boolean longArgumentsCaseSensitive,
+ boolean allowsTrailingArguments, int minTrailingArguments,
+ int maxTrailingArguments, String trailingArgsDisplayName)
+ {
+ this.mainClassName = mainClassName;
+ this.toolDescription = toolDescription;
+ this.longArgumentsCaseSensitive = longArgumentsCaseSensitive;
+ this.allowsTrailingArguments = allowsTrailingArguments;
+ this.minTrailingArguments = minTrailingArguments;
+ this.maxTrailingArguments = maxTrailingArguments;
+ this.trailingArgsDisplayName = trailingArgsDisplayName;
+
+ argumentList = new LinkedList<Argument>();
+ argumentMap = new HashMap<String, Argument>();
+ shortIDMap = new HashMap<Character, Argument>();
+ longIDMap = new HashMap<String, Argument>();
+ trailingArguments = new ArrayList<String>();
+ usageOrVersionDisplayed = false;
+ versionPresent = false;
+ rawArguments = null;
+ usageArgument = null;
+ usageOutputStream = System.out;
+ initGroups();
+ }
+
+
+
+ /**
+ * Retrieves the fully-qualified name of the Java class that should be
+ * invoked to launch the program with which this argument parser is
+ * associated.
+ *
+ * @return The fully-qualified name of the Java class that should be
+ * invoked to launch the program with which this argument
+ * parser is associated.
+ */
+ public String getMainClassName()
+ {
+ return mainClassName;
+ }
+
+
+
+ /**
+ * Retrieves a human-readable description for this tool, which should
+ * be included at the top of the command-line usage information.
+ *
+ * @return A human-readable description for this tool, or {@code null}
+ * if none is available.
+ */
+ public Message getToolDescription()
+ {
+ return toolDescription;
+ }
+
+
+
+ /**
+ * Indicates whether this parser will allow unnamed trailing
+ * arguments. These will be arguments at the end of the list that are
+ * not preceded by either a long or short identifier and will need to
+ * be manually parsed by the application using this parser. Note that
+ * once an unnamed trailing argument has been identified, all
+ * remaining arguments will be classified as such.
+ *
+ * @return <CODE>true</CODE> if this parser allows unnamed trailing
+ * arguments, or <CODE>false</CODE> if it does not.
+ */
+ public boolean allowsTrailingArguments()
+ {
+ return allowsTrailingArguments;
+ }
+
+
+
+ /**
+ * Retrieves the minimum number of unnamed trailing arguments that
+ * must be provided.
+ *
+ * @return The minimum number of unnamed trailing arguments that must
+ * be provided, or a value less than or equal to zero if no
+ * minimum will be enforced.
+ */
+ public int getMinTrailingArguments()
+ {
+ return minTrailingArguments;
+ }
+
+
+
+ /**
+ * Retrieves the maximum number of unnamed trailing arguments that may
+ * be provided.
+ *
+ * @return The maximum number of unnamed trailing arguments that may
+ * be provided, or a value less than or equal to zero if no
+ * maximum will be enforced.
+ */
+ public int getMaxTrailingArguments()
+ {
+ return maxTrailingArguments;
+ }
+
+
+
+ /**
+ * Retrieves the list of all arguments that have been defined for this
+ * argument parser.
+ *
+ * @return The list of all arguments that have been defined for this
+ * argument parser.
+ */
+ public LinkedList<Argument> getArgumentList()
+ {
+ return argumentList;
+ }
+
+
+
+ /**
+ * Retrieves the argument with the specified name.
+ *
+ * @param name
+ * The name of the argument to retrieve.
+ * @return The argument with the specified name, or <CODE>null</CODE>
+ * if there is no such argument.
+ */
+ public Argument getArgument(String name)
+ {
+ return argumentMap.get(name);
+ }
+
+
+
+ /**
+ * Retrieves the set of arguments mapped by the short identifier that
+ * may be used to reference them. Note that arguments that do not have
+ * a short identifier will not be present in this list.
+ *
+ * @return The set of arguments mapped by the short identifier that
+ * may be used to reference them.
+ */
+ public HashMap<Character, Argument> getArgumentsByShortID()
+ {
+ return shortIDMap;
+ }
+
+
+
+ /**
+ * Retrieves the argument with the specified short identifier.
+ *
+ * @param shortID
+ * The short ID for the argument to retrieve.
+ * @return The argument with the specified short identifier, or
+ * <CODE>null</CODE> if there is no such argument.
+ */
+ public Argument getArgumentForShortID(Character shortID)
+ {
+ return shortIDMap.get(shortID);
+ }
+
+
+
+ /**
+ * Retrieves the set of arguments mapped by the long identifier that
+ * may be used to reference them. Note that arguments that do not have
+ * a long identifier will not be present in this list.
+ *
+ * @return The set of arguments mapped by the long identifier that may
+ * be used to reference them.
+ */
+ public HashMap<String, Argument> getArgumentsByLongID()
+ {
+ return longIDMap;
+ }
+
+
+
+ /**
+ * Retrieves the argument with the specified long identifier.
+ *
+ * @param longID
+ * The long identifier of the argument to retrieve.
+ * @return The argument with the specified long identifier, or
+ * <CODE>null</CODE> if there is no such argument.
+ */
+ public Argument getArgumentForLongID(String longID)
+ {
+ return longIDMap.get(longID);
+ }
+
+
+
+ /**
+ * Retrieves the set of unnamed trailing arguments that were provided
+ * on the command line.
+ *
+ * @return The set of unnamed trailing arguments that were provided on
+ * the command line.
+ */
+ public ArrayList<String> getTrailingArguments()
+ {
+ return trailingArguments;
+ }
+
+
+
+ /**
+ * Retrieves the raw set of arguments that were provided.
+ *
+ * @return The raw set of arguments that were provided, or
+ * <CODE>null</CODE> if the argument list has not yet been
+ * parsed.
+ */
+ public String[] getRawArguments()
+ {
+ return rawArguments;
+ }
+
+
+
+ /**
+ * Sets the usage group description for the default argument group.
+ *
+ * @param description
+ * for the default group
+ */
+ public void setDefaultArgumentGroupDescription(Message description)
+ {
+ this.defaultArgGroup.setDescription(description);
+ }
+
+
+
+ /**
+ * Sets the usage group description for the LDAP argument group.
+ *
+ * @param description
+ * for the LDAP group
+ */
+ public void setLdapArgumentGroupDescription(Message description)
+ {
+ this.ldapArgGroup.setDescription(description);
+ }
+
+
+
+ /**
+ * Sets the usage group description for the input/output argument
+ * group.
+ *
+ * @param description
+ * for the input/output group
+ */
+ public void setInputOutputArgumentGroupDescription(Message description)
+ {
+ this.ioArgGroup.setDescription(description);
+ }
+
+
+
+ /**
+ * Sets the usage group description for the general argument group.
+ *
+ * @param description
+ * for the general group
+ */
+ public void setGeneralArgumentGroupDescription(Message description)
+ {
+ this.generalArgGroup.setDescription(description);
+ }
+
+
+
+ /**
+ * Adds the provided argument to the set of arguments handled by this
+ * parser.
+ *
+ * @param argument
+ * The argument to be added.
+ * @throws ArgumentException
+ * If the provided argument conflicts with another argument
+ * that has already been defined.
+ */
+ public void addArgument(Argument argument) throws ArgumentException
+ {
+ addArgument(argument, null);
+ }
+
+
+
+ /**
+ * Adds the provided argument to the set of arguments handled by this
+ * parser and puts the arguement in the default group.
+ *
+ * @param argument
+ * The argument to be added.
+ * @throws ArgumentException
+ * If the provided argument conflicts with another argument
+ * that has already been defined.
+ */
+ public void addDefaultArgument(Argument argument)
+ throws ArgumentException
+ {
+ addArgument(argument, defaultArgGroup);
+ }
+
+
+
+ /**
+ * Adds the provided argument to the set of arguments handled by this
+ * parser and puts the argument in the LDAP connection group.
+ *
+ * @param argument
+ * The argument to be added.
+ * @throws ArgumentException
+ * If the provided argument conflicts with another argument
+ * that has already been defined.
+ */
+ public void addLdapConnectionArgument(Argument argument)
+ throws ArgumentException
+ {
+ addArgument(argument, ldapArgGroup);
+ }
+
+
+
+ /**
+ * Adds the provided argument to the set of arguments handled by this
+ * parser and puts the argument in the input/output group.
+ *
+ * @param argument
+ * The argument to be added.
+ * @throws ArgumentException
+ * If the provided argument conflicts with another argument
+ * that has already been defined.
+ */
+ public void addInputOutputArgument(Argument argument)
+ throws ArgumentException
+ {
+ addArgument(argument, ioArgGroup);
+ }
+
+
+
+ /**
+ * Adds the provided argument to the set of arguments handled by this
+ * parser and puts the arguement in the general group.
+ *
+ * @param argument
+ * The argument to be added.
+ * @throws ArgumentException
+ * If the provided argument conflicts with another argument
+ * that has already been defined.
+ */
+ public void addGeneralArgument(Argument argument)
+ throws ArgumentException
+ {
+ addArgument(argument, generalArgGroup);
+ }
+
+
+
+ /**
+ * Adds the provided argument to the set of arguments handled by this
+ * parser.
+ *
+ * @param argument
+ * The argument to be added.
+ * @param group
+ * The argument group to which the argument belongs.
+ * @throws ArgumentException
+ * If the provided argument conflicts with another argument
+ * that has already been defined.
+ */
+ public void addArgument(Argument argument, ArgumentGroup group)
+ throws ArgumentException
+ {
+
+ Character shortID = argument.getShortIdentifier();
+ if ((shortID != null) && shortIDMap.containsKey(shortID))
+ {
+ String conflictingName = shortIDMap.get(shortID).getName();
+
+ Message message =
+ ERR_ARGPARSER_DUPLICATE_SHORT_ID.get(argument.getName(),
+ String.valueOf(shortID), conflictingName);
+ throw new ArgumentException(message);
+ }
+
+ if (versionArgument != null)
+ {
+ if (shortID == versionArgument.getShortIdentifier())
+ {
+ // Update the version argument to not display its short
+ // identifier.
+ try
+ {
+ versionArgument =
+ new BooleanArgument(OPTION_LONG_PRODUCT_VERSION, null,
+ OPTION_LONG_PRODUCT_VERSION,
+ INFO_DESCRIPTION_PRODUCT_VERSION.get());
+ this.generalArgGroup.addArgument(versionArgument);
+ }
+ catch (ArgumentException e)
+ {
+ // ignore
+ }
+ }
+ }
+
+ String longID = argument.getLongIdentifier();
+ if (longID != null)
+ {
+ if (!longArgumentsCaseSensitive)
+ {
+ longID = toLowerCase(longID);
+ }
+ if (longIDMap.containsKey(longID))
+ {
+ String conflictingName = longIDMap.get(longID).getName();
+
+ Message message =
+ ERR_ARGPARSER_DUPLICATE_LONG_ID.get(argument.getName(),
+ argument.getLongIdentifier(), conflictingName);
+ throw new ArgumentException(message);
+ }
+ }
+
+ if (shortID != null)
+ {
+ shortIDMap.put(shortID, argument);
+ }
+
+ if (longID != null)
+ {
+ longIDMap.put(longID, argument);
+ }
+
+ argumentList.add(argument);
+
+ if (group == null)
+ {
+ group = getStandardGroup(argument);
+ }
+ group.addArgument(argument);
+ argumentGroups.add(group);
+ }
+
+
+
+ /**
+ * Sets the provided argument as one which will automatically trigger
+ * the output of usage information if it is provided on the command
+ * line and no further argument validation will be performed. Note
+ * that the caller will still need to add this argument to the parser
+ * with the <CODE>addArgument</CODE> method, and the argument should
+ * not be required and should not take a value. Also, the caller will
+ * still need to check for the presence of the usage argument after
+ * calling <CODE>parseArguments</CODE> to know that no further
+ * processing will be required.
+ *
+ * @param argument
+ * The argument whose presence should automatically trigger
+ * the display of usage information.
+ */
+ public void setUsageArgument(Argument argument)
+ {
+ usageArgument = argument;
+ usageOutputStream = System.out;
+ }
+
+
+
+ /**
+ * Sets the provided argument as one which will automatically trigger
+ * the output of usage information if it is provided on the command
+ * line and no further argument validation will be performed. Note
+ * that the caller will still need to add this argument to the parser
+ * with the <CODE>addArgument</CODE> method, and the argument should
+ * not be required and should not take a value. Also, the caller will
+ * still need to check for the presence of the usage argument after
+ * calling <CODE>parseArguments</CODE> to know that no further
+ * processing will be required.
+ *
+ * @param argument
+ * The argument whose presence should automatically trigger
+ * the display of usage information.
+ * @param outputStream
+ * The output stream to which the usage information should be
+ * written.
+ */
+ public void setUsageArgument(Argument argument,
+ OutputStream outputStream)
+ {
+ usageArgument = argument;
+ usageOutputStream = outputStream;
+ }
+
+
+
+ /**
+ * Sets the provided argument which will be used to identify the file
+ * properties.
+ *
+ * @param argument
+ * The argument which will be used to identify the file
+ * properties.
+ */
+ public void setFilePropertiesArgument(StringArgument argument)
+ {
+ filePropertiesPathArgument = argument;
+ }
+
+
+
+ /**
+ * Sets the provided argument which will be used to identify the file
+ * properties.
+ *
+ * @param argument
+ * The argument which will be used to indicate if we have to
+ * look for properties file.
+ */
+ public void setNoPropertiesFileArgument(BooleanArgument argument)
+ {
+ noPropertiesFileArgument = argument;
+ }
+
+
+
+ /**
+ * Parses the provided set of arguments and updates the information
+ * associated with this parser accordingly.
+ *
+ * @param rawArguments
+ * The raw set of arguments to parse.
+ * @throws ArgumentException
+ * If a problem was encountered while parsing the provided
+ * arguments.
+ */
+ public void parseArguments(String[] rawArguments)
+ throws ArgumentException
+ {
+ parseArguments(rawArguments, null);
+ }
+
+
+
+ /**
+ * Parses the provided set of arguments and updates the information
+ * associated with this parser accordingly. Default values for
+ * unspecified arguments may be read from the specified properties
+ * file.
+ *
+ * @param rawArguments
+ * The set of raw arguments to parse.
+ * @param propertiesFile
+ * The path to the properties file to use to obtain default
+ * values for unspecified properties.
+ * @param requirePropertiesFile
+ * Indicates whether the parsing should fail if the provided
+ * properties file does not exist or is not accessible.
+ * @throws ArgumentException
+ * If a problem was encountered while parsing the provided
+ * arguments or interacting with the properties file.
+ */
+ public void parseArguments(String[] rawArguments,
+ String propertiesFile, boolean requirePropertiesFile)
+ throws ArgumentException
+ {
+ this.rawArguments = rawArguments;
+
+ Properties argumentProperties = null;
+
+ try
+ {
+ Properties p = new Properties();
+ FileInputStream fis = new FileInputStream(propertiesFile);
+ p.load(fis);
+ fis.close();
+ argumentProperties = p;
+ }
+ catch (Exception e)
+ {
+ if (requirePropertiesFile)
+ {
+ Message message =
+ ERR_ARGPARSER_CANNOT_READ_PROPERTIES_FILE.get(String
+ .valueOf(propertiesFile), getExceptionMessage(e));
+ throw new ArgumentException(message, e);
+ }
+ }
+
+ parseArguments(rawArguments, argumentProperties);
+ }
+
+
+
+ /**
+ * Parses the provided set of arguments and updates the information
+ * associated with this parser accordingly. Default values for
+ * unspecified arguments may be read from the specified properties if
+ * any are provided.
+ *
+ * @param rawArguments
+ * The set of raw arguments to parse.
+ * @param argumentProperties
+ * A set of properties that may be used to provide default
+ * values for arguments not included in the given raw
+ * arguments.
+ * @throws ArgumentException
+ * If a problem was encountered while parsing the provided
+ * arguments.
+ */
+ public void parseArguments(String[] rawArguments,
+ Properties argumentProperties) throws ArgumentException
+ {
+ this.rawArguments = rawArguments;
+
+ boolean inTrailingArgs = false;
+
+ int numArguments = rawArguments.length;
+ for (int i = 0; i < numArguments; i++)
+ {
+ String arg = rawArguments[i];
+
+ if (inTrailingArgs)
+ {
+ trailingArguments.add(arg);
+ if ((maxTrailingArguments > 0)
+ && (trailingArguments.size() > maxTrailingArguments))
+ {
+ Message message =
+ ERR_ARGPARSER_TOO_MANY_TRAILING_ARGS
+ .get(maxTrailingArguments);
+ throw new ArgumentException(message);
+ }
+
+ continue;
+ }
+
+ if (arg.equals("--"))
+ {
+ // This is a special indicator that we have reached the end of
+ // the named
+ // arguments and that everything that follows after this should
+ // be
+ // considered trailing arguments.
+ inTrailingArgs = true;
+ }
+ else if (arg.startsWith("--"))
+ {
+ // This indicates that we are using the long name to reference
+ // the
+ // argument. It may be in any of the following forms:
+ // --name
+ // --name value
+ // --name=value
+
+ String argName = arg.substring(2);
+ String argValue = null;
+ int equalPos = argName.indexOf('=');
+ if (equalPos < 0)
+ {
+ // This is fine. The value is not part of the argument name
+ // token.
+ }
+ else if (equalPos == 0)
+ {
+ // The argument starts with "--=", which is not acceptable.
+ Message message =
+ ERR_ARGPARSER_LONG_ARG_WITHOUT_NAME.get(arg);
+ throw new ArgumentException(message);
+ }
+ else
+ {
+ // The argument is in the form --name=value, so parse them
+ // both out.
+ argValue = argName.substring(equalPos + 1);
+ argName = argName.substring(0, equalPos);
+ }
+
+ // If we're not case-sensitive, then convert the name to
+ // lowercase.
+ String origArgName = argName;
+ if (!longArgumentsCaseSensitive)
+ {
+ argName = toLowerCase(argName);
+ }
+
+ // Get the argument with the specified name.
+ Argument a = longIDMap.get(argName);
+ if (a == null)
+ {
+ if (argName.equals(OPTION_LONG_HELP))
+ {
+ // "--help" will always be interpreted as requesting usage
+ // information.
+ try
+ {
+ getUsage(usageOutputStream);
+ }
+ catch (Exception e)
+ {
+ }
+
+ return;
+ }
+ else if (argName.equals(OPTION_LONG_PRODUCT_VERSION))
+ {
+ // "--version" will always be interpreted as requesting
+ // version
+ // information.
+ usageOrVersionDisplayed = true;
+ versionPresent = true;
+ try
+ {
+ // TODO
+ // DirectoryServer.printVersion(usageOutputStream);
+ }
+ catch (Exception e)
+ {
+ }
+
+ return;
+ }
+ else
+ {
+ // There is no such argument registered.
+ Message message =
+ ERR_ARGPARSER_NO_ARGUMENT_WITH_LONG_ID.get(origArgName);
+ throw new ArgumentException(message);
+ }
+ }
+ else
+ {
+ a.setPresent(true);
+
+ // If this is the usage argument, then immediately stop and
+ // print
+ // usage information.
+ if ((usageArgument != null)
+ && usageArgument.getName().equals(a.getName()))
+ {
+ try
+ {
+ getUsage(usageOutputStream);
+ }
+ catch (Exception e)
+ {
+ }
+
+ return;
+ }
+ }
+
+ // See if the argument takes a value. If so, then make sure one
+ // was
+ // provided. If not, then make sure none was provided.
+ if (a.needsValue())
+ {
+ if (argValue == null)
+ {
+ if ((i + 1) == numArguments)
+ {
+ Message message =
+ ERR_ARGPARSER_NO_VALUE_FOR_ARGUMENT_WITH_LONG_ID
+ .get(origArgName);
+ throw new ArgumentException(message);
+ }
+
+ argValue = rawArguments[++i];
+ }
+
+ MessageBuilder invalidReason = new MessageBuilder();
+ if (!a.valueIsAcceptable(argValue, invalidReason))
+ {
+ Message message =
+ ERR_ARGPARSER_VALUE_UNACCEPTABLE_FOR_LONG_ID.get(
+ argValue, origArgName, invalidReason.toString());
+ throw new ArgumentException(message);
+ }
+
+ // If the argument already has a value, then make sure it is
+ // acceptable to have more than one.
+ if (a.hasValue() && (!a.isMultiValued()))
+ {
+ Message message =
+ ERR_ARGPARSER_NOT_MULTIVALUED_FOR_LONG_ID
+ .get(origArgName);
+ throw new ArgumentException(message);
+ }
+
+ a.addValue(argValue);
+ }
+ else
+ {
+ if (argValue != null)
+ {
+ Message message =
+ ERR_ARGPARSER_ARG_FOR_LONG_ID_DOESNT_TAKE_VALUE
+ .get(origArgName);
+ throw new ArgumentException(message);
+ }
+ }
+ }
+ else if (arg.startsWith("-"))
+ {
+ // This indicates that we are using the 1-character name to
+ // reference
+ // the argument. It may be in any of the following forms:
+ // -n
+ // -nvalue
+ // -n value
+ if (arg.equals("-"))
+ {
+ Message message =
+ ERR_ARGPARSER_INVALID_DASH_AS_ARGUMENT.get();
+ throw new ArgumentException(message);
+ }
+
+ char argCharacter = arg.charAt(1);
+ String argValue;
+ if (arg.length() > 2)
+ {
+ argValue = arg.substring(2);
+ }
+ else
+ {
+ argValue = null;
+ }
+
+ // Get the argument with the specified short ID.
+ Argument a = shortIDMap.get(argCharacter);
+ if (a == null)
+ {
+ if (argCharacter == '?')
+ {
+ // "-?" will always be interpreted as requesting usage
+ // information.
+ try
+ {
+ getUsage(usageOutputStream);
+ }
+ catch (Exception e)
+ {
+ }
+
+ return;
+ }
+ else if ((argCharacter == OPTION_SHORT_PRODUCT_VERSION)
+ && (!shortIDMap.containsKey(OPTION_SHORT_PRODUCT_VERSION)))
+ {
+ // "-V" will always be interpreted as requesting
+ // version information except if it's already defined (e.g
+ // in
+ // ldap tools).
+ usageOrVersionDisplayed = true;
+ versionPresent = true;
+ try
+ {
+ // TODO
+ // DirectoryServer.printVersion(usageOutputStream);
+ }
+ catch (Exception e)
+ {
+ }
+ return;
+ }
+ else
+ {
+ // There is no such argument registered.
+ Message message =
+ ERR_ARGPARSER_NO_ARGUMENT_WITH_SHORT_ID.get(String
+ .valueOf(argCharacter));
+ throw new ArgumentException(message);
+ }
+ }
+ else
+ {
+ a.setPresent(true);
+
+ // If this is the usage argument, then immediately stop and
+ // print
+ // usage information.
+ if ((usageArgument != null)
+ && usageArgument.getName().equals(a.getName()))
+ {
+ try
+ {
+ getUsage(usageOutputStream);
+ }
+ catch (Exception e)
+ {
+ }
+
+ return;
+ }
+ }
+
+ // See if the argument takes a value. If so, then make sure one
+ // was
+ // provided. If not, then make sure none was provided.
+ if (a.needsValue())
+ {
+ if (argValue == null)
+ {
+ if ((i + 1) == numArguments)
+ {
+ Message message =
+ ERR_ARGPARSER_NO_VALUE_FOR_ARGUMENT_WITH_SHORT_ID
+ .get(String.valueOf(argCharacter));
+ throw new ArgumentException(message);
+ }
+
+ argValue = rawArguments[++i];
+ }
+
+ MessageBuilder invalidReason = new MessageBuilder();
+ if (!a.valueIsAcceptable(argValue, invalidReason))
+ {
+ Message message =
+ ERR_ARGPARSER_VALUE_UNACCEPTABLE_FOR_SHORT_ID.get(
+ argValue, String.valueOf(argCharacter),
+ invalidReason.toString());
+ throw new ArgumentException(message);
+ }
+
+ // If the argument already has a value, then make sure it is
+ // acceptable to have more than one.
+ if (a.hasValue() && (!a.isMultiValued()))
+ {
+ Message message =
+ ERR_ARGPARSER_NOT_MULTIVALUED_FOR_SHORT_ID.get(String
+ .valueOf(argCharacter));
+ throw new ArgumentException(message);
+ }
+
+ a.addValue(argValue);
+ }
+ else
+ {
+ if (argValue != null)
+ {
+ // If we've gotten here, then it means that we're in a
+ // scenario like
+ // "-abc" where "a" is a valid argument that doesn't take a
+ // value.
+ // However, this could still be valid if all remaining
+ // characters in
+ // the value are also valid argument characters that don't
+ // take
+ // values.
+ int valueLength = argValue.length();
+ for (int j = 0; j < valueLength; j++)
+ {
+ char c = argValue.charAt(j);
+ Argument b = shortIDMap.get(c);
+ if (b == null)
+ {
+ // There is no such argument registered.
+ Message message =
+ ERR_ARGPARSER_NO_ARGUMENT_WITH_SHORT_ID.get(String
+ .valueOf(argCharacter));
+ throw new ArgumentException(message);
+ }
+ else if (b.needsValue())
+ {
+ // This means we're in a scenario like "-abc" where b is
+ // a
+ // valid argument that takes a value. We don't support
+ // that.
+ Message message =
+ ERR_ARGPARSER_CANT_MIX_ARGS_WITH_VALUES.get(String
+ .valueOf(argCharacter), argValue, String
+ .valueOf(c));
+ throw new ArgumentException(message);
+ }
+ else
+ {
+ b.setPresent(true);
+
+ // If this is the usage argument, then immediately stop
+ // and
+ // print usage information.
+ if ((usageArgument != null)
+ && usageArgument.getName().equals(b.getName()))
+ {
+ try
+ {
+ getUsage(usageOutputStream);
+ }
+ catch (Exception e)
+ {
+ }
+
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+ else if (allowsTrailingArguments)
+ {
+ // It doesn't start with a dash, so it must be a trailing
+ // argument if
+ // that is acceptable.
+ inTrailingArgs = true;
+ trailingArguments.add(arg);
+ }
+ else
+ {
+ // It doesn't start with a dash and we don't allow trailing
+ // arguments,
+ // so this is illegal.
+ Message message =
+ ERR_ARGPARSER_DISALLOWED_TRAILING_ARGUMENT.get(arg);
+ throw new ArgumentException(message);
+ }
+ }
+
+ // If we allow trailing arguments and there is a minimum number,
+ // then make
+ // sure at least that many were provided.
+ if (allowsTrailingArguments && (minTrailingArguments > 0))
+ {
+ if (trailingArguments.size() < minTrailingArguments)
+ {
+ Message message =
+ ERR_ARGPARSER_TOO_FEW_TRAILING_ARGUMENTS
+ .get(minTrailingArguments);
+ throw new ArgumentException(message);
+ }
+ }
+
+ // If we don't have the argumentProperties, try to load a properties
+ // file.
+ if (argumentProperties == null)
+ {
+ argumentProperties = checkExternalProperties();
+ }
+
+ // Iterate through all of the arguments. For any that were not
+ // provided on
+ // the command line, see if there is an alternate default that can
+ // be used.
+ // For cases where there is not, see that argument is required.
+ for (Argument a : argumentList)
+ {
+ if (!a.isPresent())
+ {
+ // See if there is a value in the properties that can be used
+ if ((argumentProperties != null)
+ && (a.getPropertyName() != null))
+ {
+ String value =
+ argumentProperties.getProperty(a.getPropertyName()
+ .toLowerCase());
+ MessageBuilder invalidReason = new MessageBuilder();
+ if (value != null)
+ {
+ Boolean addValue = true;
+ if (!(a instanceof BooleanArgument))
+ {
+ addValue = a.valueIsAcceptable(value, invalidReason);
+ }
+ if (addValue)
+ {
+ a.addValue(value);
+ if (a.needsValue())
+ {
+ a.setPresent(true);
+ }
+ a.setValueSetByProperty(true);
+ }
+ }
+ }
+ }
+
+ if ((!a.isPresent()) && a.needsValue())
+ {
+ // See if the argument defines a default.
+ if (a.getDefaultValue() != null)
+ {
+ a.addValue(a.getDefaultValue());
+ }
+
+ // If there is still no value and the argument is required, then
+ // that's
+ // a problem.
+ if ((!a.hasValue()) && a.isRequired())
+ {
+ Message message =
+ ERR_ARGPARSER_NO_VALUE_FOR_REQUIRED_ARG.get(a.getName());
+ throw new ArgumentException(message);
+ }
+ }
+ }
+ }
+
+
+
+ /**
+ * Check if we have a properties file.
+ *
+ * @return The properties found in the properties file or null.
+ * @throws ArgumentException
+ * If a problem was encountered while parsing the provided
+ * arguments.
+ */
+ Properties checkExternalProperties() throws ArgumentException
+ {
+ // We don't look for properties file.
+ if ((noPropertiesFileArgument != null)
+ && (noPropertiesFileArgument.isPresent()))
+ {
+ return null;
+ }
+
+ // Check if we have a properties file argument
+ if (filePropertiesPathArgument == null)
+ {
+ return null;
+ }
+
+ // check if the properties file argument has been set. If not
+ // look for default location.
+ String propertiesFilePath = null;
+ if (filePropertiesPathArgument.isPresent())
+ {
+ propertiesFilePath = filePropertiesPathArgument.getValue();
+ }
+ else
+ {
+ // Check in "user home"/.opends directory
+ String userDir = System.getProperty("user.home");
+ propertiesFilePath =
+ findPropertiesFile(userDir + File.separator
+ + DEFAULT_OPENDS_CONFIG_DIR);
+
+ // TODO
+ /*
+ * if (propertiesFilePath == null) { // check
+ * "Opends instance"/config directory String instanceDir =
+ * DirectoryServer.getInstanceRoot(); propertiesFilePath =
+ * findPropertiesFile(instanceDir+ File.separator + "config"); }
+ */
+ }
+
+ // We don't have a properties file location
+ if (propertiesFilePath == null)
+ {
+ return null;
+ }
+
+ // We have a location for the properties file.
+ Properties argumentProperties = new Properties();
+ String scriptName = System.getProperty(PROPERTY_SCRIPT_NAME);
+ try
+ {
+ Properties p = new Properties();
+ FileInputStream fis = new FileInputStream(propertiesFilePath);
+ p.load(fis);
+ fis.close();
+
+ for (Enumeration<?> e = p.propertyNames(); e.hasMoreElements();)
+ {
+ String currentPropertyName = (String) e.nextElement();
+ String propertyName = currentPropertyName;
+
+ // Property name form <script name>.<property name> has the
+ // precedence to <property name>
+ if (scriptName != null)
+ {
+ if (currentPropertyName.startsWith(scriptName))
+ {
+ propertyName =
+ currentPropertyName.substring(scriptName.length() + 1);
+ }
+ else
+ {
+ if (p.containsKey(scriptName + "." + currentPropertyName))
+ {
+ continue;
+ }
+ }
+ }
+ argumentProperties.setProperty(propertyName.toLowerCase(), p
+ .getProperty(currentPropertyName));
+ }
+ }
+ catch (Exception e)
+ {
+ Message message =
+ ERR_ARGPARSER_CANNOT_READ_PROPERTIES_FILE.get(String
+ .valueOf(propertiesFilePath), getExceptionMessage(e));
+ throw new ArgumentException(message, e);
+ }
+ return argumentProperties;
+ }
+
+
+
+ /**
+ * Get the absolute path of the properties file.
+ *
+ * @param directory
+ * The location in which we should look for properties file
+ * @return The absolute path of the properties file or null
+ */
+ private String findPropertiesFile(String directory)
+ {
+ // Look for the tools properties file
+ File f =
+ new File(directory, DEFAULT_OPENDS_PROPERTIES_FILE_NAME
+ + DEFAULT_OPENDS_PROPERTIES_FILE_EXTENSION);
+ if (f.exists() && f.canRead())
+ {
+ return f.getAbsolutePath();
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+
+
+ /**
+ * Appends usage information based on the defined arguments to the
+ * provided buffer.
+ *
+ * @param buffer
+ * The buffer to which the usage information should be
+ * appended.
+ */
+ public void getUsage(StringBuilder buffer)
+ {
+ usageOrVersionDisplayed = true;
+ if ((toolDescription != null) && (toolDescription.length() > 0))
+ {
+ buffer
+ .append(wrapText(toolDescription.toString(), MAX_LENGTH - 1));
+ buffer.append(EOL);
+ buffer.append(EOL);
+ }
+
+ String scriptName = System.getProperty(PROPERTY_SCRIPT_NAME);
+ if ((scriptName == null) || (scriptName.length() == 0))
+ {
+ buffer.append(INFO_ARGPARSER_USAGE_JAVA_CLASSNAME
+ .get(mainClassName));
+ }
+ else
+ {
+ buffer.append(INFO_ARGPARSER_USAGE_JAVA_SCRIPTNAME
+ .get(scriptName));
+ }
+
+ if (allowsTrailingArguments)
+ {
+ if (trailingArgsDisplayName == null)
+ {
+ buffer.append(" " + INFO_ARGPARSER_USAGE_TRAILINGARGS.get());
+ }
+ else
+ {
+ buffer.append(" ");
+ buffer.append(trailingArgsDisplayName);
+ }
+ }
+ buffer.append(EOL);
+ buffer.append(INFO_SUBCMDPARSER_WHERE_OPTIONS_INCLUDE.get());
+ buffer.append(EOL);
+ buffer.append(EOL);
+
+ Argument helpArgument = null;
+
+ boolean printHeaders = printUsageGroupHeaders();
+ for (ArgumentGroup argGroup : argumentGroups)
+ {
+ if (argGroup.containsArguments() && printHeaders)
+ {
+ // Print the groups description if any
+ Message groupDesc = argGroup.getDescription();
+ if (groupDesc != null && !Message.EMPTY.equals(groupDesc))
+ {
+ buffer.append(EOL);
+ buffer.append(wrapText(groupDesc.toString(), MAX_LENGTH - 1));
+ buffer.append(EOL);
+ buffer.append(EOL);
+ }
+ }
+
+ for (Argument a : argGroup.getArguments())
+ {
+ // If this argument is hidden, then skip it.
+ if (a.isHidden())
+ {
+ continue;
+ }
+
+ // Help argument should be printed at the end
+ if ((usageArgument != null)
+ && usageArgument.getName().equals(a.getName()))
+ {
+ helpArgument = a;
+ continue;
+ }
+ printArgumentUsage(a, buffer);
+ }
+ }
+ if (helpArgument != null)
+ {
+ printArgumentUsage(helpArgument, buffer);
+ }
+ else
+ {
+ buffer.append(EOL);
+ buffer.append("-?");
+ buffer.append(EOL);
+ }
+ }
+
+
+
+ /**
+ * Retrieves a message containing usage information based on the
+ * defined arguments.
+ *
+ * @return A string containing usage information based on the defined
+ * arguments.
+ */
+ public Message getUsageMessage()
+ {
+ StringBuilder buffer = new StringBuilder();
+ getUsage(buffer);
+
+ // TODO: rework getUsage(OutputStream) to work with messages
+ // framework
+ return Message.raw(buffer.toString());
+ }
+
+
+
+ /**
+ * Retrieves a string containing usage information based on the
+ * defined arguments.
+ *
+ * @return A string containing usage information based on the defined
+ * arguments.
+ */
+ public String getUsage()
+ {
+ StringBuilder buffer = new StringBuilder();
+ getUsage(buffer);
+
+ return buffer.toString();
+ }
+
+
+
+ /**
+ * Writes usage information based on the defined arguments to the
+ * provided output stream.
+ *
+ * @param outputStream
+ * The output stream to which the usage information should be
+ * written.
+ * @throws IOException
+ * If a problem occurs while attempting to write the usage
+ * information to the provided output stream.
+ */
+ public void getUsage(OutputStream outputStream) throws IOException
+ {
+ StringBuilder buffer = new StringBuilder();
+ getUsage(buffer);
+
+ outputStream.write(getBytes(buffer.toString()));
+ }
+
+
+
+ /**
+ * Indicates whether the version or the usage information has been
+ * displayed to the end user either by an explicit argument like "-H"
+ * or "--help", or by a built-in argument like "-?".
+ *
+ * @return {@code true} if the usage information has been displayed,
+ * or {@code false} if not.
+ */
+ public boolean usageOrVersionDisplayed()
+ {
+ return usageOrVersionDisplayed;
+ }
+
+
+
+ /**
+ * Appends argument usage information to the provided buffer.
+ *
+ * @param a
+ * The argument to handle.
+ * @param buffer
+ * The buffer to which the usage information should be
+ * appended.
+ */
+ private void printArgumentUsage(Argument a, StringBuilder buffer)
+ {
+ // Write a line with the short and/or long identifiers that may be
+ // used
+ // for the argument.
+ final int indentLength = INDENT.length();
+ Character shortID = a.getShortIdentifier();
+ String longID = a.getLongIdentifier();
+ if (shortID != null)
+ {
+ int currentLength = buffer.length();
+
+ if (usageArgument.getName().equals(a.getName()))
+ {
+ buffer.append("-?, ");
+ }
+
+ buffer.append("-");
+ buffer.append(shortID.charValue());
+
+ if (a.needsValue() && longID == null)
+ {
+ buffer.append(" ");
+ buffer.append(a.getValuePlaceholder());
+ }
+
+ if (longID != null)
+ {
+ StringBuilder newBuffer = new StringBuilder();
+ newBuffer.append(", --");
+ newBuffer.append(longID);
+
+ if (a.needsValue())
+ {
+ newBuffer.append(" ");
+ newBuffer.append(a.getValuePlaceholder());
+ }
+
+ int lineLength =
+ (buffer.length() - currentLength) + newBuffer.length();
+ if (lineLength > MAX_LENGTH)
+ {
+ buffer.append(EOL);
+ buffer.append(newBuffer.toString());
+ }
+ else
+ {
+ buffer.append(newBuffer.toString());
+ }
+ }
+
+ buffer.append(EOL);
+ }
+ else
+ {
+ if (longID != null)
+ {
+ if (usageArgument.getName().equals(a.getName()))
+ {
+ buffer.append("-?, ");
+ }
+ buffer.append("--");
+ buffer.append(longID);
+
+ if (a.needsValue())
+ {
+ buffer.append(" ");
+ buffer.append(a.getValuePlaceholder());
+ }
+
+ buffer.append(EOL);
+ }
+ }
+
+ // Write one or more lines with the description of the argument.
+ // We will
+ // indent the description five characters and try our best to wrap
+ // at or
+ // before column 79 so it will be friendly to 80-column displays.
+ Message description = a.getDescription();
+ int descMaxLength = MAX_LENGTH - indentLength - 1;
+ if (description.length() <= descMaxLength)
+ {
+ buffer.append(INDENT);
+ buffer.append(description);
+ buffer.append(EOL);
+ }
+ else
+ {
+ String s = description.toString();
+ while (s.length() > descMaxLength)
+ {
+ int spacePos = s.lastIndexOf(' ', descMaxLength);
+ if (spacePos > 0)
+ {
+ buffer.append(INDENT);
+ buffer.append(s.substring(0, spacePos).trim());
+ s = s.substring(spacePos + 1).trim();
+ buffer.append(EOL);
+ }
+ else
+ {
+ // There are no spaces in the first 74 columns. See if there
+ // is one
+ // after that point. If so, then break there. If not, then
+ // don't
+ // break at all.
+ spacePos = s.indexOf(' ');
+ if (spacePos > 0)
+ {
+ buffer.append(INDENT);
+ buffer.append(s.substring(0, spacePos).trim());
+ s = s.substring(spacePos + 1).trim();
+ buffer.append(EOL);
+ }
+ else
+ {
+ buffer.append(INDENT);
+ buffer.append(s);
+ s = "";
+ buffer.append(EOL);
+ }
+ }
+ }
+
+ if (s.length() > 0)
+ {
+ buffer.append(INDENT);
+ buffer.append(s);
+ buffer.append(EOL);
+ }
+ }
+
+ if (a.needsValue() && (a.getDefaultValue() != null)
+ && (a.getDefaultValue().length() > 0))
+ {
+ buffer.append(INDENT);
+ buffer.append(INFO_ARGPARSER_USAGE_DEFAULT_VALUE.get(
+ a.getDefaultValue()).toString());
+ buffer.append(EOL);
+ }
+ }
+
+
+
+ /**
+ * Given an argument, returns an appropriate group. Arguments may be
+ * part of one of the special groups or the default group.
+ *
+ * @param argument
+ * for which a group is requested
+ * @return argument group appropriate for <code>argument</code>
+ */
+ ArgumentGroup getStandardGroup(Argument argument)
+ {
+ ArgumentGroup group;
+ if (isInputOutputArgument(argument))
+ {
+ group = ioArgGroup;
+ }
+ else if (isGeneralArgument(argument))
+ {
+ group = generalArgGroup;
+ }
+ else if (isLdapConnectionArgument(argument))
+ {
+ group = ldapArgGroup;
+ }
+ else
+ {
+ group = defaultArgGroup;
+ }
+ return group;
+ }
+
+
+
+ /**
+ * Indicates whether or not argument group description headers should
+ * be printed.
+ *
+ * @return boolean where true means print the descriptions
+ */
+ boolean printUsageGroupHeaders()
+ {
+ // If there is only a single group then we won't print them.
+ int groupsContainingArgs = 0;
+ for (ArgumentGroup argGroup : argumentGroups)
+ {
+ if (argGroup.containsNonHiddenArguments())
+ {
+ groupsContainingArgs++;
+ }
+ }
+ return groupsContainingArgs > 1;
+ }
+
+
+
+ private void initGroups()
+ {
+ this.argumentGroups = new TreeSet<ArgumentGroup>();
+ this.argumentGroups.add(defaultArgGroup);
+ this.argumentGroups.add(ldapArgGroup);
+ this.argumentGroups.add(generalArgGroup);
+ this.argumentGroups.add(ioArgGroup);
+
+ try
+ {
+ versionArgument =
+ new BooleanArgument(OPTION_LONG_PRODUCT_VERSION,
+ OPTION_SHORT_PRODUCT_VERSION,
+ OPTION_LONG_PRODUCT_VERSION,
+ INFO_DESCRIPTION_PRODUCT_VERSION.get());
+ this.generalArgGroup.addArgument(versionArgument);
+ }
+ catch (ArgumentException e)
+ {
+ // ignore
+ }
+ }
+
+
+
+ private boolean isInputOutputArgument(Argument arg)
+ {
+ boolean io = false;
+ if (arg != null)
+ {
+ String longId = arg.getLongIdentifier();
+ io =
+ OPTION_LONG_VERBOSE.equals(longId)
+ || OPTION_LONG_QUIET.equals(longId)
+ || OPTION_LONG_NO_PROMPT.equals(longId)
+ || OPTION_LONG_PROP_FILE_PATH.equals(longId)
+ || OPTION_LONG_NO_PROP_FILE.equals(longId)
+ || OPTION_LONG_SCRIPT_FRIENDLY.equals(longId)
+ || OPTION_LONG_DONT_WRAP.equals(longId)
+ || OPTION_LONG_ENCODING.equals(longId)
+ || OPTION_DSCFG_LONG_DISPLAY_EQUIVALENT.equals(longId)
+ || OPTION_LONG_EQUIVALENT_COMMAND_FILE_PATH
+ .equals(longId)
+ || OPTION_LONG_BATCH_FILE_PATH.equals(longId);
+ }
+ return io;
+ }
+
+
+
+ private boolean isLdapConnectionArgument(Argument arg)
+ {
+ boolean ldap = false;
+ if (arg != null)
+ {
+ String longId = arg.getLongIdentifier();
+ ldap =
+ OPTION_LONG_USE_SSL.equals(longId)
+ || OPTION_LONG_START_TLS.equals(longId)
+ || OPTION_LONG_HOST.equals(longId)
+ || OPTION_LONG_PORT.equals(longId)
+ || OPTION_LONG_BINDDN.equals(longId)
+ || OPTION_LONG_BINDPWD.equals(longId)
+ || OPTION_LONG_BINDPWD_FILE.equals(longId)
+ || OPTION_LONG_SASLOPTION.equals(longId)
+ || OPTION_LONG_TRUSTALL.equals(longId)
+ || OPTION_LONG_TRUSTSTOREPATH.equals(longId)
+ || OPTION_LONG_TRUSTSTORE_PWD.equals(longId)
+ || OPTION_LONG_TRUSTSTORE_PWD_FILE.equals(longId)
+ || OPTION_LONG_KEYSTOREPATH.equals(longId)
+ || OPTION_LONG_KEYSTORE_PWD.equals(longId)
+ || OPTION_LONG_KEYSTORE_PWD_FILE.equals(longId)
+ || OPTION_LONG_CERT_NICKNAME.equals(longId)
+ || OPTION_LONG_REFERENCED_HOST_NAME.equals(longId)
+ || OPTION_LONG_ADMIN_UID.equals(longId)
+ || OPTION_LONG_REPORT_AUTHZ_ID.equals(longId)
+ || OPTION_LONG_USE_PW_POLICY_CTL.equals(longId)
+ || OPTION_LONG_USE_SASL_EXTERNAL.equals(longId)
+ || OPTION_LONG_PROTOCOL_VERSION.equals(longId);
+ }
+ return ldap;
+ }
+
+
+
+ private boolean isGeneralArgument(Argument arg)
+ {
+ boolean general = false;
+ if (arg != null)
+ {
+ String longId = arg.getLongIdentifier();
+ general =
+ OPTION_LONG_HELP.equals(longId)
+ || OPTION_LONG_PRODUCT_VERSION.equals(longId);
+ }
+ return general;
+ }
+
+
+
+ /**
+ * Returns whether the usage argument was provided or not. This method
+ * should be called after a call to parseArguments.
+ *
+ * @return <CODE>true</CODE> if the usage argument was provided and
+ * <CODE>false</CODE> otherwise.
+ */
+ public boolean isUsageArgumentPresent()
+ {
+ boolean isUsageArgumentPresent = false;
+ if (usageArgument != null)
+ {
+ isUsageArgumentPresent = usageArgument.isPresent();
+ }
+ return isUsageArgumentPresent;
+ }
+
+
+
+ /**
+ * Returns whether the version argument was provided or not. This
+ * method should be called after a call to parseArguments.
+ *
+ * @return <CODE>true</CODE> if the version argument was provided and
+ * <CODE>false</CODE> otherwise.
+ */
+ public boolean isVersionArgumentPresent()
+ {
+ return versionPresent;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/tools/ArgumentParserConnectionFactory.java b/sdk/src/org/opends/sdk/tools/ArgumentParserConnectionFactory.java
new file mode 100644
index 0000000..6fc6de3
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/ArgumentParserConnectionFactory.java
@@ -0,0 +1,940 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.AdminToolMessages.INFO_DESCRIPTION_ADMIN_UID;
+import static org.opends.messages.ToolMessages.*;
+import static org.opends.server.tools.ToolConstants.*;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.util.logging.Logger;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509KeyManager;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginException;
+
+import org.opends.admin.ads.util.ApplicationKeyManager;
+import org.opends.messages.Message;
+import org.opends.quicksetup.Constants;
+import org.opends.sdk.*;
+import org.opends.sdk.ldap.LDAPConnectionFactory;
+import org.opends.sdk.ldap.LDAPConnectionOptions;
+import org.opends.sdk.requests.BindRequest;
+import org.opends.sdk.requests.Requests;
+import org.opends.sdk.sasl.*;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.SSLUtils;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.ssl.PromptingTrustManager;
+import org.opends.sdk.util.ssl.TrustAllTrustManager;
+import org.opends.sdk.util.ssl.TrustStoreTrustManager;
+import org.opends.server.util.SelectableCertificateKeyManager;
+import org.opends.server.util.cli.CLIException;
+import org.opends.server.util.cli.ConsoleApplication;
+
+
+
+/**
+ * A connection factory designed for use with command line tools.
+ */
+final class ArgumentParserConnectionFactory extends
+ AbstractConnectionFactory<AsynchronousConnection> implements
+ ConnectionFactory<AsynchronousConnection>
+{
+ /**
+ * End Of Line.
+ */
+ static final String EOL = System.getProperty("line.separator");
+
+ /**
+ * The Logger.
+ */
+ static final Logger LOG = Logger
+ .getLogger(ArgumentParserConnectionFactory.class.getName());
+
+ /**
+ * The 'hostName' global argument.
+ */
+ private StringArgument hostNameArg = null;
+
+ /**
+ * The 'port' global argument.
+ */
+ private IntegerArgument portArg = null;
+
+ /**
+ * The 'bindDN' global argument.
+ */
+ private StringArgument bindDnArg = null;
+
+ /**
+ * The 'adminUID' global argument.
+ */
+ private StringArgument adminUidArg = null;
+
+ /**
+ * The 'bindPasswordFile' global argument.
+ */
+ private FileBasedArgument bindPasswordFileArg = null;
+
+ /**
+ * The 'bindPassword' global argument.
+ */
+ private StringArgument bindPasswordArg = null;
+
+ /**
+ * The 'trustAllArg' global argument.
+ */
+ private BooleanArgument trustAllArg = null;
+
+ /**
+ * The 'trustStore' global argument.
+ */
+ private StringArgument trustStorePathArg = null;
+
+ /**
+ * The 'trustStorePassword' global argument.
+ */
+ private StringArgument trustStorePasswordArg = null;
+
+ /**
+ * The 'trustStorePasswordFile' global argument.
+ */
+ private FileBasedArgument trustStorePasswordFileArg = null;
+
+ /**
+ * The 'keyStore' global argument.
+ */
+ private StringArgument keyStorePathArg = null;
+
+ /**
+ * The 'keyStorePassword' global argument.
+ */
+ private StringArgument keyStorePasswordArg = null;
+
+ /**
+ * The 'keyStorePasswordFile' global argument.
+ */
+ private FileBasedArgument keyStorePasswordFileArg = null;
+
+ /**
+ * The 'certNicknameArg' global argument.
+ */
+ private StringArgument certNicknameArg = null;
+
+ /**
+ * The 'useSSLArg' global argument.
+ */
+ private BooleanArgument useSSLArg = null;
+
+ /**
+ * The 'useStartTLSArg' global argument.
+ */
+ private BooleanArgument useStartTLSArg = null;
+
+ /**
+ * Argument indicating a SASL option.
+ */
+ private StringArgument saslOptionArg = null;
+
+ /**
+ * Whether to request that the server return the authorization ID in
+ * the bind response.
+ */
+ private final BooleanArgument reportAuthzID;
+
+ /**
+ * Whether to use the password policy control in the bind request.
+ */
+ private final BooleanArgument usePasswordPolicyControl;
+
+ private int port = 389;
+
+ private SSLContext sslContext;
+
+ private ConnectionFactory<? extends AsynchronousConnection> connFactory;
+
+ private BindRequest bindRequest = null;
+
+ private final ConsoleApplication app;
+
+
+
+ public ArgumentParserConnectionFactory(ArgumentParser argumentParser,
+ ConsoleApplication app) throws ArgumentException
+ {
+ this(argumentParser, app, "cn=Directory Manager", 389, false);
+ }
+
+
+
+ public ArgumentParserConnectionFactory(ArgumentParser argumentParser,
+ ConsoleApplication app, String defaultBindDN, int defaultPort,
+ boolean alwaysSSL) throws ArgumentException
+ {
+ this.app = app;
+ useSSLArg = new BooleanArgument("useSSL", OPTION_SHORT_USE_SSL,
+ OPTION_LONG_USE_SSL, INFO_DESCRIPTION_USE_SSL.get());
+ useSSLArg.setPropertyName(OPTION_LONG_USE_SSL);
+ if (!alwaysSSL)
+ {
+ argumentParser.addLdapConnectionArgument(useSSLArg);
+ }
+ else
+ {
+ // simulate that the useSSL arg has been given in the CLI
+ useSSLArg.setPresent(true);
+ }
+
+ useStartTLSArg = new BooleanArgument("startTLS",
+ OPTION_SHORT_START_TLS, OPTION_LONG_START_TLS,
+ INFO_DESCRIPTION_START_TLS.get());
+ useStartTLSArg.setPropertyName(OPTION_LONG_START_TLS);
+ if (!alwaysSSL)
+ {
+ argumentParser.addLdapConnectionArgument(useStartTLSArg);
+ }
+
+ String defaultHostName;
+ try
+ {
+ defaultHostName = InetAddress.getLocalHost().getHostName();
+ }
+ catch (Exception e)
+ {
+ defaultHostName = "Unknown (" + e + ")";
+ }
+ hostNameArg = new StringArgument("host", OPTION_SHORT_HOST,
+ OPTION_LONG_HOST, false, false, true, INFO_HOST_PLACEHOLDER
+ .get(), defaultHostName, null, INFO_DESCRIPTION_HOST.get());
+ hostNameArg.setPropertyName(OPTION_LONG_HOST);
+ argumentParser.addLdapConnectionArgument(hostNameArg);
+
+ Message portDescription = INFO_DESCRIPTION_PORT.get();
+ if (alwaysSSL)
+ {
+ portDescription = INFO_DESCRIPTION_ADMIN_PORT.get();
+ }
+
+ portArg = new IntegerArgument("port", OPTION_SHORT_PORT,
+ OPTION_LONG_PORT, false, false, true, INFO_PORT_PLACEHOLDER
+ .get(), defaultPort, null, portDescription);
+ portArg.setPropertyName(OPTION_LONG_PORT);
+ argumentParser.addLdapConnectionArgument(portArg);
+
+ bindDnArg = new StringArgument("bindDN", OPTION_SHORT_BINDDN,
+ OPTION_LONG_BINDDN, false, false, true, INFO_BINDDN_PLACEHOLDER
+ .get(), defaultBindDN, null, INFO_DESCRIPTION_BINDDN.get());
+ bindDnArg.setPropertyName(OPTION_LONG_BINDDN);
+ argumentParser.addLdapConnectionArgument(bindDnArg);
+
+ // It is up to the classes that required admin UID to make this
+ // argument
+ // visible and add it.
+ adminUidArg = new StringArgument("adminUID", 'I',
+ OPTION_LONG_ADMIN_UID, false, false, true,
+ INFO_ADMINUID_PLACEHOLDER.get(), Constants.GLOBAL_ADMIN_UID,
+ null, INFO_DESCRIPTION_ADMIN_UID.get());
+ adminUidArg.setPropertyName(OPTION_LONG_ADMIN_UID);
+ adminUidArg.setHidden(true);
+
+ bindPasswordArg = new StringArgument("bindPassword",
+ OPTION_SHORT_BINDPWD, OPTION_LONG_BINDPWD, false, false, true,
+ INFO_BINDPWD_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_BINDPASSWORD.get());
+ bindPasswordArg.setPropertyName(OPTION_LONG_BINDPWD);
+ argumentParser.addLdapConnectionArgument(bindPasswordArg);
+
+ bindPasswordFileArg = new FileBasedArgument("bindPasswordFile",
+ OPTION_SHORT_BINDPWD_FILE, OPTION_LONG_BINDPWD_FILE, false,
+ false, INFO_BINDPWD_FILE_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_BINDPASSWORDFILE.get());
+ bindPasswordFileArg.setPropertyName(OPTION_LONG_BINDPWD_FILE);
+ argumentParser.addLdapConnectionArgument(bindPasswordFileArg);
+
+ saslOptionArg = new StringArgument("sasloption",
+ OPTION_SHORT_SASLOPTION, OPTION_LONG_SASLOPTION, false, true,
+ true, INFO_SASL_OPTION_PLACEHOLDER.get(), null, null,
+ INFO_LDAP_CONN_DESCRIPTION_SASLOPTIONS.get());
+ saslOptionArg.setPropertyName(OPTION_LONG_SASLOPTION);
+ argumentParser.addLdapConnectionArgument(saslOptionArg);
+
+ trustAllArg = new BooleanArgument("trustAll",
+ OPTION_SHORT_TRUSTALL, OPTION_LONG_TRUSTALL,
+ INFO_DESCRIPTION_TRUSTALL.get());
+ trustAllArg.setPropertyName(OPTION_LONG_TRUSTALL);
+ argumentParser.addLdapConnectionArgument(trustAllArg);
+
+ trustStorePathArg = new StringArgument("trustStorePath",
+ OPTION_SHORT_TRUSTSTOREPATH, OPTION_LONG_TRUSTSTOREPATH, false,
+ false, true, INFO_TRUSTSTOREPATH_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_TRUSTSTOREPATH.get());
+ trustStorePathArg.setPropertyName(OPTION_LONG_TRUSTSTOREPATH);
+ argumentParser.addLdapConnectionArgument(trustStorePathArg);
+
+ trustStorePasswordArg = new StringArgument("trustStorePassword",
+ OPTION_SHORT_TRUSTSTORE_PWD, OPTION_LONG_TRUSTSTORE_PWD, false,
+ false, true, INFO_TRUSTSTORE_PWD_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_TRUSTSTOREPASSWORD.get());
+ trustStorePasswordArg.setPropertyName(OPTION_LONG_TRUSTSTORE_PWD);
+ argumentParser.addLdapConnectionArgument(trustStorePasswordArg);
+
+ trustStorePasswordFileArg = new FileBasedArgument(
+ "trustStorePasswordFile", OPTION_SHORT_TRUSTSTORE_PWD_FILE,
+ OPTION_LONG_TRUSTSTORE_PWD_FILE, false, false,
+ INFO_TRUSTSTORE_PWD_FILE_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_TRUSTSTOREPASSWORD_FILE.get());
+ trustStorePasswordFileArg
+ .setPropertyName(OPTION_LONG_TRUSTSTORE_PWD_FILE);
+ argumentParser.addLdapConnectionArgument(trustStorePasswordFileArg);
+
+ keyStorePathArg = new StringArgument("keyStorePath",
+ OPTION_SHORT_KEYSTOREPATH, OPTION_LONG_KEYSTOREPATH, false,
+ false, true, INFO_KEYSTOREPATH_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_KEYSTOREPATH.get());
+ keyStorePathArg.setPropertyName(OPTION_LONG_KEYSTOREPATH);
+ argumentParser.addLdapConnectionArgument(keyStorePathArg);
+
+ keyStorePasswordArg = new StringArgument("keyStorePassword",
+ OPTION_SHORT_KEYSTORE_PWD, OPTION_LONG_KEYSTORE_PWD, false,
+ false, true, INFO_KEYSTORE_PWD_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_KEYSTOREPASSWORD.get());
+ keyStorePasswordArg.setPropertyName(OPTION_LONG_KEYSTORE_PWD);
+ argumentParser.addLdapConnectionArgument(keyStorePasswordArg);
+
+ keyStorePasswordFileArg = new FileBasedArgument(
+ "keystorePasswordFile", OPTION_SHORT_KEYSTORE_PWD_FILE,
+ OPTION_LONG_KEYSTORE_PWD_FILE, false, false,
+ INFO_KEYSTORE_PWD_FILE_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_KEYSTOREPASSWORD_FILE.get());
+ keyStorePasswordFileArg
+ .setPropertyName(OPTION_LONG_KEYSTORE_PWD_FILE);
+ argumentParser.addLdapConnectionArgument(keyStorePasswordFileArg);
+
+ certNicknameArg = new StringArgument("certNickname",
+ OPTION_SHORT_CERT_NICKNAME, OPTION_LONG_CERT_NICKNAME, false,
+ false, true, INFO_NICKNAME_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_CERT_NICKNAME.get());
+ certNicknameArg.setPropertyName(OPTION_LONG_CERT_NICKNAME);
+ argumentParser.addLdapConnectionArgument(certNicknameArg);
+
+ reportAuthzID = new BooleanArgument("reportauthzid", 'E',
+ OPTION_LONG_REPORT_AUTHZ_ID, INFO_DESCRIPTION_REPORT_AUTHZID
+ .get());
+ reportAuthzID.setPropertyName(OPTION_LONG_REPORT_AUTHZ_ID);
+ argumentParser.addArgument(reportAuthzID);
+
+ usePasswordPolicyControl = new BooleanArgument(
+ "usepwpolicycontrol", null, OPTION_LONG_USE_PW_POLICY_CTL,
+ INFO_DESCRIPTION_USE_PWP_CONTROL.get());
+ usePasswordPolicyControl
+ .setPropertyName(OPTION_LONG_USE_PW_POLICY_CTL);
+ argumentParser.addArgument(usePasswordPolicyControl);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <P> ConnectionFuture<? extends AsynchronousConnection> getAsynchronousConnection(
+ ConnectionResultHandler<? super AsynchronousConnection, P> handler,
+ P p)
+ {
+ return connFactory.getAsynchronousConnection(handler, p);
+ }
+
+
+
+ public void validate() throws ArgumentException
+ {
+ port = portArg.getIntValue();
+
+ // Couldn't have at the same time bindPassword and bindPasswordFile
+ if (bindPasswordArg.isPresent() && bindPasswordFileArg.isPresent())
+ {
+ Message message = ERR_TOOL_CONFLICTING_ARGS
+ .get(bindPasswordArg.getLongIdentifier(), bindPasswordFileArg
+ .getLongIdentifier());
+ throw new ArgumentException(message);
+ }
+
+ // Couldn't have at the same time trustAll and
+ // trustStore related arg
+ if (trustAllArg.isPresent() && trustStorePathArg.isPresent())
+ {
+ Message message = ERR_TOOL_CONFLICTING_ARGS.get(trustAllArg
+ .getLongIdentifier(), trustStorePathArg.getLongIdentifier());
+ throw new ArgumentException(message);
+ }
+ if (trustAllArg.isPresent() && trustStorePasswordArg.isPresent())
+ {
+ Message message = ERR_TOOL_CONFLICTING_ARGS.get(trustAllArg
+ .getLongIdentifier(), trustStorePasswordArg
+ .getLongIdentifier());
+ throw new ArgumentException(message);
+ }
+ if (trustAllArg.isPresent()
+ && trustStorePasswordFileArg.isPresent())
+ {
+ Message message = ERR_TOOL_CONFLICTING_ARGS.get(trustAllArg
+ .getLongIdentifier(), trustStorePasswordFileArg
+ .getLongIdentifier());
+ throw new ArgumentException(message);
+ }
+
+ // Couldn't have at the same time trustStorePasswordArg and
+ // trustStorePasswordFileArg
+ if (trustStorePasswordArg.isPresent()
+ && trustStorePasswordFileArg.isPresent())
+ {
+ Message message = ERR_TOOL_CONFLICTING_ARGS.get(
+ trustStorePasswordArg.getLongIdentifier(),
+ trustStorePasswordFileArg.getLongIdentifier());
+ throw new ArgumentException(message);
+ }
+
+ if (trustStorePathArg.isPresent())
+ {
+ // Check that the path exists and is readable
+ String value = trustStorePathArg.getValue();
+ if (!canRead(trustStorePathArg.getValue()))
+ {
+ Message message = ERR_CANNOT_READ_TRUSTSTORE.get(value);
+ throw new ArgumentException(message);
+ }
+ }
+
+ if (keyStorePathArg.isPresent())
+ {
+ // Check that the path exists and is readable
+ String value = keyStorePathArg.getValue();
+ if (!canRead(trustStorePathArg.getValue()))
+ {
+ Message message = ERR_CANNOT_READ_KEYSTORE.get(value);
+ throw new ArgumentException(message);
+ }
+ }
+
+ // Couldn't have at the same time startTLSArg and
+ // useSSLArg
+ if (useStartTLSArg.isPresent() && useSSLArg.isPresent())
+ {
+ Message message = ERR_TOOL_CONFLICTING_ARGS.get(useStartTLSArg
+ .getLongIdentifier(), useSSLArg.getLongIdentifier());
+ throw new ArgumentException(message);
+ }
+
+ try
+ {
+ if (useSSLArg.isPresent() || useStartTLSArg.isPresent())
+ {
+ String clientAlias;
+ if (certNicknameArg.isPresent())
+ {
+ clientAlias = certNicknameArg.getValue();
+ }
+ else
+ {
+ clientAlias = null;
+ }
+
+ if (sslContext == null)
+ {
+ TrustManager trustManager = getTrustManager();
+
+ KeyManager keyManager = null;
+ X509KeyManager akm = getKeyManager(keyStorePathArg.getValue());
+
+ if (keyManager != null && clientAlias != null)
+ {
+ keyManager = new SelectableCertificateKeyManager(akm,
+ clientAlias);
+ }
+ sslContext = SSLUtils.getSSLContext(trustManager, keyManager);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ throw new ArgumentException(ERR_LDAP_CONN_CANNOT_INITIALIZE_SSL
+ .get(e.toString()), e);
+ }
+
+ if (sslContext != null)
+ {
+ LDAPConnectionOptions options = LDAPConnectionOptions
+ .defaultOptions().setSSLContext(sslContext).setUseStartTLS(
+ useStartTLSArg.isPresent());
+ connFactory = new LDAPConnectionFactory(hostNameArg.getValue(),
+ port, options);
+ }
+ else
+ {
+ connFactory = new LDAPConnectionFactory(hostNameArg.getValue(),
+ port);
+ }
+
+ try
+ {
+ bindRequest = getBindRequest();
+ }
+ catch (CLIException e)
+ {
+ throw new ArgumentException(Message.raw("Error reading input: "
+ + e.toString()));
+ }
+ if (bindRequest != null)
+ {
+ connFactory = new AuthenticatedConnectionFactory(connFactory,
+ bindRequest).setRebindAllowed(true);
+ }
+ }
+
+
+
+ private BindRequest getBindRequest() throws CLIException,
+ ArgumentException
+ {
+ String mech = null;
+ for (String s : saslOptionArg.getValues())
+ {
+ if (s.startsWith(SASL_PROPERTY_MECH))
+ {
+ mech = parseSASLOptionValue(s);
+ break;
+ }
+ }
+
+ if (mech == null)
+ {
+ if (bindDnArg.isPresent() || bindPasswordFileArg.isPresent()
+ || bindPasswordArg.isPresent())
+ {
+ return Requests.newSimpleBindRequest(getBindDN(), getPassword());
+ }
+ return null;
+ }
+
+ if (mech.equals(DigestMD5SASLBindRequest.SASL_MECHANISM_DIGEST_MD5))
+ {
+ return new DigestMD5SASLBindRequest(
+ getAuthID(DigestMD5SASLBindRequest.SASL_MECHANISM_DIGEST_MD5),
+ getAuthzID(), getPassword(), getRealm());
+ }
+ if (mech.equals(CRAMMD5SASLBindRequest.SASL_MECHANISM_CRAM_MD5))
+ {
+ return new CRAMMD5SASLBindRequest(
+ getAuthID(CRAMMD5SASLBindRequest.SASL_MECHANISM_CRAM_MD5),
+ getPassword());
+ }
+ if (mech.equals(GSSAPISASLBindRequest.SASL_MECHANISM_GSSAPI))
+ {
+ try
+ {
+ Subject subject = GSSAPISASLBindRequest.Kerberos5Login(
+ getAuthID(GSSAPISASLBindRequest.SASL_MECHANISM_GSSAPI),
+ getPassword(), getRealm(), getKDC());
+ return new GSSAPISASLBindRequest(subject, getAuthzID());
+ }
+ catch (LoginException e)
+ {
+ Message message = ERR_LDAPAUTH_GSSAPI_LOCAL_AUTHENTICATION_FAILED
+ .get(StaticUtils.getExceptionMessage(e));
+ throw new ArgumentException(message, e);
+ }
+ }
+ if (mech.equals(ExternalSASLBindRequest.SASL_MECHANISM_EXTERNAL))
+ {
+ if (sslContext == null)
+ {
+ Message message = ERR_TOOL_SASLEXTERNAL_NEEDS_SSL_OR_TLS.get();
+ throw new ArgumentException(message);
+ }
+ if (!keyStorePathArg.isPresent() && getKeyStore() == null)
+ {
+ Message message = ERR_TOOL_SASLEXTERNAL_NEEDS_KEYSTORE.get();
+ throw new ArgumentException(message);
+ }
+ return new ExternalSASLBindRequest(getAuthzID());
+ }
+ if (mech.equals(PlainSASLBindRequest.SASL_MECHANISM_PLAIN))
+ {
+ return new PlainSASLBindRequest(
+ getAuthID(PlainSASLBindRequest.SASL_MECHANISM_PLAIN),
+ getAuthzID(), getPassword());
+ }
+
+ throw new ArgumentException(ERR_LDAPAUTH_UNSUPPORTED_SASL_MECHANISM
+ .get(mech));
+ }
+
+
+
+ private DN getBindDN() throws CLIException, ArgumentException
+ {
+ String value = "";
+ if (bindDnArg.isPresent())
+ {
+ value = bindDnArg.getValue();
+ }
+ else if (app.isInteractive())
+ {
+ value = app.readInput(Message.raw("Bind DN:"), bindDnArg
+ .getDefaultValue() == null ? value : bindDnArg
+ .getDefaultValue());
+ }
+
+ try
+ {
+ return DN.valueOf(value);
+ }
+ catch (LocalizedIllegalArgumentException e)
+ {
+ throw new ArgumentException(e.getMessageObject());
+ }
+ }
+
+
+
+ private String getAuthID(String mech) throws CLIException,
+ ArgumentException
+ {
+ String value = null;
+ for (String s : saslOptionArg.getValues())
+ {
+ if (s.startsWith(SASL_PROPERTY_AUTHID))
+ {
+ value = parseSASLOptionValue(s);
+ break;
+ }
+ }
+ if (value == null && bindDnArg.isPresent())
+ {
+ value = "dn: " + bindDnArg.getValue();
+ }
+ if (value == null && app.isInteractive())
+ {
+ value = app.readInput(Message.raw("Authentication ID:"),
+ bindDnArg.getDefaultValue() == null ? null : "dn: "
+ + bindDnArg.getDefaultValue());
+ }
+ if (value == null)
+ {
+ Message message = ERR_LDAPAUTH_SASL_AUTHID_REQUIRED.get(mech);
+ throw new ArgumentException(message);
+ }
+ return value;
+ }
+
+
+
+ private String getAuthzID() throws CLIException, ArgumentException
+ {
+ String value = null;
+ for (String s : saslOptionArg.getValues())
+ {
+ if (s.startsWith(SASL_PROPERTY_AUTHZID))
+ {
+ value = parseSASLOptionValue(s);
+ break;
+ }
+ }
+ return value;
+ }
+
+
+
+ /**
+ * Get the password which has to be used for the command. If no
+ * password was specified, return null.
+ *
+ * @return The password stored into the specified file on by the
+ * command line argument, or null it if not specified.
+ */
+ private ByteString getPassword() throws CLIException
+ {
+ String value = "";
+ if (bindPasswordArg.isPresent())
+ {
+ value = bindPasswordArg.getValue();
+ }
+ else if (bindPasswordFileArg.isPresent())
+ {
+ value = bindPasswordFileArg.getValue();
+ }
+ if (value.length() == 0 && app.isInteractive())
+ {
+ value = app.readLineOfInput(Message.raw("Bind Password:"));
+ }
+
+ return ByteString.valueOf(value);
+ }
+
+
+
+ private String getRealm() throws ArgumentException, CLIException
+ {
+ String value = null;
+ for (String s : saslOptionArg.getValues())
+ {
+ if (s.startsWith(SASL_PROPERTY_REALM))
+ {
+ value = parseSASLOptionValue(s);
+ break;
+ }
+ }
+ return value;
+ }
+
+
+
+ private String getKDC() throws ArgumentException, CLIException
+ {
+ String value = null;
+ for (String s : saslOptionArg.getValues())
+ {
+ if (s.startsWith(SASL_PROPERTY_KDC))
+ {
+ value = parseSASLOptionValue(s);
+ break;
+ }
+ }
+ return value;
+ }
+
+
+
+ /**
+ * Returns <CODE>true</CODE> if we can read on the provided path and
+ * <CODE>false</CODE> otherwise.
+ *
+ * @param path
+ * the path.
+ * @return <CODE>true</CODE> if we can read on the provided path and
+ * <CODE>false</CODE> otherwise.
+ */
+ private boolean canRead(String path)
+ {
+ boolean canRead;
+ File file = new File(path);
+ canRead = file.exists() && file.canRead();
+ return canRead;
+ }
+
+
+
+ /**
+ * Retrieves a <CODE>TrustManager</CODE> object that may be used for
+ * interactions requiring access to a trust manager.
+ *
+ * @return A set of <CODE>TrustManager</CODE> objects that may be used
+ * for interactions requiring access to a trust manager.
+ * @throws KeyStoreException
+ * If a problem occurs while interacting with the trust
+ * store.
+ */
+ private TrustManager getTrustManager() throws KeyStoreException,
+ IOException, NoSuchAlgorithmException, CertificateException
+ {
+ if (trustAllArg.isPresent())
+ {
+ return new TrustAllTrustManager();
+ }
+
+ TrustStoreTrustManager tm = null;
+ if (trustStorePathArg.isPresent()
+ && trustStorePathArg.getValue().length() > 0)
+ {
+ tm = new TrustStoreTrustManager(trustStorePathArg.getValue(),
+ getTrustStorePIN(), hostNameArg.getValue(), true);
+ }
+ else if (getTrustStore() != null)
+ {
+ tm = new TrustStoreTrustManager(getTrustStore(),
+ getTrustStorePIN(), hostNameArg.getValue(), true);
+ }
+
+ if (app != null && !app.isQuiet())
+ {
+ return new PromptingTrustManager(app, tm);
+ }
+ return null;
+ }
+
+
+
+ /**
+ * Retrieves a <CODE>KeyManager</CODE> object that may be used for
+ * interactions requiring access to a key manager.
+ *
+ * @param keyStoreFile
+ * The path to the file containing the key store data.
+ * @return A set of <CODE>KeyManager</CODE> objects that may be used
+ * for interactions requiring access to a key manager.
+ * @throws java.security.KeyStoreException
+ * If a problem occurs while interacting with the key store.
+ */
+
+ private X509KeyManager getKeyManager(String keyStoreFile)
+ throws KeyStoreException, IOException, NoSuchAlgorithmException,
+ CertificateException
+ {
+ if (keyStoreFile == null)
+ {
+ // Lookup the file name through the JDK property.
+ keyStoreFile = getKeyStore();
+ }
+
+ if (keyStoreFile == null)
+ {
+ return null;
+ }
+
+ String keyStorePass = getKeyStorePIN();
+ char[] keyStorePIN = null;
+ if (keyStorePass != null)
+ {
+ keyStorePIN = keyStorePass.toCharArray();
+ }
+
+ FileInputStream fos = new FileInputStream(keyStoreFile);
+ KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
+ keystore.load(fos, keyStorePIN);
+ fos.close();
+
+ return new ApplicationKeyManager(keystore, keyStorePIN);
+ }
+
+
+
+ /**
+ * Read the KeyStore PIN from the JSSE system property.
+ *
+ * @return The PIN that should be used to access the key store.
+ */
+
+ private String getKeyStorePIN()
+ {
+ String pwd;
+ if (keyStorePasswordArg.isPresent())
+ {
+ pwd = keyStorePasswordArg.getValue();
+ }
+ else if (keyStorePasswordFileArg.isPresent())
+ {
+ pwd = keyStorePasswordFileArg.getValue();
+ }
+ else
+ {
+ pwd = System.getProperty("javax.net.ssl.keyStorePassword");
+ }
+ return pwd;
+ }
+
+
+
+ /**
+ * Read the TrustStore PIN from the JSSE system property.
+ *
+ * @return The PIN that should be used to access the trust store.
+ */
+
+ private String getTrustStorePIN()
+ {
+ String pwd;
+ if (trustStorePasswordArg.isPresent())
+ {
+ pwd = trustStorePasswordArg.getValue();
+ }
+ else if (trustStorePasswordFileArg.isPresent())
+ {
+ pwd = trustStorePasswordFileArg.getValue();
+ }
+ else
+ {
+ pwd = System.getProperty("javax.net.ssl.trustStorePassword");
+ }
+ return pwd;
+ }
+
+
+
+ /**
+ * Read the KeyStore from the JSSE system property.
+ *
+ * @return The path to the key store file.
+ */
+
+ private String getKeyStore()
+ {
+ return System.getProperty("javax.net.ssl.keyStore");
+ }
+
+
+
+ /**
+ * Read the TrustStore from the JSSE system property.
+ *
+ * @return The path to the trust store file.
+ */
+
+ private String getTrustStore()
+ {
+ return System.getProperty("javax.net.ssl.trustStore");
+ }
+
+
+
+ private String parseSASLOptionValue(String option)
+ throws ArgumentException
+ {
+ int equalPos = option.indexOf('=');
+ if (equalPos <= 0)
+ {
+ Message message = ERR_LDAP_CONN_CANNOT_PARSE_SASL_OPTION
+ .get(option);
+ throw new ArgumentException(message);
+ }
+
+ return option.substring(equalPos + 1, option.length());
+ }
+}
diff --git a/sdk/src/org/opends/sdk/tools/BooleanArgument.java b/sdk/src/org/opends/sdk/tools/BooleanArgument.java
new file mode 100644
index 0000000..3707350
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/BooleanArgument.java
@@ -0,0 +1,125 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2008 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.UtilityMessages.ERR_BOOLEANARG_NO_VALUE_ALLOWED;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+
+
+
+/**
+ * This class defines an argument type that will be used to represent
+ * Boolean values. These arguments will never take values from the
+ * command line but and will never be required. If the argument is
+ * provided, then it will be considered true, and if not then it will be
+ * considered false. As such, the default value will always be "false".
+ */
+final class BooleanArgument extends Argument
+{
+ /**
+ * Creates a new Boolean argument with the provided information.
+ *
+ * @param name
+ * The generic name that should be used to refer to this
+ * argument.
+ * @param shortIdentifier
+ * The single-character identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param longIdentifier
+ * The long identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param description
+ * Message for the description of this argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to
+ * create this argument.
+ */
+ public BooleanArgument(String name, Character shortIdentifier,
+ String longIdentifier, Message description)
+ throws ArgumentException
+ {
+ super(name, shortIdentifier, longIdentifier, false, false, false,
+ null, String.valueOf(false), null, description);
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in this
+ * argument.
+ *
+ * @param valueString
+ * The value for which to make the determination.
+ * @param invalidReason
+ * A buffer into which the invalid reason may be written if
+ * the value is not acceptable.
+ * @return <CODE>true</CODE> if the value is acceptable, or
+ * <CODE>false</CODE> if it is not.
+ */
+ public boolean valueIsAcceptable(String valueString,
+ MessageBuilder invalidReason)
+ {
+ // This argument type should never have a value, so any value
+ // provided will
+ // be unacceptable.
+
+ invalidReason
+ .append(ERR_BOOLEANARG_NO_VALUE_ALLOWED.get(getName()));
+
+ return false;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ final public void addValue(String valueString)
+ {
+ if (valueString != null)
+ {
+ clearValues();
+ super.addValue(valueString);
+ super.setPresent(Boolean.valueOf(valueString));
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ final public void setPresent(boolean isPresent)
+ {
+ addValue(String.valueOf(isPresent));
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/tools/DataSource.java b/sdk/src/org/opends/sdk/tools/DataSource.java
new file mode 100644
index 0000000..b469d97
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/DataSource.java
@@ -0,0 +1,481 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.tools;
+
+
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.*;
+
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * A source of data for performance tools.
+ */
+final class DataSource
+{
+ private static interface IDataSource
+ {
+ public Object getData();
+
+
+
+ public IDataSource duplicate();
+ }
+
+
+
+ private static class RandomNumberDataSource implements IDataSource
+ {
+ private final Random random;
+ private final int offset;
+ private final int range;
+
+
+
+ public RandomNumberDataSource(long seed, int low, int high)
+ {
+ random = new Random(seed);
+ offset = low;
+ range = high - low;
+ }
+
+
+
+ public Object getData()
+ {
+ return random.nextInt(range) + offset;
+ }
+
+
+
+ public IDataSource duplicate()
+ {
+ // There is no state info so threads can just share one instance.
+ return this;
+ }
+ }
+
+
+
+ private static class IncrementNumberDataSource implements IDataSource
+ {
+ private final int low;
+ private int next;
+ private final int high;
+
+
+
+ public IncrementNumberDataSource(int low, int high)
+ {
+ this.low = this.next = low;
+ this.high = high;
+ }
+
+
+
+ public Object getData()
+ {
+ if (next == high)
+ {
+ next = low;
+ return high;
+ }
+
+ return next++;
+ }
+
+
+
+ public IDataSource duplicate()
+ {
+ return new IncrementNumberDataSource(low, high);
+ }
+ }
+
+
+
+ private static class RandomLineFileDataSource implements IDataSource
+ {
+ private final List<String> lines;
+ private final Random random;
+
+
+
+ public RandomLineFileDataSource(long seed, String file)
+ throws IOException
+ {
+ lines = new ArrayList<String>();
+ random = new Random(seed);
+ BufferedReader in = new BufferedReader(new FileReader(file));
+ String line;
+ while ((line = in.readLine()) != null)
+ {
+ lines.add(line);
+ }
+ }
+
+
+
+ public Object getData()
+ {
+ return lines.get(random.nextInt(lines.size()));
+ }
+
+
+
+ public IDataSource duplicate()
+ {
+ return this;
+ }
+ }
+
+
+
+ private static class IncrementLineFileDataSource implements
+ IDataSource
+ {
+ private final List<String> lines;
+ private int next;
+
+
+
+ public IncrementLineFileDataSource(String file) throws IOException
+ {
+ lines = new ArrayList<String>();
+ BufferedReader in = new BufferedReader(new FileReader(file));
+ String line;
+ while ((line = in.readLine()) != null)
+ {
+ lines.add(line);
+ }
+ }
+
+
+
+ private IncrementLineFileDataSource(List<String> lines)
+ {
+ this.lines = lines;
+ }
+
+
+
+ public Object getData()
+ {
+ if (next == lines.size())
+ {
+ next = 0;
+ }
+
+ return lines.get(next++);
+ }
+
+
+
+ public IDataSource duplicate()
+ {
+ return new IncrementLineFileDataSource(lines);
+ }
+ }
+
+
+
+ private static class RandomStringDataSource implements IDataSource
+ {
+ private final Random random;
+ private final int length;
+ private final Character[] charSet;
+
+
+
+ private RandomStringDataSource(int seed, int length, String charSet)
+ {
+ this.length = length;
+ Set<Character> chars = new HashSet<Character>();
+ for (int i = 0; i < charSet.length(); i++)
+ {
+ char c = charSet.charAt(i);
+ if (c == '[')
+ {
+ i += 1;
+ char start = charSet.charAt(i);
+ i += 2;
+ char end = charSet.charAt(i);
+ i += 1;
+ for (int j = start; j <= end; j++)
+ {
+ chars.add((char) j);
+ }
+ }
+ else
+ {
+ chars.add(c);
+ }
+ }
+ this.charSet = chars.toArray(new Character[chars.size()]);
+ this.random = new Random(seed);
+ }
+
+
+
+ public Object getData()
+ {
+ char[] str = new char[length];
+ for (int i = 0; i < length; i++)
+ {
+ str[i] = charSet[random.nextInt(charSet.length)];
+ }
+ return new String(str);
+ }
+
+
+
+ public IDataSource duplicate()
+ {
+ return this;
+ }
+ }
+
+
+
+ private static class StaticDataSource implements IDataSource
+ {
+ private final Object data;
+
+
+
+ private StaticDataSource(Object data)
+ {
+ this.data = data;
+ }
+
+
+
+ public Object getData()
+ {
+ return data;
+ }
+
+
+
+ public IDataSource duplicate()
+ {
+ // There is no state info so threads can just share one instance.
+ return this;
+ }
+ }
+
+ private IDataSource impl;
+
+
+
+ private DataSource(IDataSource impl)
+ {
+ this.impl = impl;
+ }
+
+
+
+ public Object getData()
+ {
+ return impl.getData();
+ }
+
+
+
+ public DataSource duplicate()
+ {
+ IDataSource dup = impl.duplicate();
+ if (dup == impl)
+ {
+ return this;
+ }
+ else
+ {
+ return new DataSource(dup);
+ }
+ }
+
+
+
+ /**
+ * Parses a list of source definitions into an array of data source
+ * objects. A data source is defined as follows: - rand({min},{max})
+ * generates a random integer between the min and max. -
+ * rand({filename}) retrieves a random line from a file. -
+ * inc({min},{max}) returns incremental integer between the min and
+ * max. - inc({filename}) retrieves lines in order from a file. -
+ * {number} always return the integer as given. - {string} always
+ * return the string as given.
+ *
+ * @param sources
+ * The list of source definitions to parse.
+ * @return The array of parsed data sources.
+ * @throws IOException
+ * If an exception occurs while reading a file.
+ */
+ public static DataSource[] parse(List<String> sources)
+ throws IOException
+ {
+ Validator.ensureNotNull(sources);
+ DataSource[] dataSources = new DataSource[sources.size()];
+ for (int i = 0; i < sources.size(); i++)
+ {
+ String dataSourceDef = sources.get(i);
+ if (dataSourceDef.startsWith("rand(")
+ && dataSourceDef.endsWith(")"))
+ {
+ int lparenPos = dataSourceDef.indexOf("(");
+ int commaPos = dataSourceDef.indexOf(",");
+ int rparenPos = dataSourceDef.indexOf(")");
+ if (commaPos < 0)
+ {
+ // This is a file name
+ dataSources[i] =
+ new DataSource(new RandomLineFileDataSource(0,
+ dataSourceDef.substring(lparenPos + 1, rparenPos)));
+ }
+ else
+ {
+ // This range of integers
+ int low =
+ Integer.parseInt(dataSourceDef.substring(lparenPos + 1,
+ commaPos));
+ int high =
+ Integer.parseInt(dataSourceDef.substring(commaPos + 1,
+ rparenPos));
+ dataSources[i] =
+ new DataSource(new RandomNumberDataSource(0, low, high));
+ }
+ }
+ else if (dataSourceDef.startsWith("randstr(")
+ && dataSourceDef.endsWith(")"))
+ {
+ int lparenPos = dataSourceDef.indexOf("(");
+ int commaPos = dataSourceDef.indexOf(",");
+ int rparenPos = dataSourceDef.indexOf(")");
+ int length;
+ String charSet;
+ if (commaPos < 0)
+ {
+ length =
+ Integer.parseInt(dataSourceDef.substring(lparenPos + 1,
+ rparenPos));
+ charSet = "[A-Z][a-z][0-9]";
+ }
+ else
+ {
+ // length and charSet
+ length =
+ Integer.parseInt(dataSourceDef.substring(lparenPos + 1,
+ commaPos));
+ charSet = dataSourceDef.substring(commaPos + 1, rparenPos);
+ }
+ dataSources[i] =
+ new DataSource(new RandomStringDataSource(0, length,
+ charSet));
+
+ }
+ else if (dataSourceDef.startsWith("inc(")
+ && dataSourceDef.endsWith(")"))
+ {
+ int lparenPos = dataSourceDef.indexOf("(");
+ int commaPos = dataSourceDef.indexOf(",");
+ int rparenPos = dataSourceDef.indexOf(")");
+ if (commaPos < 0)
+ {
+ // This is a file name
+ dataSources[i] =
+ new DataSource(new IncrementLineFileDataSource(
+ dataSourceDef.substring(lparenPos + 1, rparenPos)));
+ }
+ else
+ {
+ int low =
+ Integer.parseInt(dataSourceDef.substring(lparenPos + 1,
+ commaPos));
+ int high =
+ Integer.parseInt(dataSourceDef.substring(commaPos + 1,
+ rparenPos));
+ dataSources[i] =
+ new DataSource(new IncrementNumberDataSource(low, high));
+ }
+ }
+ else
+ {
+ try
+ {
+ dataSources[i] =
+ new DataSource(new StaticDataSource(Integer
+ .parseInt(dataSourceDef)));
+ }
+ catch (NumberFormatException nfe)
+ {
+ dataSources[i] =
+ new DataSource(new StaticDataSource(dataSourceDef));
+ }
+ }
+ }
+
+ return dataSources;
+ }
+
+
+
+ /**
+ * Returns Generated data from the specified data sources. Generated
+ * data will be placed in the specified data array. If the data array
+ * is null or smaller than the number of data sources, one will be
+ * allocated.
+ *
+ * @param dataSources
+ * Data sources that will generate arguments referenced by
+ * the format specifiers in the format string.
+ * @param data
+ * The array where genereated data will be placed to format
+ * the string.
+ * @return A formatted string
+ */
+ public static Object[] generateData(DataSource[] dataSources,
+ Object[] data)
+ {
+ if (data == null || data.length < dataSources.length)
+ {
+ data = new Object[dataSources.length];
+ }
+ for (int i = 0; i < dataSources.length; i++)
+ {
+ data[i] = dataSources[i].getData();
+ }
+ return data;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/tools/FileBasedArgument.java b/sdk/src/org/opends/sdk/tools/FileBasedArgument.java
new file mode 100644
index 0000000..2225feb
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/FileBasedArgument.java
@@ -0,0 +1,283 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2008 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.UtilityMessages.*;
+import static org.opends.server.util.StaticUtils.getExceptionMessage;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.util.LinkedHashMap;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+
+
+
+/**
+ * This class defines an argument whose value will be read from a file
+ * rather than actually specified on the command-line. When a value is
+ * specified on the command line, it will be treated as the path to the
+ * file containing the actual value rather than the value itself. <BR>
+ * <BR>
+ * Note that if if no filename is provided on the command line but a
+ * default value is specified programatically or if the default value is
+ * read from a specified property, then that default value will be taken
+ * as the actual value rather than a filename. <BR>
+ * <BR>
+ * Also note that this argument type assumes that the entire value for
+ * the argument is on a single line in the specified file. If the file
+ * contains multiple lines, then only the first line will be read.
+ */
+final class FileBasedArgument extends Argument
+{
+ // The mapping between filenames specified and the first lines read
+ // from those
+ // files.
+ private LinkedHashMap<String, String> namesToValues;
+
+
+
+ /**
+ * Creates a new file-based argument with the provided information.
+ *
+ * @param name
+ * The generic name that should be used to refer to this
+ * argument.
+ * @param shortIdentifier
+ * The single-character identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param longIdentifier
+ * The long identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param isRequired
+ * Indicates whether this argument must be specified on the
+ * command line.
+ * @param valuePlaceholder
+ * The placeholder for the argument value that will be
+ * displayed in usage information, or <CODE>null</CODE> if
+ * this argument does not require a value.
+ * @param description
+ * Message for the description of this argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to
+ * create this argument.
+ */
+ public FileBasedArgument(String name, Character shortIdentifier,
+ String longIdentifier, boolean isRequired,
+ Message valuePlaceholder, Message description)
+ throws ArgumentException
+ {
+ super(name, shortIdentifier, longIdentifier, isRequired, false,
+ true, valuePlaceholder, null, null, description);
+
+ namesToValues = new LinkedHashMap<String, String>();
+ }
+
+
+
+ /**
+ * Creates a new file-based argument with the provided information.
+ *
+ * @param name
+ * The generic name that should be used to refer to this
+ * argument.
+ * @param shortIdentifier
+ * The single-character identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param longIdentifier
+ * The long identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param isRequired
+ * Indicates whether this argument must be specified on the
+ * command line.
+ * @param isMultiValued
+ * Indicates whether this argument may be specified more than
+ * once to provide multiple values.
+ * @param valuePlaceholder
+ * The placeholder for the argument value that will be
+ * displayed in usage information, or <CODE>null</CODE> if
+ * this argument does not require a value.
+ * @param defaultValue
+ * The default value that should be used for this argument if
+ * none is provided in a properties file or on the command
+ * line. This may be <CODE>null</CODE> if there is no generic
+ * default.
+ * @param propertyName
+ * The name of the property in a property file that may be
+ * used to override the default value but will be overridden
+ * by a command-line argument.
+ * @param description
+ * Message for the description of this argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to
+ * create this argument.
+ */
+ public FileBasedArgument(String name, Character shortIdentifier,
+ String longIdentifier, boolean isRequired, boolean isMultiValued,
+ Message valuePlaceholder, String defaultValue,
+ String propertyName, Message description)
+ throws ArgumentException
+ {
+ super(name, shortIdentifier, longIdentifier, isRequired,
+ isMultiValued, true, valuePlaceholder, defaultValue,
+ propertyName, description);
+
+ namesToValues = new LinkedHashMap<String, String>();
+ }
+
+
+
+ /**
+ * Retrieves a map between the filenames specified on the command line
+ * and the first lines read from those files.
+ *
+ * @return A map between the filenames specified on the command line
+ * and the first lines read from those files.
+ */
+ public LinkedHashMap<String, String> getNameToValueMap()
+ {
+ return namesToValues;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in this
+ * argument.
+ *
+ * @param valueString
+ * The value for which to make the determination.
+ * @param invalidReason
+ * A buffer into which the invalid reason may be written if
+ * the value is not acceptable.
+ * @return <CODE>true</CODE> if the value is acceptable, or
+ * <CODE>false</CODE> if it is not.
+ */
+ public boolean valueIsAcceptable(String valueString,
+ MessageBuilder invalidReason)
+ {
+ // First, make sure that the specified file exists.
+ File valueFile;
+ try
+ {
+ valueFile = new File(valueString);
+ if (!valueFile.exists())
+ {
+ invalidReason.append(ERR_FILEARG_NO_SUCH_FILE.get(valueString,
+ getName()));
+ return false;
+ }
+ }
+ catch (Exception e)
+ {
+ invalidReason.append(ERR_FILEARG_CANNOT_VERIFY_FILE_EXISTENCE
+ .get(valueString, getName(), getExceptionMessage(e)));
+ return false;
+ }
+
+ // Open the file for reading.
+ BufferedReader reader;
+ try
+ {
+ reader = new BufferedReader(new FileReader(valueFile));
+ }
+ catch (Exception e)
+ {
+ invalidReason.append(ERR_FILEARG_CANNOT_OPEN_FILE.get(
+ valueString, getName(), getExceptionMessage(e)));
+ return false;
+ }
+
+ // Read the first line and close the file.
+ String line;
+ try
+ {
+ line = reader.readLine();
+ }
+ catch (Exception e)
+ {
+ invalidReason.append(ERR_FILEARG_CANNOT_READ_FILE.get(
+ valueString, getName(), getExceptionMessage(e)));
+ return false;
+ }
+ finally
+ {
+ try
+ {
+ reader.close();
+ }
+ catch (Exception e)
+ {
+ }
+ }
+
+ // If the line read is null, then that means the file was empty.
+ if (line == null)
+ {
+
+ invalidReason.append(ERR_FILEARG_EMPTY_FILE.get(valueString,
+ getName()));
+ return false;
+ }
+
+ // Store the value in the hash so it will be available for addValue.
+ // We
+ // won't do any validation on the value itself, so anything that we
+ // read
+ // will be considered acceptable.
+ namesToValues.put(valueString, line);
+ return true;
+ }
+
+
+
+ /**
+ * Adds a value to the set of values for this argument. This should
+ * only be called if the value is allowed by the
+ * <CODE>valueIsAcceptable</CODE> method. Note that in this case,
+ * correct behavior depends on a previous successful call to
+ * <CODE>valueIsAcceptable</CODE> so that the value read from the file
+ * may be stored in the name-to-value hash and used in place of the
+ * filename here.
+ *
+ * @param valueString
+ * The string representation of the value to add to this
+ * argument.
+ */
+ public void addValue(String valueString)
+ {
+ String actualValue = namesToValues.get(valueString);
+ if (actualValue != null)
+ {
+ super.addValue(actualValue);
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/tools/IntegerArgument.java b/sdk/src/org/opends/sdk/tools/IntegerArgument.java
new file mode 100644
index 0000000..41f44f4
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/IntegerArgument.java
@@ -0,0 +1,547 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2008 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.UtilityMessages.ERR_ARG_CANNOT_DECODE_AS_INT;
+import static org.opends.messages.UtilityMessages.ERR_INTARG_LOWER_BOUND_ABOVE_UPPER_BOUND;
+import static org.opends.messages.UtilityMessages.ERR_INTARG_VALUE_ABOVE_UPPER_BOUND;
+import static org.opends.messages.UtilityMessages.ERR_INTARG_VALUE_BELOW_LOWER_BOUND;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+
+
+
+/**
+ * This class defines an argument type that will only accept integer
+ * values, and potentially only those in a given range.
+ */
+final class IntegerArgument extends Argument
+{
+ // Indicates whether a lower bound will be enforced for this argument.
+ private boolean hasLowerBound;
+
+ // Indicates whether an upper bound will be enforced for this
+ // argument.
+ private boolean hasUpperBound;
+
+ // The lower bound that will be enforced for this argument.
+ private double lowerBound;
+
+ // The upper bound that will be enforced for this argument.
+ private double upperBound;
+
+
+
+ /**
+ * Creates a new integer argument with the provided information.
+ *
+ * @param name
+ * The generic name that should be used to refer to this
+ * argument.
+ * @param shortIdentifier
+ * The single-character identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param longIdentifier
+ * The long identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param isRequired
+ * Indicates whether this argument must be specified on the
+ * command line.
+ * @param needsValue
+ * Indicates whether this argument requires a value.
+ * @param valuePlaceholder
+ * The placeholder for the argument value that will be
+ * displayed in usage information, or <CODE>null</CODE> if
+ * this argument does not require a value.
+ * @param description
+ * Message for the description of this argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to
+ * create this argument.
+ */
+ public IntegerArgument(String name, Character shortIdentifier,
+ String longIdentifier, boolean isRequired, boolean needsValue,
+ Message valuePlaceholder, Message description)
+ throws ArgumentException
+ {
+ super(name, shortIdentifier, longIdentifier, isRequired, false,
+ needsValue, valuePlaceholder, null, null, description);
+
+ hasLowerBound = false;
+ hasUpperBound = false;
+ lowerBound = Double.MIN_VALUE;
+ upperBound = Double.MAX_VALUE;
+ }
+
+
+
+ /**
+ * Creates a new integer argument with the provided information.
+ *
+ * @param name
+ * The generic name that should be used to refer to this
+ * argument.
+ * @param shortIdentifier
+ * The single-character identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param longIdentifier
+ * The long identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param isRequired
+ * Indicates whether this argument must be specified on the
+ * command line.
+ * @param needsValue
+ * Indicates whether this argument requires a value.
+ * @param valuePlaceholder
+ * The placeholder for the argument value that will be
+ * displayed in usage information, or <CODE>null</CODE> if
+ * this argument does not require a value.
+ * @param hasLowerBound
+ * Indicates whether a lower bound should be enforced for
+ * values of this argument.
+ * @param lowerBound
+ * The lower bound that should be enforced for values of this
+ * argument.
+ * @param hasUpperBound
+ * Indicates whether an upperbound should be enforced for
+ * values of this argument.
+ * @param upperBound
+ * The upper bound that should be enforced for values of this
+ * argument.
+ * @param description
+ * Message for the description of this argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to
+ * create this argument.
+ */
+ public IntegerArgument(String name, Character shortIdentifier,
+ String longIdentifier, boolean isRequired, boolean needsValue,
+ Message valuePlaceholder, boolean hasLowerBound,
+ double lowerBound, boolean hasUpperBound, double upperBound,
+ Message description) throws ArgumentException
+ {
+ super(name, shortIdentifier, longIdentifier, isRequired, false,
+ needsValue, valuePlaceholder, null, null, description);
+
+ this.hasLowerBound = hasLowerBound;
+ this.hasUpperBound = hasUpperBound;
+ this.lowerBound = lowerBound;
+ this.upperBound = upperBound;
+
+ if (hasLowerBound && hasUpperBound && (lowerBound > upperBound))
+ {
+ Message message =
+ ERR_INTARG_LOWER_BOUND_ABOVE_UPPER_BOUND.get(name,
+ lowerBound, upperBound);
+ throw new ArgumentException(message);
+ }
+ }
+
+
+
+ /**
+ * Creates a new integer argument with the provided information.
+ *
+ * @param name
+ * The generic name that should be used to refer to this
+ * argument.
+ * @param shortIdentifier
+ * The single-character identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param longIdentifier
+ * The long identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param isRequired
+ * Indicates whether this argument must be specified on the
+ * command line.
+ * @param isMultiValued
+ * Indicates whether this argument may be specified more than
+ * once to provide multiple values.
+ * @param needsValue
+ * Indicates whether this argument requires a value.
+ * @param valuePlaceholder
+ * The placeholder for the argument value that will be
+ * displayed in usage information, or <CODE>null</CODE> if
+ * this argument does not require a value.
+ * @param defaultValue
+ * The default value that should be used for this argument if
+ * none is provided in a properties file or on the command
+ * line. This may be <CODE>null</CODE> if there is no generic
+ * default.
+ * @param propertyName
+ * The name of the property in a property file that may be
+ * used to override the default value but will be overridden
+ * by a command-line argument.
+ * @param description
+ * Message for the description of this argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to
+ * create this argument.
+ */
+ public IntegerArgument(String name, Character shortIdentifier,
+ String longIdentifier, boolean isRequired, boolean isMultiValued,
+ boolean needsValue, Message valuePlaceholder, int defaultValue,
+ String propertyName, Message description)
+ throws ArgumentException
+ {
+ super(name, shortIdentifier, longIdentifier, isRequired,
+ isMultiValued, needsValue, valuePlaceholder, String
+ .valueOf(defaultValue), propertyName, description);
+
+ hasLowerBound = false;
+ hasUpperBound = false;
+ lowerBound = Integer.MIN_VALUE;
+ upperBound = Integer.MAX_VALUE;
+ }
+
+
+
+ /**
+ * Creates a new integer argument with the provided information.
+ *
+ * @param name
+ * The generic name that should be used to refer to this
+ * argument.
+ * @param shortIdentifier
+ * The single-character identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param longIdentifier
+ * The long identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param isRequired
+ * Indicates whether this argument must be specified on the
+ * command line.
+ * @param isMultiValued
+ * Indicates whether this argument may be specified more than
+ * once to provide multiple values.
+ * @param needsValue
+ * Indicates whether this argument requires a value.
+ * @param valuePlaceholder
+ * The placeholder for the argument value that will be
+ * displayed in usage information, or <CODE>null</CODE> if
+ * this argument does not require a value.
+ * @param defaultValue
+ * The default value that should be used for this argument if
+ * none is provided in a properties file or on the command
+ * line. This may be <CODE>null</CODE> if there is no generic
+ * default.
+ * @param propertyName
+ * The name of the property in a property file that may be
+ * used to override the default value but will be overridden
+ * by a command-line argument.
+ * @param description
+ * Message for the description of this argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to
+ * create this argument.
+ */
+ public IntegerArgument(String name, Character shortIdentifier,
+ String longIdentifier, boolean isRequired, boolean isMultiValued,
+ boolean needsValue, Message valuePlaceholder,
+ double defaultValue, String propertyName, Message description)
+ throws ArgumentException
+ {
+ super(name, shortIdentifier, longIdentifier, isRequired,
+ isMultiValued, needsValue, valuePlaceholder, String.format(
+ "%f", defaultValue), propertyName, description);
+
+ hasLowerBound = false;
+ hasUpperBound = false;
+ lowerBound = Integer.MIN_VALUE;
+ upperBound = Integer.MAX_VALUE;
+ }
+
+
+
+ /**
+ * Creates a new integer argument with the provided information.
+ *
+ * @param name
+ * The generic name that should be used to refer to this
+ * argument.
+ * @param shortIdentifier
+ * The single-character identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param longIdentifier
+ * The long identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param isRequired
+ * Indicates whether this argument must be specified on the
+ * command line.
+ * @param isMultiValued
+ * Indicates whether this argument may be specified more than
+ * once to provide multiple values.
+ * @param needsValue
+ * Indicates whether this argument requires a value.
+ * @param valuePlaceholder
+ * The placeholder for the argument value that will be
+ * displayed in usage information, or <CODE>null</CODE> if
+ * this argument does not require a value.
+ * @param defaultValue
+ * The default value that should be used for this argument if
+ * none is provided in a properties file or on the command
+ * line. This may be <CODE>null</CODE> if there is no generic
+ * default.
+ * @param propertyName
+ * The name of the property in a property file that may be
+ * used to override the default value but will be overridden
+ * by a command-line argument.
+ * @param hasLowerBound
+ * Indicates whether a lower bound should be enforced for
+ * values of this argument.
+ * @param lowerBound
+ * The lower bound that should be enforced for values of this
+ * argument.
+ * @param hasUpperBound
+ * Indicates whether an upperbound should be enforced for
+ * values of this argument.
+ * @param upperBound
+ * The upper bound that should be enforced for values of this
+ * argument.
+ * @param description
+ * Message for the description of this argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to
+ * create this argument.
+ */
+ public IntegerArgument(String name, Character shortIdentifier,
+ String longIdentifier, boolean isRequired, boolean isMultiValued,
+ boolean needsValue, Message valuePlaceholder, int defaultValue,
+ String propertyName, boolean hasLowerBound, double lowerBound,
+ boolean hasUpperBound, double upperBound, Message description)
+ throws ArgumentException
+ {
+ super(name, shortIdentifier, longIdentifier, isRequired,
+ isMultiValued, needsValue, valuePlaceholder, String
+ .valueOf(defaultValue), propertyName, description);
+
+ this.hasLowerBound = hasLowerBound;
+ this.hasUpperBound = hasUpperBound;
+ this.lowerBound = lowerBound;
+ this.upperBound = upperBound;
+
+ if (hasLowerBound && hasUpperBound && (lowerBound > upperBound))
+ {
+ Message message =
+ ERR_INTARG_LOWER_BOUND_ABOVE_UPPER_BOUND.get(name,
+ lowerBound, upperBound);
+ throw new ArgumentException(message);
+ }
+ }
+
+
+
+ /**
+ * Creates a new integer argument with the provided information.
+ *
+ * @param name
+ * The generic name that should be used to refer to this
+ * argument.
+ * @param shortIdentifier
+ * The single-character identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param longIdentifier
+ * The long identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param isRequired
+ * Indicates whether this argument must be specified on the
+ * command line.
+ * @param isMultiValued
+ * Indicates whether this argument may be specified more than
+ * once to provide multiple values.
+ * @param needsValue
+ * Indicates whether this argument requires a value.
+ * @param valuePlaceholder
+ * The placeholder for the argument value that will be
+ * displayed in usage information, or <CODE>null</CODE> if
+ * this argument does not require a value.
+ * @param defaultValue
+ * The default value that should be used for this argument if
+ * none is provided in a properties file or on the command
+ * line. This may be <CODE>null</CODE> if there is no generic
+ * default.
+ * @param propertyName
+ * The name of the property in a property file that may be
+ * used to override the default value but will be overridden
+ * by a command-line argument.
+ * @param hasLowerBound
+ * Indicates whether a lower bound should be enforced for
+ * values of this argument.
+ * @param lowerBound
+ * The lower bound that should be enforced for values of this
+ * argument.
+ * @param hasUpperBound
+ * Indicates whether an upperbound should be enforced for
+ * values of this argument.
+ * @param upperBound
+ * The upper bound that should be enforced for values of this
+ * argument.
+ * @param description
+ * Message for the description of this argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to
+ * create this argument.
+ */
+ public IntegerArgument(String name, Character shortIdentifier,
+ String longIdentifier, boolean isRequired, boolean isMultiValued,
+ boolean needsValue, Message valuePlaceholder,
+ double defaultValue, String propertyName, boolean hasLowerBound,
+ double lowerBound, boolean hasUpperBound, double upperBound,
+ Message description) throws ArgumentException
+ {
+ super(name, shortIdentifier, longIdentifier, isRequired,
+ isMultiValued, needsValue, valuePlaceholder, String
+ .valueOf(defaultValue), propertyName, description);
+
+ this.hasLowerBound = hasLowerBound;
+ this.hasUpperBound = hasUpperBound;
+ this.lowerBound = lowerBound;
+ this.upperBound = upperBound;
+
+ if (hasLowerBound && hasUpperBound && (lowerBound > upperBound))
+ {
+ Message message =
+ ERR_INTARG_LOWER_BOUND_ABOVE_UPPER_BOUND.get(name,
+ lowerBound, upperBound);
+ throw new ArgumentException(message);
+ }
+ }
+
+
+
+ /**
+ * Indicates whether a lower bound should be enforced for values of
+ * this argument.
+ *
+ * @return <CODE>true</CODE> if a lower bound should be enforced for
+ * values of this argument, or <CODE>false</CODE> if not.
+ */
+ public boolean hasLowerBound()
+ {
+ return hasLowerBound;
+ }
+
+
+
+ /**
+ * Retrieves the lower bound that may be enforced for values of this
+ * argument.
+ *
+ * @return The lower bound that may be enforced for values of this
+ * argument.
+ */
+ public double getLowerBound()
+ {
+ return lowerBound;
+ }
+
+
+
+ /**
+ * Indicates whether a upper bound should be enforced for values of
+ * this argument.
+ *
+ * @return <CODE>true</CODE> if a upper bound should be enforced for
+ * values of this argument, or <CODE>false</CODE> if not.
+ */
+ public boolean hasUpperBound()
+ {
+ return hasUpperBound;
+ }
+
+
+
+ /**
+ * Retrieves the upper bound that may be enforced for values of this
+ * argument.
+ *
+ * @return The upper bound that may be enforced for values of this
+ * argument.
+ */
+ public double getUpperBound()
+ {
+ return upperBound;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in this
+ * argument.
+ *
+ * @param valueString
+ * The value for which to make the determination.
+ * @param invalidReason
+ * A buffer into which the invalid reason may be written if
+ * the value is not acceptable.
+ * @return <CODE>true</CODE> if the value is acceptable, or
+ * <CODE>false</CODE> if it is not.
+ */
+ public boolean valueIsAcceptable(String valueString,
+ MessageBuilder invalidReason)
+ {
+ // First, the value must be decodable as an integer.
+ double intValue;
+ try
+ {
+ intValue = Double.parseDouble(valueString);
+ }
+ catch (Exception e)
+ {
+ invalidReason.append(ERR_ARG_CANNOT_DECODE_AS_INT.get(
+ valueString, getName()));
+ return false;
+ }
+
+ // If there is a lower bound, then the value must be greater than or
+ // equal
+ // to it.
+ if (hasLowerBound && (intValue < lowerBound))
+ {
+ invalidReason.append(ERR_INTARG_VALUE_BELOW_LOWER_BOUND.get(
+ getName(), intValue, lowerBound));
+ return false;
+ }
+
+ // If there is an upper bound, then the value must be less than or
+ // equal to
+ // it.
+ if (hasUpperBound && (intValue > upperBound))
+ {
+
+ invalidReason.append(ERR_INTARG_VALUE_ABOVE_UPPER_BOUND.get(
+ getName(), intValue, upperBound));
+ return false;
+ }
+
+ // At this point, the value should be acceptable.
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/tools/LDAPCompare.java b/sdk/src/org/opends/sdk/tools/LDAPCompare.java
new file mode 100644
index 0000000..8af5264
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/LDAPCompare.java
@@ -0,0 +1,674 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.ToolMessages.*;
+import static org.opends.server.tools.ToolConstants.*;
+import static org.opends.server.util.StaticUtils.*;
+
+import java.io.*;
+import java.util.ArrayList;
+
+import org.opends.messages.Message;
+import org.opends.sdk.*;
+import org.opends.sdk.controls.AssertionControl;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.controls.ProxiedAuthV2Control;
+import org.opends.sdk.requests.CompareRequest;
+import org.opends.sdk.requests.Requests;
+import org.opends.sdk.responses.Responses;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.Base64;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.server.util.cli.ConsoleApplication;
+
+
+
+/**
+ * This class provides a tool that can be used to issue compare requests
+ * to the Directory Server.
+ */
+public final class LDAPCompare extends ConsoleApplication
+{
+ private BooleanArgument verbose;
+
+
+
+ /**
+ * The main method for LDAPModify tool.
+ *
+ * @param args
+ * The command-line arguments provided to this program.
+ */
+
+ public static void main(String[] args)
+ {
+ int retCode = mainCompare(args, System.in, System.out, System.err);
+
+ if (retCode != 0)
+ {
+ System.exit(filterExitCode(retCode));
+ }
+ }
+
+
+
+ /**
+ * Parses the provided command-line arguments and uses that
+ * information to run the LDAPModify tool.
+ *
+ * @param args
+ * The command-line arguments provided to this program.
+ * @return The error code.
+ */
+
+ public static int mainCompare(String[] args)
+ {
+ return mainCompare(args, System.in, System.out, System.err);
+ }
+
+
+
+ /**
+ * Parses the provided command-line arguments and uses that
+ * information to run the LDAPModify tool.
+ *
+ * @param args
+ * The command-line arguments provided to this program.
+ * specified, the number of matching entries should be
+ * returned or not.
+ * @param inStream
+ * The input stream to use for standard input, or
+ * <CODE>null</CODE> if standard input is not needed.
+ * @param outStream
+ * The output stream to use for standard output, or
+ * <CODE>null</CODE> if standard output is not needed.
+ * @param errStream
+ * The output stream to use for standard error, or
+ * <CODE>null</CODE> if standard error is not needed.
+ * @return The error code.
+ */
+ public static int mainCompare(String[] args, InputStream inStream,
+ OutputStream outStream, OutputStream errStream)
+ {
+ return new LDAPCompare(inStream, outStream, errStream).run(args);
+ }
+
+
+
+ private LDAPCompare(InputStream in, OutputStream out, OutputStream err)
+ {
+ super(in, out, err);
+
+ }
+
+
+
+ private int run(String[] args)
+ {
+ // Create the command-line argument parser for use with this
+ // program.
+ Message toolDescription = INFO_LDAPCOMPARE_TOOL_DESCRIPTION.get();
+ ArgumentParser argParser =
+ new ArgumentParser(LDAPCompare.class.getName(),
+ toolDescription, false, true, 1, 0,
+ "attribute:value [DN ...]");
+ ArgumentParserConnectionFactory connectionFactory;
+
+ BooleanArgument continueOnError;
+ BooleanArgument noop;
+ BooleanArgument showUsage;
+ IntegerArgument version;
+ StringArgument assertionFilter;
+ StringArgument controlStr;
+ StringArgument encodingStr;
+ StringArgument filename;
+ StringArgument proxyAuthzID;
+ StringArgument propertiesFileArgument;
+ BooleanArgument noPropertiesFileArgument;
+
+ try
+ {
+ connectionFactory =
+ new ArgumentParserConnectionFactory(argParser, this);
+ propertiesFileArgument =
+ new StringArgument("propertiesFilePath", null,
+ OPTION_LONG_PROP_FILE_PATH, false, false, true,
+ INFO_PROP_FILE_PATH_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_PROP_FILE_PATH.get());
+ argParser.addArgument(propertiesFileArgument);
+ argParser.setFilePropertiesArgument(propertiesFileArgument);
+
+ noPropertiesFileArgument =
+ new BooleanArgument("noPropertiesFileArgument", null,
+ OPTION_LONG_NO_PROP_FILE, INFO_DESCRIPTION_NO_PROP_FILE
+ .get());
+ argParser.addArgument(noPropertiesFileArgument);
+ argParser.setNoPropertiesFileArgument(noPropertiesFileArgument);
+
+ filename =
+ new StringArgument("filename", OPTION_SHORT_FILENAME,
+ OPTION_LONG_FILENAME, false, false, true,
+ INFO_FILE_PLACEHOLDER.get(), null, null,
+ INFO_LDAPMODIFY_DESCRIPTION_FILENAME.get());
+ filename.setPropertyName(OPTION_LONG_FILENAME);
+ argParser.addArgument(filename);
+
+ proxyAuthzID =
+ new StringArgument("proxy_authzid", OPTION_SHORT_PROXYAUTHID,
+ OPTION_LONG_PROXYAUTHID, false, false, true,
+ INFO_PROXYAUTHID_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_PROXY_AUTHZID.get());
+ proxyAuthzID.setPropertyName(OPTION_LONG_PROXYAUTHID);
+ argParser.addArgument(proxyAuthzID);
+
+ assertionFilter =
+ new StringArgument("assertionfilter", null,
+ OPTION_LONG_ASSERTION_FILE, false, false, true,
+ INFO_ASSERTION_FILTER_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_ASSERTION_FILTER.get());
+ assertionFilter.setPropertyName(OPTION_LONG_ASSERTION_FILE);
+ argParser.addArgument(assertionFilter);
+
+ controlStr =
+ new StringArgument("control", 'J', "control", false, true,
+ true, INFO_LDAP_CONTROL_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_CONTROLS.get());
+ controlStr.setPropertyName("control");
+ argParser.addArgument(controlStr);
+
+ version =
+ new IntegerArgument("version", OPTION_SHORT_PROTOCOL_VERSION,
+ OPTION_LONG_PROTOCOL_VERSION, false, false, true,
+ INFO_PROTOCOL_VERSION_PLACEHOLDER.get(), 3, null,
+ INFO_DESCRIPTION_VERSION.get());
+ version.setPropertyName(OPTION_LONG_PROTOCOL_VERSION);
+ argParser.addArgument(version);
+
+ encodingStr =
+ new StringArgument("encoding", 'i', "encoding", false, false,
+ true, INFO_ENCODING_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_ENCODING.get());
+ encodingStr.setPropertyName("encoding");
+ argParser.addArgument(encodingStr);
+
+ continueOnError =
+ new BooleanArgument("continueOnError", 'c',
+ "continueOnError", INFO_DESCRIPTION_CONTINUE_ON_ERROR
+ .get());
+ continueOnError.setPropertyName("continueOnError");
+ argParser.addArgument(continueOnError);
+
+ noop =
+ new BooleanArgument("no-op", OPTION_SHORT_DRYRUN,
+ OPTION_LONG_DRYRUN, INFO_DESCRIPTION_NOOP.get());
+ noop.setPropertyName(OPTION_LONG_DRYRUN);
+ argParser.addArgument(noop);
+
+ verbose =
+ new BooleanArgument("verbose", 'v', "verbose",
+ INFO_DESCRIPTION_VERBOSE.get());
+ verbose.setPropertyName("verbose");
+ argParser.addArgument(verbose);
+
+ showUsage =
+ new BooleanArgument("showUsage", OPTION_SHORT_HELP,
+ OPTION_LONG_HELP, INFO_DESCRIPTION_SHOWUSAGE.get());
+ argParser.addArgument(showUsage);
+ argParser.setUsageArgument(showUsage, getOutputStream());
+ }
+ catch (ArgumentException ae)
+ {
+ Message message = ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage());
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ // Parse the command-line arguments provided to this program.
+ try
+ {
+ argParser.parseArguments(args);
+ connectionFactory.validate();
+ }
+ catch (ArgumentException ae)
+ {
+ Message message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
+ println(message);
+ println(argParser.getUsageMessage());
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ // If we should just display usage or version information,
+ // then print it and exit.
+ if (argParser.usageOrVersionDisplayed())
+ {
+ return 0;
+ }
+
+ try
+ {
+ int versionNumber = version.getIntValue();
+ if (versionNumber != 2 && versionNumber != 3)
+ {
+ println(ERR_DESCRIPTION_INVALID_VERSION.get(String
+ .valueOf(versionNumber)));
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ }
+ catch (ArgumentException ae)
+ {
+ println(ERR_DESCRIPTION_INVALID_VERSION.get(String
+ .valueOf(version.getValue())));
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ ArrayList<String> dnStrings = new ArrayList<String>();
+ ArrayList<String> attrAndDNStrings =
+ argParser.getTrailingArguments();
+
+ if (attrAndDNStrings.isEmpty())
+ {
+ Message message = ERR_LDAPCOMPARE_NO_ATTR.get();
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ // First element should be an attribute string.
+ String attributeString = attrAndDNStrings.remove(0);
+
+ // Rest are DN strings
+ for (String s : attrAndDNStrings)
+ {
+ dnStrings.add(s);
+ }
+
+ // If no DNs were provided, then exit with an error.
+ if (dnStrings.isEmpty() && (!filename.isPresent()))
+ {
+ println(ERR_LDAPCOMPARE_NO_DNS.get());
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ // If trailing DNs were provided and the filename argument was also
+ // provided, exit with an error.
+ if (!dnStrings.isEmpty() && filename.isPresent())
+ {
+ println(ERR_LDAPCOMPARE_FILENAME_AND_DNS.get());
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ // parse the attribute string
+ int idx = attributeString.indexOf(":");
+ if (idx == -1)
+ {
+ Message message =
+ ERR_LDAPCOMPARE_INVALID_ATTR_STRING.get(attributeString);
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ String attributeType = attributeString.substring(0, idx);
+ ByteString attributeVal;
+ String remainder =
+ attributeString.substring(idx + 1, attributeString.length());
+ if (remainder.length() > 0)
+ {
+ char nextChar = remainder.charAt(0);
+ if (nextChar == ':')
+ {
+ String base64 = remainder.substring(1, remainder.length());
+ try
+ {
+ attributeVal = Base64.decode(base64);
+ }
+ catch (LocalizedIllegalArgumentException e)
+ {
+ println(INFO_COMPARE_CANNOT_BASE64_DECODE_ASSERTION_VALUE
+ .get());
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ }
+ else if (nextChar == '<')
+ {
+ try
+ {
+ String filePath = remainder.substring(1, remainder.length());
+ attributeVal =
+ ByteString.wrap(Utils.readBytesFromFile(filePath));
+ }
+ catch (Exception e)
+ {
+ println(INFO_COMPARE_CANNOT_READ_ASSERTION_VALUE_FROM_FILE
+ .get(String.valueOf(e)));
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ }
+ else
+ {
+ attributeVal = ByteString.valueOf(remainder);
+ }
+ }
+ else
+ {
+ attributeVal = ByteString.valueOf(remainder);
+ }
+
+ CompareRequest compare =
+ Requests.newCompareRequest("", attributeType, attributeVal);
+
+ if (controlStr.isPresent())
+ {
+ for (String ctrlString : controlStr.getValues())
+ {
+ try
+ {
+ Control ctrl = Utils.getControl(ctrlString);
+ compare.addControl(ctrl);
+ }
+ catch (DecodeException de)
+ {
+ Message message =
+ ERR_TOOL_INVALID_CONTROL_STRING.get(ctrlString);
+ println(message);
+ ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ }
+ }
+
+ if (proxyAuthzID.isPresent())
+ {
+ Control proxyControl =
+ new ProxiedAuthV2Control(proxyAuthzID.getValue());
+ compare.addControl(proxyControl);
+ }
+
+ if (assertionFilter.isPresent())
+ {
+ String filterString = assertionFilter.getValue();
+ Filter filter;
+ try
+ {
+ filter = Filter.valueOf(filterString);
+
+ // FIXME -- Change this to the correct OID when the official one
+ // is
+ // assigned.
+ Control assertionControl = new AssertionControl(true, filter);
+ compare.addControl(assertionControl);
+ }
+ catch (LocalizedIllegalArgumentException le)
+ {
+ Message message =
+ ERR_LDAP_ASSERTION_INVALID_FILTER.get(le.getMessage());
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ }
+
+ BufferedReader rdr = null;
+ if (!filename.isPresent() && dnStrings.isEmpty())
+ {
+ // Read from stdin.
+ rdr = new BufferedReader(new InputStreamReader(System.in));
+ }
+ else if (filename.isPresent())
+ {
+ try
+ {
+ rdr = new BufferedReader(new FileReader(filename.getValue()));
+ }
+ catch (FileNotFoundException t)
+ {
+ println(ERR_LDAPCOMPARE_ERROR_READING_FILE.get(filename
+ .getValue(), t.toString()));
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ }
+
+ Connection connection = null;
+ if (!noop.isPresent())
+ {
+ try
+ {
+ connection = connectionFactory.getConnection();
+ }
+ catch (ErrorResultException ere)
+ {
+ println(Message.raw(ere.getMessage()));
+ return ere.getResult().getResultCode().intValue();
+ }
+ }
+
+ try
+ {
+ int result;
+ if (rdr == null)
+ {
+ for (String dn : dnStrings)
+ {
+ compare.setName(dn);
+ result = executeCompare(compare, connection);
+ if (result != 0 && !continueOnError.isPresent())
+ {
+ return result;
+ }
+ }
+ }
+ else
+ {
+ String dn;
+ try
+ {
+ while ((dn = rdr.readLine()) != null)
+ {
+ compare.setName(dn);
+ result = executeCompare(compare, connection);
+ if (result != 0 && !continueOnError.isPresent())
+ {
+ return result;
+ }
+ }
+ }
+ catch (IOException ioe)
+ {
+ println(ERR_LDAPCOMPARE_ERROR_READING_FILE.get(filename
+ .getValue(), ioe.toString()));
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ }
+ }
+ finally
+ {
+ if (connection != null)
+ {
+ connection.close();
+ }
+ if (rdr != null)
+ {
+ try
+ {
+ rdr.close();
+ }
+ catch (IOException ioe)
+ {
+ // Just ignore
+ }
+ }
+ }
+
+ return 0;
+ }
+
+
+
+ private int executeCompare(CompareRequest request,
+ Connection connection)
+ {
+ println(INFO_PROCESSING_COMPARE_OPERATION.get(request
+ .getAttributeDescription().toString(), request
+ .getAssertionValueAsString(), request.getName().toString()));
+ if (connection != null)
+ {
+ try
+ {
+ Result result;
+ try
+ {
+ result = connection.compare(request);
+ }
+ catch (InterruptedException e)
+ {
+ // This shouldn't happen because there are no other threads to
+ // interrupt this one.
+ result = Responses.newResult(
+ ResultCode.CLIENT_SIDE_USER_CANCELLED).setCause(e)
+ .setDiagnosticMessage(e.getLocalizedMessage());
+ throw ErrorResultException.wrap(result);
+ }
+
+ if (result.getResultCode() == ResultCode.COMPARE_FALSE)
+ {
+ println(INFO_COMPARE_OPERATION_RESULT_FALSE.get(request
+ .getName().toString()));
+ }
+ else
+ {
+
+ println(INFO_COMPARE_OPERATION_RESULT_TRUE.get(request
+ .getName().toString()));
+ }
+ }
+ catch (ErrorResultException ere)
+ {
+ Message msg = INFO_OPERATION_FAILED.get("COMPARE");
+ println(msg);
+ Result r = ere.getResult();
+ println(ERR_TOOL_RESULT_CODE.get(r.getResultCode().intValue(),
+ r.getResultCode().toString()));
+ if ((r.getDiagnosticMessage() != null)
+ && (r.getDiagnosticMessage().length() > 0))
+ {
+ println(Message.raw(r.getDiagnosticMessage()));
+ }
+ if (r.getMatchedDN() != null && r.getMatchedDN().length() > 0)
+ {
+ println(ERR_TOOL_MATCHED_DN.get(r.getMatchedDN()));
+ }
+ return r.getResultCode().intValue();
+ }
+ }
+ return ResultCode.SUCCESS.intValue();
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested advanced mode.
+ *
+ * @return Returns <code>true</code> if the user has requested
+ * advanced mode.
+ */
+ public boolean isAdvancedMode()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested interactive
+ * behavior.
+ *
+ * @return Returns <code>true</code> if the user has requested
+ * interactive behavior.
+ */
+ public boolean isInteractive()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not this console application is running in its
+ * menu-driven mode. This can be used to dictate whether output should
+ * go to the error stream or not. In addition, it may also dictate
+ * whether or not sub-menus should display a cancel option as well as
+ * a quit option.
+ *
+ * @return Returns <code>true</code> if this console application is
+ * running in its menu-driven mode.
+ */
+ public boolean isMenuDrivenMode()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested quiet output.
+ *
+ * @return Returns <code>true</code> if the user has requested quiet
+ * output.
+ */
+ public boolean isQuiet()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested script-friendly
+ * output.
+ *
+ * @return Returns <code>true</code> if the user has requested
+ * script-friendly output.
+ */
+ public boolean isScriptFriendly()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested verbose output.
+ *
+ * @return Returns <code>true</code> if the user has requested verbose
+ * output.
+ */
+ public boolean isVerbose()
+ {
+ return verbose.isPresent();
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/tools/LDAPModify.java b/sdk/src/org/opends/sdk/tools/LDAPModify.java
new file mode 100644
index 0000000..fdad8c0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/LDAPModify.java
@@ -0,0 +1,801 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.ToolMessages.*;
+import static org.opends.server.tools.ToolConstants.*;
+import static org.opends.server.util.StaticUtils.*;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.StringTokenizer;
+
+import org.opends.messages.Message;
+import org.opends.sdk.*;
+import org.opends.sdk.controls.*;
+import org.opends.sdk.ldif.*;
+import org.opends.sdk.requests.AddRequest;
+import org.opends.sdk.requests.DeleteRequest;
+import org.opends.sdk.requests.ModifyDNRequest;
+import org.opends.sdk.requests.ModifyRequest;
+import org.opends.sdk.responses.Responses;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.server.util.cli.ConsoleApplication;
+
+
+
+/**
+ * This class provides a tool that can be used to issue modify requests
+ * to the Directory Server.
+ */
+public final class LDAPModify extends ConsoleApplication
+{
+ private Connection connection;
+ private EntryWriter writer;
+ private Collection<Control> controls;
+ private BooleanArgument verbose;
+
+
+
+ /**
+ * The main method for LDAPModify tool.
+ *
+ * @param args
+ * The command-line arguments provided to this program.
+ */
+
+ public static void main(String[] args)
+ {
+ int retCode = mainModify(args, System.in, System.out, System.err);
+
+ if (retCode != 0)
+ {
+ System.exit(filterExitCode(retCode));
+ }
+ }
+
+
+
+ /**
+ * Parses the provided command-line arguments and uses that
+ * information to run the LDAPModify tool.
+ *
+ * @param args
+ * The command-line arguments provided to this program.
+ * @return The error code.
+ */
+
+ public static int mainModify(String[] args)
+ {
+ return mainModify(args, System.in, System.out, System.err);
+ }
+
+
+
+ /**
+ * Parses the provided command-line arguments and uses that
+ * information to run the LDAPModify tool.
+ *
+ * @param args
+ * The command-line arguments provided to this program.
+ * specified, the number of matching entries should be
+ * returned or not.
+ * @param inStream
+ * The input stream to use for standard input, or
+ * <CODE>null</CODE> if standard input is not needed.
+ * @param outStream
+ * The output stream to use for standard output, or
+ * <CODE>null</CODE> if standard output is not needed.
+ * @param errStream
+ * The output stream to use for standard error, or
+ * <CODE>null</CODE> if standard error is not needed.
+ * @return The error code.
+ */
+ public static int mainModify(String[] args, InputStream inStream,
+ OutputStream outStream, OutputStream errStream)
+ {
+ return new LDAPModify(inStream, outStream, errStream).run(args);
+ }
+
+
+
+ private LDAPModify(InputStream in, OutputStream out, OutputStream err)
+ {
+ super(in, out, err);
+
+ }
+
+
+
+ private int run(String[] args)
+ {
+ // Create the command-line argument parser for use with this
+ // program.
+ Message toolDescription = INFO_LDAPMODIFY_TOOL_DESCRIPTION.get();
+ ArgumentParser argParser =
+ new ArgumentParser(LDAPModify.class.getName(), toolDescription,
+ false);
+ ArgumentParserConnectionFactory connectionFactory;
+
+ BooleanArgument continueOnError;
+ // TODO: Remove this due to new LDIF reader api?
+ BooleanArgument defaultAdd;
+ BooleanArgument noop;
+ BooleanArgument showUsage;
+ IntegerArgument version;
+ StringArgument assertionFilter;
+ StringArgument controlStr;
+ StringArgument encodingStr;
+ StringArgument filename;
+ StringArgument postReadAttributes;
+ StringArgument preReadAttributes;
+ StringArgument proxyAuthzID;
+ StringArgument propertiesFileArgument;
+ BooleanArgument noPropertiesFileArgument;
+
+ try
+ {
+ connectionFactory =
+ new ArgumentParserConnectionFactory(argParser, this);
+ propertiesFileArgument =
+ new StringArgument("propertiesFilePath", null,
+ OPTION_LONG_PROP_FILE_PATH, false, false, true,
+ INFO_PROP_FILE_PATH_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_PROP_FILE_PATH.get());
+ argParser.addArgument(propertiesFileArgument);
+ argParser.setFilePropertiesArgument(propertiesFileArgument);
+
+ noPropertiesFileArgument =
+ new BooleanArgument("noPropertiesFileArgument", null,
+ OPTION_LONG_NO_PROP_FILE, INFO_DESCRIPTION_NO_PROP_FILE
+ .get());
+ argParser.addArgument(noPropertiesFileArgument);
+ argParser.setNoPropertiesFileArgument(noPropertiesFileArgument);
+
+ defaultAdd =
+ new BooleanArgument("defaultAdd", 'a', "defaultAdd",
+ INFO_MODIFY_DESCRIPTION_DEFAULT_ADD.get());
+ argParser.addArgument(defaultAdd);
+
+ filename =
+ new StringArgument("filename", OPTION_SHORT_FILENAME,
+ OPTION_LONG_FILENAME, false, false, true,
+ INFO_FILE_PLACEHOLDER.get(), null, null,
+ INFO_LDAPMODIFY_DESCRIPTION_FILENAME.get());
+ filename.setPropertyName(OPTION_LONG_FILENAME);
+ argParser.addArgument(filename);
+
+ proxyAuthzID =
+ new StringArgument("proxy_authzid", OPTION_SHORT_PROXYAUTHID,
+ OPTION_LONG_PROXYAUTHID, false, false, true,
+ INFO_PROXYAUTHID_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_PROXY_AUTHZID.get());
+ proxyAuthzID.setPropertyName(OPTION_LONG_PROXYAUTHID);
+ argParser.addArgument(proxyAuthzID);
+
+ assertionFilter =
+ new StringArgument("assertionfilter", null,
+ OPTION_LONG_ASSERTION_FILE, false, false, true,
+ INFO_ASSERTION_FILTER_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_ASSERTION_FILTER.get());
+ assertionFilter.setPropertyName(OPTION_LONG_ASSERTION_FILE);
+ argParser.addArgument(assertionFilter);
+
+ preReadAttributes =
+ new StringArgument("prereadattrs", null, "preReadAttributes",
+ false, false, true,
+ INFO_ATTRIBUTE_LIST_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_PREREAD_ATTRS.get());
+ preReadAttributes.setPropertyName("preReadAttributes");
+ argParser.addArgument(preReadAttributes);
+
+ postReadAttributes =
+ new StringArgument("postreadattrs", null,
+ "postReadAttributes", false, false, true,
+ INFO_ATTRIBUTE_LIST_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_POSTREAD_ATTRS.get());
+ postReadAttributes.setPropertyName("postReadAttributes");
+ argParser.addArgument(postReadAttributes);
+
+ controlStr =
+ new StringArgument("control", 'J', "control", false, true,
+ true, INFO_LDAP_CONTROL_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_CONTROLS.get());
+ controlStr.setPropertyName("control");
+ argParser.addArgument(controlStr);
+
+ version =
+ new IntegerArgument("version", OPTION_SHORT_PROTOCOL_VERSION,
+ OPTION_LONG_PROTOCOL_VERSION, false, false, true,
+ INFO_PROTOCOL_VERSION_PLACEHOLDER.get(), 3, null,
+ INFO_DESCRIPTION_VERSION.get());
+ version.setPropertyName(OPTION_LONG_PROTOCOL_VERSION);
+ argParser.addArgument(version);
+
+ encodingStr =
+ new StringArgument("encoding", 'i', "encoding", false, false,
+ true, INFO_ENCODING_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_ENCODING.get());
+ encodingStr.setPropertyName("encoding");
+ argParser.addArgument(encodingStr);
+
+ continueOnError =
+ new BooleanArgument("continueOnError", 'c',
+ "continueOnError", INFO_DESCRIPTION_CONTINUE_ON_ERROR
+ .get());
+ continueOnError.setPropertyName("continueOnError");
+ argParser.addArgument(continueOnError);
+
+ noop =
+ new BooleanArgument("no-op", OPTION_SHORT_DRYRUN,
+ OPTION_LONG_DRYRUN, INFO_DESCRIPTION_NOOP.get());
+ noop.setPropertyName(OPTION_LONG_DRYRUN);
+ argParser.addArgument(noop);
+
+ verbose =
+ new BooleanArgument("verbose", 'v', "verbose",
+ INFO_DESCRIPTION_VERBOSE.get());
+ verbose.setPropertyName("verbose");
+ argParser.addArgument(verbose);
+
+ showUsage =
+ new BooleanArgument("showUsage", OPTION_SHORT_HELP,
+ OPTION_LONG_HELP, INFO_DESCRIPTION_SHOWUSAGE.get());
+ argParser.addArgument(showUsage);
+ argParser.setUsageArgument(showUsage, getOutputStream());
+ }
+ catch (ArgumentException ae)
+ {
+ Message message = ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage());
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ // Parse the command-line arguments provided to this program.
+ try
+ {
+ argParser.parseArguments(args);
+ connectionFactory.validate();
+ }
+ catch (ArgumentException ae)
+ {
+ Message message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
+ println(message);
+ println(argParser.getUsageMessage());
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ // If we should just display usage or version information,
+ // then print it and exit.
+ if (argParser.usageOrVersionDisplayed())
+ {
+ return 0;
+ }
+
+ try
+ {
+ int versionNumber = version.getIntValue();
+ if (versionNumber != 2 && versionNumber != 3)
+ {
+ println(ERR_DESCRIPTION_INVALID_VERSION.get(String
+ .valueOf(versionNumber)));
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ }
+ catch (ArgumentException ae)
+ {
+ println(ERR_DESCRIPTION_INVALID_VERSION.get(String
+ .valueOf(version.getValue())));
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ // modifyOptions.setShowOperations(noop.isPresent());
+ // modifyOptions.setVerbose(verbose.isPresent());
+ // modifyOptions.setContinueOnError(continueOnError.isPresent());
+ // modifyOptions.setEncoding(encodingStr.getValue());
+ // modifyOptions.setDefaultAdd(defaultAdd.isPresent());
+
+ controls = new LinkedList<Control>();
+ if (controlStr.isPresent())
+ {
+ for (String ctrlString : controlStr.getValues())
+ {
+ try
+ {
+ Control ctrl = Utils.getControl(ctrlString);
+ controls.add(ctrl);
+ }
+ catch (DecodeException de)
+ {
+ Message message =
+ ERR_TOOL_INVALID_CONTROL_STRING.get(ctrlString);
+ println(message);
+ ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ }
+ }
+
+ if (proxyAuthzID.isPresent())
+ {
+ Control proxyControl =
+ new ProxiedAuthV2Control(proxyAuthzID.getValue());
+ controls.add(proxyControl);
+ }
+
+ if (assertionFilter.isPresent())
+ {
+ String filterString = assertionFilter.getValue();
+ Filter filter;
+ try
+ {
+ filter = Filter.valueOf(filterString);
+
+ // FIXME -- Change this to the correct OID when the official one
+ // is
+ // assigned.
+ Control assertionControl = new AssertionControl(true, filter);
+ controls.add(assertionControl);
+ }
+ catch (LocalizedIllegalArgumentException le)
+ {
+ Message message =
+ ERR_LDAP_ASSERTION_INVALID_FILTER.get(le.getMessage());
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ }
+
+ if (preReadAttributes.isPresent())
+ {
+ String valueStr = preReadAttributes.getValue();
+ StringTokenizer tokenizer = new StringTokenizer(valueStr, ", ");
+ PreReadControl.Request control = new PreReadControl.Request(true);
+ while (tokenizer.hasMoreTokens())
+ {
+ control.addAttribute(tokenizer.nextToken());
+ }
+ controls.add(control);
+ }
+
+ if (postReadAttributes.isPresent())
+ {
+ String valueStr = postReadAttributes.getValue();
+ StringTokenizer tokenizer = new StringTokenizer(valueStr, ", ");
+ PostReadControl.Request control =
+ new PostReadControl.Request(true);
+ while (tokenizer.hasMoreTokens())
+ {
+ control.addAttribute(tokenizer.nextToken());
+ }
+ controls.add(control);
+ }
+
+ if (!noop.isPresent())
+ {
+ try
+ {
+ connection = connectionFactory.getConnection();
+ }
+ catch (ErrorResultException ere)
+ {
+ return Utils.printErrorMessage(this, ere);
+ }
+ }
+
+ Utils.printPasswordPolicyResults(this, connection);
+
+ writer = new LDIFEntryWriter(getOutputStream());
+ VisitorImpl visitor = new VisitorImpl();
+ try
+ {
+ ChangeRecordReader reader;
+ if (filename.isPresent())
+ {
+ try
+ {
+ reader =
+ new LDIFChangeRecordReader(new FileInputStream(filename
+ .getValue()));
+ }
+ catch (Exception e)
+ {
+ Message message =
+ ERR_LDIF_FILE_CANNOT_OPEN_FOR_READ.get(filename
+ .getValue(), e.getLocalizedMessage());
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ }
+ else
+ {
+ reader = new LDIFChangeRecordReader(getInputStream());
+ }
+
+ ChangeRecord cr;
+ try
+ {
+ int result;
+ while ((cr = reader.readChangeRecord()) != null)
+ {
+ result = cr.accept(visitor, null);
+ if (result != 0 && !continueOnError.isPresent())
+ {
+ return result;
+ }
+ }
+ }
+ catch (IOException ioe)
+ {
+ Message message =
+ ERR_LDIF_FILE_READ_ERROR.get(filename.getValue(), ioe
+ .getLocalizedMessage());
+ println(message);
+ return ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue();
+ }
+ }
+ finally
+ {
+ if (connection != null)
+ {
+ connection.close();
+ }
+ }
+
+ return ResultCode.SUCCESS.intValue();
+ }
+
+
+
+ private class VisitorImpl implements
+ ChangeRecordVisitor<Integer, java.lang.Void>
+ {
+ private void printResult(String operationType, String name, Result r)
+ {
+ if (r.getResultCode() != ResultCode.SUCCESS
+ && r.getResultCode() != ResultCode.REFERRAL)
+ {
+ Message msg = INFO_OPERATION_FAILED.get(operationType);
+ println(msg);
+ println(ERR_TOOL_RESULT_CODE.get(r.getResultCode().intValue(),
+ r.getResultCode().toString()));
+ if ((r.getDiagnosticMessage() != null)
+ && (r.getDiagnosticMessage().length() > 0))
+ {
+ println(Message.raw(r.getDiagnosticMessage()));
+ }
+ if (r.getMatchedDN() != null && r.getMatchedDN().length() > 0)
+ {
+ println(ERR_TOOL_MATCHED_DN.get(r.getMatchedDN()));
+ }
+ }
+ else
+ {
+ Message msg =
+ INFO_OPERATION_SUCCESSFUL.get(operationType, name);
+ println(msg);
+ if ((r.getDiagnosticMessage() != null)
+ && (r.getDiagnosticMessage().length() > 0))
+ {
+ println(Message.raw(r.getDiagnosticMessage()));
+ }
+ if (r.getReferralURIs() != null)
+ {
+ for (String uri : r.getReferralURIs())
+ {
+ println(Message.raw(uri));
+ }
+ }
+ }
+
+ Control control =
+ r.getControl(PreReadControl.OID_LDAP_READENTRY_PREREAD);
+ if (control != null && control instanceof PreReadControl.Response)
+ {
+ PreReadControl.Response dc = (PreReadControl.Response) control;
+ println(INFO_LDAPMODIFY_PREREAD_ENTRY.get());
+ try
+ {
+ writer.writeEntry(dc.getSearchEntry());
+ }
+ catch (IOException ioe)
+ {
+ throw new RuntimeException(ioe);
+ }
+ }
+ control =
+ r.getControl(PostReadControl.OID_LDAP_READENTRY_POSTREAD);
+ if (control != null
+ && control instanceof PostReadControl.Response)
+ {
+ PostReadControl.Response dc =
+ (PostReadControl.Response) control;
+ println(INFO_LDAPMODIFY_POSTREAD_ENTRY.get());
+ try
+ {
+ writer.writeEntry(dc.getSearchEntry());
+ }
+ catch (IOException ioe)
+ {
+ throw new RuntimeException(ioe);
+ }
+ }
+ // TODO: CSN control
+ }
+
+
+
+ public Integer visitChangeRecord(Void aVoid, AddRequest change)
+ {
+ for (Control control : controls)
+ {
+ change.addControl(control);
+ }
+ String opType = "ADD";
+ println(INFO_PROCESSING_OPERATION.get(opType, change.getName()
+ .toString()));
+ if (connection != null)
+ {
+ try
+ {
+ Result r;
+ try
+ {
+ r = connection.add(change);
+ }
+ catch (InterruptedException e)
+ {
+ // This shouldn't happen because there are no other threads
+ // to interrupt this one.
+ r = Responses.newResult(
+ ResultCode.CLIENT_SIDE_USER_CANCELLED).setCause(e)
+ .setDiagnosticMessage(e.getLocalizedMessage());
+ throw ErrorResultException.wrap(r);
+ }
+ printResult(opType, change.getName().toString(), r);
+ return r.getResultCode().intValue();
+ }
+ catch (ErrorResultException ere)
+ {
+ return Utils.printErrorMessage(LDAPModify.this, ere);
+ }
+ }
+ return ResultCode.SUCCESS.intValue();
+ }
+
+
+
+ public Integer visitChangeRecord(Void aVoid, DeleteRequest change)
+ {
+ for (Control control : controls)
+ {
+ change.addControl(control);
+ }
+ String opType = "DELETE";
+ println(INFO_PROCESSING_OPERATION.get(opType, change.getName()
+ .toString()));
+ if (connection != null)
+ {
+ try
+ {
+ Result r;
+ try
+ {
+ r = connection.delete(change);
+ }
+ catch (InterruptedException e)
+ {
+ // This shouldn't happen because there are no other threads
+ // to interrupt this one.
+ r = Responses.newResult(
+ ResultCode.CLIENT_SIDE_USER_CANCELLED).setCause(e)
+ .setDiagnosticMessage(e.getLocalizedMessage());
+ throw ErrorResultException.wrap(r);
+ }
+ printResult(opType, change.getName().toString(), r);
+ return r.getResultCode().intValue();
+ }
+ catch (ErrorResultException ere)
+ {
+ return Utils.printErrorMessage(LDAPModify.this, ere);
+ }
+ }
+ return ResultCode.SUCCESS.intValue();
+ }
+
+
+
+ public Integer visitChangeRecord(Void aVoid, ModifyDNRequest change)
+ {
+ for (Control control : controls)
+ {
+ change.addControl(control);
+ }
+ String opType = "MODIFY DN";
+ println(INFO_PROCESSING_OPERATION.get(opType, change.getName()
+ .toString()));
+ if (connection != null)
+ {
+ try
+ {
+ Result r;
+ try
+ {
+ r = connection.modifyDN(change);
+ }
+ catch (InterruptedException e)
+ {
+ // This shouldn't happen because there are no other threads
+ // to interrupt this one.
+ r = Responses.newResult(
+ ResultCode.CLIENT_SIDE_USER_CANCELLED).setCause(e)
+ .setDiagnosticMessage(e.getLocalizedMessage());
+ throw ErrorResultException.wrap(r);
+ }
+ printResult(opType, change.getName().toString(), r);
+ return r.getResultCode().intValue();
+ }
+ catch (ErrorResultException ere)
+ {
+ return Utils.printErrorMessage(LDAPModify.this, ere);
+ }
+ }
+ return ResultCode.SUCCESS.intValue();
+ }
+
+
+
+ public Integer visitChangeRecord(Void aVoid, ModifyRequest change)
+ {
+ for (Control control : controls)
+ {
+ change.addControl(control);
+ }
+ String opType = "MODIFY";
+ println(INFO_PROCESSING_OPERATION.get(opType, change.getName()
+ .toString()));
+ if (connection != null)
+ {
+ try
+ {
+ Result r;
+ try
+ {
+ r = connection.modify(change);
+ }
+ catch (InterruptedException e)
+ {
+ // This shouldn't happen because there are no other threads
+ // to interrupt this one.
+ r = Responses.newResult(
+ ResultCode.CLIENT_SIDE_USER_CANCELLED).setCause(e)
+ .setDiagnosticMessage(e.getLocalizedMessage());
+ throw ErrorResultException.wrap(r);
+ }
+ printResult(opType, change.getName().toString(), r);
+ return r.getResultCode().intValue();
+ }
+ catch (ErrorResultException ere)
+ {
+ return Utils.printErrorMessage(LDAPModify.this, ere);
+ }
+ }
+ return ResultCode.SUCCESS.intValue();
+ }
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested advanced mode.
+ *
+ * @return Returns <code>true</code> if the user has requested
+ * advanced mode.
+ */
+ public boolean isAdvancedMode()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested interactive
+ * behavior.
+ *
+ * @return Returns <code>true</code> if the user has requested
+ * interactive behavior.
+ */
+ public boolean isInteractive()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not this console application is running in its
+ * menu-driven mode. This can be used to dictate whether output should
+ * go to the error stream or not. In addition, it may also dictate
+ * whether or not sub-menus should display a cancel option as well as
+ * a quit option.
+ *
+ * @return Returns <code>true</code> if this console application is
+ * running in its menu-driven mode.
+ */
+ public boolean isMenuDrivenMode()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested quiet output.
+ *
+ * @return Returns <code>true</code> if the user has requested quiet
+ * output.
+ */
+ public boolean isQuiet()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested script-friendly
+ * output.
+ *
+ * @return Returns <code>true</code> if the user has requested
+ * script-friendly output.
+ */
+ public boolean isScriptFriendly()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested verbose output.
+ *
+ * @return Returns <code>true</code> if the user has requested verbose
+ * output.
+ */
+ public boolean isVerbose()
+ {
+ return verbose.isPresent();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/tools/LDAPPasswordModify.java b/sdk/src/org/opends/sdk/tools/LDAPPasswordModify.java
new file mode 100644
index 0000000..6f51b57
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/LDAPPasswordModify.java
@@ -0,0 +1,475 @@
+package org.opends.sdk.tools;
+
+import static org.opends.messages.ToolMessages.*;
+import static org.opends.server.tools.ToolConstants.*;
+import static org.opends.server.util.StaticUtils.*;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.opends.messages.Message;
+import org.opends.sdk.Connection;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.ErrorResultException;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.extensions.PasswordModifyRequest;
+import org.opends.sdk.extensions.PasswordModifyResult;
+import org.opends.sdk.util.ByteString;
+import org.opends.server.util.cli.ConsoleApplication;
+
+/**
+ * This program provides a utility that uses the LDAP password modify extended
+ * operation to change the password for a user. It exposes the three primary
+ * options available for this operation, which are:
+ *
+ * <UL>
+ * <LI>The user identity whose password should be changed.</LI>
+ * <LI>The current password for the user.</LI>
+ * <LI>The new password for the user.
+ * </UL>
+ *
+ * All of these are optional components that may be included or omitted from the
+ * request.
+ */
+public class LDAPPasswordModify extends ConsoleApplication
+{
+ private BooleanArgument verbose;
+
+ /**
+ * Parses the command-line arguments, establishes a connection to the
+ * Directory Server, sends the password modify request, and reads the
+ * response.
+ *
+ * @param args The command-line arguments provided to this program.
+ */
+ public static void main(String[] args)
+ {
+ int retCode = mainPasswordModify(args, System.in, System.out, System.err);
+
+ if (retCode != 0)
+ {
+ System.exit(filterExitCode(retCode));
+ }
+ }
+
+
+
+ /**
+ * Parses the command-line arguments, establishes a connection to the
+ * Directory Server, sends the password modify request, and reads the
+ * response.
+ *
+ * @param args The command-line arguments provided to this program.
+ *
+ * @return An integer value of zero if everything completed successfully, or
+ * a nonzero value if an error occurred.
+ */
+ public static int mainPasswordModify(String[] args)
+ {
+ return mainPasswordModify(args, System.in, System.out, System.err);
+ }
+
+
+
+ /**
+ * Parses the provided command-line arguments and uses that
+ * information to run the LDAPPasswordModify tool.
+ *
+ * @param args
+ * The command-line arguments provided to this program.
+ * specified, the number of matching entries should be
+ * returned or not.
+ * @param inStream
+ * The input stream to use for standard input, or
+ * <CODE>null</CODE> if standard input is not needed.
+ * @param outStream
+ * The output stream to use for standard output, or
+ * <CODE>null</CODE> if standard output is not needed.
+ * @param errStream
+ * The output stream to use for standard error, or
+ * <CODE>null</CODE> if standard error is not needed.
+ * @return The error code.
+ */
+ public static int mainPasswordModify(String[] args, InputStream inStream,
+ OutputStream outStream, OutputStream errStream)
+ {
+ return new LDAPPasswordModify(inStream, outStream, errStream).run(args);
+ }
+
+
+
+ private LDAPPasswordModify(InputStream in, OutputStream out, OutputStream err)
+ {
+ super(in, out, err);
+
+ }
+
+ private int run(String[] args)
+ {
+ // Create the command-line argument parser for use with this
+ // program.
+ Message toolDescription = INFO_LDAPPWMOD_TOOL_DESCRIPTION.get();
+ ArgumentParser argParser =
+ new ArgumentParser(LDAPPasswordModify.class.getName(), toolDescription,
+ false);
+ ArgumentParserConnectionFactory connectionFactory;
+
+ FileBasedArgument currentPWFile;
+ FileBasedArgument newPWFile;
+ BooleanArgument showUsage;
+ IntegerArgument version;
+ StringArgument currentPW;
+ StringArgument controlStr;
+ StringArgument newPW;
+ StringArgument proxyAuthzID;
+ StringArgument propertiesFileArgument;
+ BooleanArgument noPropertiesFileArgument;
+
+ try
+ {
+ connectionFactory =
+ new ArgumentParserConnectionFactory(argParser, this);
+ propertiesFileArgument =
+ new StringArgument("propertiesFilePath", null,
+ OPTION_LONG_PROP_FILE_PATH, false, false, true,
+ INFO_PROP_FILE_PATH_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_PROP_FILE_PATH.get());
+ argParser.addArgument(propertiesFileArgument);
+ argParser.setFilePropertiesArgument(propertiesFileArgument);
+
+ noPropertiesFileArgument =
+ new BooleanArgument("noPropertiesFileArgument", null,
+ OPTION_LONG_NO_PROP_FILE, INFO_DESCRIPTION_NO_PROP_FILE
+ .get());
+ argParser.addArgument(noPropertiesFileArgument);
+ argParser.setNoPropertiesFileArgument(noPropertiesFileArgument);
+
+ newPW = new StringArgument("newpw", 'n', "newPassword", false, false,
+ true, INFO_NEW_PASSWORD_PLACEHOLDER.get(),
+ null, null,
+ INFO_LDAPPWMOD_DESCRIPTION_NEWPW.get());
+ newPW.setPropertyName("newPassword");
+ argParser.addArgument(newPW);
+
+
+ newPWFile = new FileBasedArgument(
+ "newpwfile", 'F', "newPasswordFile",
+ false, false, INFO_FILE_PLACEHOLDER.get(), null, null,
+ INFO_LDAPPWMOD_DESCRIPTION_NEWPWFILE.get());
+ newPWFile.setPropertyName("newPasswordFile");
+ argParser.addArgument(newPWFile);
+
+
+ currentPW =
+ new StringArgument("currentpw", 'c', "currentPassword", false, false,
+ true, INFO_CURRENT_PASSWORD_PLACEHOLDER.get(),
+ null, null,
+ INFO_LDAPPWMOD_DESCRIPTION_CURRENTPW.get());
+ currentPW.setPropertyName("currentPassword");
+ argParser.addArgument(currentPW);
+
+
+ currentPWFile =
+ new FileBasedArgument(
+ "currentpwfile", 'C', "currentPasswordFile",
+ false, false, INFO_FILE_PLACEHOLDER.get(), null, null,
+ INFO_LDAPPWMOD_DESCRIPTION_CURRENTPWFILE.get());
+ currentPWFile.setPropertyName("currentPasswordFile");
+ argParser.addArgument(currentPWFile);
+
+ proxyAuthzID =
+ new StringArgument("authzid", 'a', "authzID", false, false,
+ true, INFO_PROXYAUTHID_PLACEHOLDER.get(),
+ null, null,
+ INFO_LDAPPWMOD_DESCRIPTION_AUTHZID.get());
+ proxyAuthzID.setPropertyName("authzID");
+ argParser.addArgument(proxyAuthzID);
+
+ controlStr =
+ new StringArgument("control", 'J', "control", false, true,
+ true, INFO_LDAP_CONTROL_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_CONTROLS.get());
+ controlStr.setPropertyName("control");
+ argParser.addArgument(controlStr);
+
+ version =
+ new IntegerArgument("version", OPTION_SHORT_PROTOCOL_VERSION,
+ OPTION_LONG_PROTOCOL_VERSION, false, false, true,
+ INFO_PROTOCOL_VERSION_PLACEHOLDER.get(), 3, null,
+ INFO_DESCRIPTION_VERSION.get());
+ version.setPropertyName(OPTION_LONG_PROTOCOL_VERSION);
+ argParser.addArgument(version);
+
+ verbose =
+ new BooleanArgument("verbose", 'v', "verbose",
+ INFO_DESCRIPTION_VERBOSE.get());
+ verbose.setPropertyName("verbose");
+ argParser.addArgument(verbose);
+
+ showUsage =
+ new BooleanArgument("showUsage", OPTION_SHORT_HELP,
+ OPTION_LONG_HELP, INFO_DESCRIPTION_SHOWUSAGE.get());
+ argParser.addArgument(showUsage);
+ argParser.setUsageArgument(showUsage, getOutputStream());
+ }
+ catch (ArgumentException ae)
+ {
+ Message message = ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage());
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ // Parse the command-line arguments provided to this program.
+ try
+ {
+ argParser.parseArguments(args);
+ connectionFactory.validate();
+ }
+ catch (ArgumentException ae)
+ {
+ Message message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
+ println(message);
+ println(argParser.getUsageMessage());
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ // If we should just display usage or version information,
+ // then print it and exit.
+ if (argParser.usageOrVersionDisplayed())
+ {
+ return 0;
+ }
+
+ PasswordModifyRequest request = new PasswordModifyRequest();
+ try
+ {
+ int versionNumber = version.getIntValue();
+ if (versionNumber != 2 && versionNumber != 3)
+ {
+ println(ERR_DESCRIPTION_INVALID_VERSION.get(String
+ .valueOf(versionNumber)));
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ }
+ catch (ArgumentException ae)
+ {
+ println(ERR_DESCRIPTION_INVALID_VERSION.get(String
+ .valueOf(version.getValue())));
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ if (controlStr.isPresent())
+ {
+ for (String ctrlString : controlStr.getValues())
+ {
+ try
+ {
+ Control ctrl = Utils.getControl(ctrlString);
+ request.addControl(ctrl);
+ }
+ catch (DecodeException de)
+ {
+ Message message =
+ ERR_TOOL_INVALID_CONTROL_STRING.get(ctrlString);
+ println(message);
+ ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ }
+ }
+
+ if (newPW.isPresent() && newPWFile.isPresent())
+ {
+ Message message = ERR_LDAPPWMOD_CONFLICTING_ARGS.get(
+ newPW.getLongIdentifier(),
+ newPWFile.getLongIdentifier());
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ if (currentPW.isPresent() && currentPWFile.isPresent())
+ {
+ Message message = ERR_LDAPPWMOD_CONFLICTING_ARGS.get(
+ currentPW.getLongIdentifier(),
+ currentPWFile.getLongIdentifier());
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ Connection connection;
+ try
+ {
+ connection = connectionFactory.getConnection();
+ }
+ catch (ErrorResultException ere)
+ {
+ return Utils.printErrorMessage(this, ere);
+ }
+
+ if (proxyAuthzID.isPresent())
+ {
+ request.setUserIdentity(proxyAuthzID.getValue());
+ }
+
+ if (currentPW.isPresent())
+ {
+ request.setOldPassword(ByteString.valueOf(currentPW.getValue()));
+ }
+ else if (currentPWFile.isPresent())
+ {
+ request.setOldPassword(ByteString.valueOf(currentPWFile.getValue()));
+ }
+
+ if (newPW.isPresent())
+ {
+ request.setNewPassword(ByteString.valueOf(newPW.getValue()));
+ }
+ else if (newPWFile.isPresent())
+ {
+ request.setNewPassword(ByteString.valueOf(newPWFile.getValue()));
+ }
+
+ PasswordModifyResult result;
+ try
+ {
+ try
+ {
+ result = connection.extendedRequest(request);
+ }
+ catch (InterruptedException e)
+ {
+ // This shouldn't happen because there are no other threads to
+ // interrupt this one.
+ result = new PasswordModifyResult(
+ ResultCode.CLIENT_SIDE_USER_CANCELLED).setCause(e)
+ .setDiagnosticMessage(e.getLocalizedMessage());
+ throw ErrorResultException.wrap(result);
+ }
+ }
+ catch (ErrorResultException e)
+ {
+ Message message =
+ ERR_LDAPPWMOD_FAILED.get(e.getResult().getResultCode().intValue(),
+ e.getResult().getResultCode().toString());
+ println(message);
+
+ String errorMessage = e.getResult().getDiagnosticMessage();
+ if ((errorMessage != null) && (errorMessage.length() > 0))
+ {
+ message = ERR_LDAPPWMOD_FAILURE_ERROR_MESSAGE.get(errorMessage);
+ println(message);
+ }
+
+ String matchedDN = e.getResult().getMatchedDN();
+ if (matchedDN != null && matchedDN.length() > 0)
+ {
+ message = ERR_LDAPPWMOD_FAILURE_MATCHED_DN.get(matchedDN);
+ println(message);
+ }
+ return e.getResult().getResultCode().intValue();
+ }
+
+ Message message = INFO_LDAPPWMOD_SUCCESSFUL.get();
+ println(message);
+
+ String additionalInfo = result.getDiagnosticMessage();
+ if ((additionalInfo != null) && (additionalInfo.length() > 0))
+ {
+
+ message = INFO_LDAPPWMOD_ADDITIONAL_INFO.get(additionalInfo);
+ println(message);
+ }
+
+ if(result.getGenPassword() != null)
+ {
+ message = INFO_LDAPPWMOD_GENERATED_PASSWORD.get(result.getGenPassword().toString());
+ println(message);
+ }
+
+ return 0;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested advanced mode.
+ *
+ * @return Returns <code>true</code> if the user has requested
+ * advanced mode.
+ */
+ public boolean isAdvancedMode()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested interactive
+ * behavior.
+ *
+ * @return Returns <code>true</code> if the user has requested
+ * interactive behavior.
+ */
+ public boolean isInteractive()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not this console application is running in its
+ * menu-driven mode. This can be used to dictate whether output should
+ * go to the error stream or not. In addition, it may also dictate
+ * whether or not sub-menus should display a cancel option as well as
+ * a quit option.
+ *
+ * @return Returns <code>true</code> if this console application is
+ * running in its menu-driven mode.
+ */
+ public boolean isMenuDrivenMode()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested quiet output.
+ *
+ * @return Returns <code>true</code> if the user has requested quiet
+ * output.
+ */
+ public boolean isQuiet()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested script-friendly
+ * output.
+ *
+ * @return Returns <code>true</code> if the user has requested
+ * script-friendly output.
+ */
+ public boolean isScriptFriendly()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested verbose output.
+ *
+ * @return Returns <code>true</code> if the user has requested verbose
+ * output.
+ */
+ public boolean isVerbose()
+ {
+ return verbose.isPresent();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/tools/LDAPSearch.java b/sdk/src/org/opends/sdk/tools/LDAPSearch.java
new file mode 100644
index 0000000..bc443df
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/LDAPSearch.java
@@ -0,0 +1,1241 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.ToolMessages.*;
+import static org.opends.server.tools.ToolConstants.*;
+import static org.opends.server.util.ServerConstants.*;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.opends.messages.Message;
+import org.opends.sdk.*;
+import org.opends.sdk.controls.*;
+import org.opends.sdk.ldif.EntryWriter;
+import org.opends.sdk.ldif.LDIFEntryWriter;
+import org.opends.sdk.requests.Requests;
+import org.opends.sdk.requests.SearchRequest;
+import org.opends.sdk.responses.Responses;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.responses.SearchResultEntry;
+import org.opends.sdk.responses.SearchResultReference;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.server.util.cli.ConsoleApplication;
+
+
+
+/**
+ * This class provides a tool that can be used to issue search requests
+ * to the Directory Server.
+ */
+public final class LDAPSearch extends ConsoleApplication
+{
+ private BooleanArgument verbose;
+
+ private EntryWriter ldifWriter;
+
+
+
+ private class LDAPSearchResultHandler implements
+ SearchResultHandler<Void>
+ {
+ private int entryCount = 0;
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void handleEntry(Void p, SearchResultEntry entry)
+ {
+ entryCount++;
+
+ Control control = entry.getControl(OID_ENTRY_CHANGE_NOTIFICATION);
+ if (control != null
+ && control instanceof EntryChangeNotificationControl)
+ {
+ EntryChangeNotificationControl dc = (EntryChangeNotificationControl) control;
+ println(INFO_LDAPSEARCH_PSEARCH_CHANGE_TYPE.get(dc
+ .getChangeType().toString()));
+ String previousDN = dc.getPreviousDN();
+ if (previousDN != null)
+ {
+ println(INFO_LDAPSEARCH_PSEARCH_PREVIOUS_DN.get(previousDN));
+ }
+ }
+ control = entry.getControl(OID_ACCOUNT_USABLE_CONTROL);
+ if (control != null
+ && control instanceof AccountUsabilityControl.Response)
+ {
+ AccountUsabilityControl.Response dc = (AccountUsabilityControl.Response) control;
+
+ println(INFO_LDAPSEARCH_ACCTUSABLE_HEADER.get());
+ if (dc.isUsable())
+ {
+
+ println(INFO_LDAPSEARCH_ACCTUSABLE_IS_USABLE.get());
+ if (dc.getSecondsBeforeExpiration() > 0)
+ {
+ int timeToExp = dc.getSecondsBeforeExpiration();
+ Message timeToExpStr = Utils.secondsToTimeString(timeToExp);
+
+ println(INFO_LDAPSEARCH_ACCTUSABLE_TIME_UNTIL_EXPIRATION
+ .get(timeToExpStr));
+ }
+ }
+ else
+ {
+
+ println(INFO_LDAPSEARCH_ACCTUSABLE_NOT_USABLE.get());
+ if (dc.isInactive())
+ {
+ println(INFO_LDAPSEARCH_ACCTUSABLE_ACCT_INACTIVE.get());
+ }
+ if (dc.isReset())
+ {
+ println(INFO_LDAPSEARCH_ACCTUSABLE_PW_RESET.get());
+ }
+ if (dc.isExpired())
+ {
+ println(INFO_LDAPSEARCH_ACCTUSABLE_PW_EXPIRED.get());
+
+ if (dc.getRemainingGraceLogins() > 0)
+ {
+ println(INFO_LDAPSEARCH_ACCTUSABLE_REMAINING_GRACE.get(dc
+ .getRemainingGraceLogins()));
+ }
+ }
+ if (dc.isLocked())
+ {
+ println(INFO_LDAPSEARCH_ACCTUSABLE_LOCKED.get());
+ if (dc.getSecondsBeforeUnlock() > 0)
+ {
+ int timeToUnlock = dc.getSecondsBeforeUnlock();
+ Message timeToUnlockStr = Utils
+ .secondsToTimeString(timeToUnlock);
+
+ println(INFO_LDAPSEARCH_ACCTUSABLE_TIME_UNTIL_UNLOCK
+ .get(timeToUnlockStr));
+ }
+ }
+ }
+ }
+ try
+ {
+ ldifWriter.writeEntry(entry);
+ ldifWriter.flush();
+ }
+ catch (IOException ioe)
+ {
+ // Something is seriously wrong
+ throw new RuntimeException(ioe);
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void handleReference(Void p, SearchResultReference reference)
+ {
+ println(Message.raw(reference.toString()));
+ }
+ }
+
+
+
+ /**
+ * The main method for LDAPSearch tool.
+ *
+ * @param args
+ * The command-line arguments provided to this program.
+ */
+
+ public static void main(String[] args)
+ {
+ int retCode = mainSearch(args, false, System.in, System.out,
+ System.err);
+
+ if (retCode != 0)
+ {
+ System.exit(Utils.filterExitCode(retCode));
+ }
+ }
+
+
+
+ /**
+ * Parses the provided command-line arguments and uses that
+ * information to run the ldapsearch tool.
+ *
+ * @param args
+ * The command-line arguments provided to this program.
+ * @return The error code.
+ */
+
+ public static int mainSearch(String[] args)
+ {
+ return mainSearch(args, true, System.in, System.out, System.err);
+ }
+
+
+
+ /**
+ * Parses the provided command-line arguments and uses that
+ * information to run the ldapsearch tool.
+ *
+ * @param args
+ * The command-line arguments provided to this program.
+ * @param inStream
+ * The input stream to use for standard input, or
+ * <CODE>null</CODE> if standard input is not needed.
+ * @param outStream
+ * The output stream to use for standard output, or
+ * <CODE>null</CODE> if standard output is not needed.
+ * @param errStream
+ * The output stream to use for standard error, or
+ * <CODE>null</CODE> if standard error is not needed.
+ * @return The error code.
+ */
+ public static int mainSearch(String[] args, InputStream inStream,
+ OutputStream outStream, OutputStream errStream)
+ {
+ return mainSearch(args, true, inStream, outStream, errStream);
+ }
+
+
+
+ /**
+ * Parses the provided command-line arguments and uses that
+ * information to run the ldapsearch tool.
+ *
+ * @param args
+ * The command-line arguments provided to this program.
+ * @param returnMatchingEntries
+ * whether when the option --countEntries is specified, the
+ * number of matching entries should be returned or not.
+ * @param inStream
+ * The input stream to use for standard input, or
+ * <CODE>null</CODE> if standard input is not needed.
+ * @param outStream
+ * The output stream to use for standard output, or
+ * <CODE>null</CODE> if standard output is not needed.
+ * @param errStream
+ * The output stream to use for standard error, or
+ * <CODE>null</CODE> if standard error is not needed.
+ * @return The error code.
+ */
+
+ public static int mainSearch(String[] args,
+ boolean returnMatchingEntries, InputStream inStream,
+ OutputStream outStream, OutputStream errStream)
+ {
+ return new LDAPSearch(inStream, outStream, errStream).run(args,
+ returnMatchingEntries);
+ }
+
+
+
+ private LDAPSearch(InputStream in, OutputStream out, OutputStream err)
+ {
+ super(in, out, err);
+
+ }
+
+
+
+ private int run(String[] args, boolean returnMatchingEntries)
+ {
+ // Create the command-line argument parser for use with this
+ // program.
+ Message toolDescription = INFO_LDAPSEARCH_TOOL_DESCRIPTION.get();
+ ArgumentParser argParser = new ArgumentParser(LDAPSearch.class
+ .getName(), toolDescription, false, true, 0, 0,
+ "[filter] [attributes ...]");
+ ArgumentParserConnectionFactory connectionFactory;
+
+ BooleanArgument countEntries;
+ BooleanArgument dontWrap;
+ BooleanArgument noop;
+ BooleanArgument typesOnly;
+ IntegerArgument simplePageSize;
+ IntegerArgument timeLimit;
+ IntegerArgument version;
+ StringArgument baseDN;
+ StringArgument controlStr;
+ MultiChoiceArgument<DereferenceAliasesPolicy> dereferencePolicy;
+ StringArgument filename;
+ StringArgument matchedValuesFilter;
+ StringArgument pSearchInfo;
+ MultiChoiceArgument<SearchScope> searchScope;
+ StringArgument vlvDescriptor;
+ StringArgument effectiveRightsUser;
+ StringArgument effectiveRightsAttrs;
+ StringArgument sortOrder;
+ StringArgument proxyAuthzID;
+ StringArgument assertionFilter;
+ IntegerArgument sizeLimit;
+ try
+ {
+ connectionFactory = new ArgumentParserConnectionFactory(
+ argParser, this);
+ StringArgument propertiesFileArgument = new StringArgument(
+ "propertiesFilePath", null, OPTION_LONG_PROP_FILE_PATH,
+ false, false, true, INFO_PROP_FILE_PATH_PLACEHOLDER.get(),
+ null, null, INFO_DESCRIPTION_PROP_FILE_PATH.get());
+ argParser.addArgument(propertiesFileArgument);
+ argParser.setFilePropertiesArgument(propertiesFileArgument);
+
+ BooleanArgument noPropertiesFileArgument = new BooleanArgument(
+ "noPropertiesFileArgument", null, OPTION_LONG_NO_PROP_FILE,
+ INFO_DESCRIPTION_NO_PROP_FILE.get());
+ argParser.addArgument(noPropertiesFileArgument);
+ argParser.setNoPropertiesFileArgument(noPropertiesFileArgument);
+
+ baseDN = new StringArgument("baseDN", OPTION_SHORT_BASEDN,
+ OPTION_LONG_BASEDN, true, false, true,
+ INFO_BASEDN_PLACEHOLDER.get(), null, null,
+ INFO_SEARCH_DESCRIPTION_BASEDN.get());
+ baseDN.setPropertyName(OPTION_LONG_BASEDN);
+ argParser.addArgument(baseDN);
+
+ searchScope = new MultiChoiceArgument<SearchScope>("searchScope",
+ 's', "searchScope", false, true,
+ INFO_SEARCH_SCOPE_PLACEHOLDER.get(), SearchScope.values(),
+ false, INFO_SEARCH_DESCRIPTION_SEARCH_SCOPE.get());
+ searchScope.setPropertyName("searchScope");
+ searchScope.setDefaultValue(SearchScope.WHOLE_SUBTREE);
+ argParser.addArgument(searchScope);
+
+ filename = new StringArgument("filename", OPTION_SHORT_FILENAME,
+ OPTION_LONG_FILENAME, false, false, true,
+ INFO_FILE_PLACEHOLDER.get(), null, null,
+ INFO_SEARCH_DESCRIPTION_FILENAME.get());
+ searchScope.setPropertyName(OPTION_LONG_FILENAME);
+ argParser.addArgument(filename);
+
+ proxyAuthzID = new StringArgument("proxy_authzid",
+ OPTION_SHORT_PROXYAUTHID, OPTION_LONG_PROXYAUTHID, false,
+ false, true, INFO_PROXYAUTHID_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_PROXY_AUTHZID.get());
+ proxyAuthzID.setPropertyName(OPTION_LONG_PROXYAUTHID);
+ argParser.addArgument(proxyAuthzID);
+
+ pSearchInfo = new StringArgument("psearchinfo", 'C',
+ "persistentSearch", false, false, true,
+ INFO_PSEARCH_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_PSEARCH_INFO.get());
+ pSearchInfo.setPropertyName("persistentSearch");
+ argParser.addArgument(pSearchInfo);
+
+ simplePageSize = new IntegerArgument("simplepagesize", null,
+ "simplePageSize", false, false, true,
+ INFO_NUM_ENTRIES_PLACEHOLDER.get(), 1000, null, true, 1,
+ false, 0, INFO_DESCRIPTION_SIMPLE_PAGE_SIZE.get());
+ simplePageSize.setPropertyName("simplePageSize");
+ argParser.addArgument(simplePageSize);
+
+ assertionFilter = new StringArgument("assertionfilter", null,
+ OPTION_LONG_ASSERTION_FILE, false, false, true,
+ INFO_ASSERTION_FILTER_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_ASSERTION_FILTER.get());
+ assertionFilter.setPropertyName(OPTION_LONG_ASSERTION_FILE);
+ argParser.addArgument(assertionFilter);
+
+ matchedValuesFilter = new StringArgument("matchedvalues", null,
+ "matchedValuesFilter", false, true, true,
+ INFO_FILTER_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_MATCHED_VALUES_FILTER.get());
+ matchedValuesFilter.setPropertyName("matchedValuesFilter");
+ argParser.addArgument(matchedValuesFilter);
+
+ sortOrder = new StringArgument("sortorder", 'S', "sortOrder",
+ false, false, true, INFO_SORT_ORDER_PLACEHOLDER.get(), null,
+ null, INFO_DESCRIPTION_SORT_ORDER.get());
+ sortOrder.setPropertyName("sortOrder");
+ argParser.addArgument(sortOrder);
+
+ vlvDescriptor = new StringArgument("vlvdescriptor", 'G',
+ "virtualListView", false, false, true, INFO_VLV_PLACEHOLDER
+ .get(), null, null, INFO_DESCRIPTION_VLV.get());
+ vlvDescriptor.setPropertyName("virtualListView");
+ argParser.addArgument(vlvDescriptor);
+
+ controlStr = new StringArgument("control", 'J', "control", false,
+ true, true, INFO_LDAP_CONTROL_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_CONTROLS.get());
+ controlStr.setPropertyName("control");
+ argParser.addArgument(controlStr);
+
+ effectiveRightsUser = new StringArgument("effectiveRightsUser",
+ OPTION_SHORT_EFFECTIVERIGHTSUSER,
+ OPTION_LONG_EFFECTIVERIGHTSUSER, false, false, true,
+ INFO_PROXYAUTHID_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_EFFECTIVERIGHTS_USER.get());
+ effectiveRightsUser
+ .setPropertyName(OPTION_LONG_EFFECTIVERIGHTSUSER);
+ argParser.addArgument(effectiveRightsUser);
+
+ effectiveRightsAttrs = new StringArgument("effectiveRightsAttrs",
+ OPTION_SHORT_EFFECTIVERIGHTSATTR,
+ OPTION_LONG_EFFECTIVERIGHTSATTR, false, true, true,
+ INFO_ATTRIBUTE_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_EFFECTIVERIGHTS_ATTR.get());
+ effectiveRightsAttrs
+ .setPropertyName(OPTION_LONG_EFFECTIVERIGHTSATTR);
+ argParser.addArgument(effectiveRightsAttrs);
+
+ version = new IntegerArgument("version",
+ OPTION_SHORT_PROTOCOL_VERSION, OPTION_LONG_PROTOCOL_VERSION,
+ false, false, true, INFO_PROTOCOL_VERSION_PLACEHOLDER.get(),
+ 3, null, INFO_DESCRIPTION_VERSION.get());
+ version.setPropertyName(OPTION_LONG_PROTOCOL_VERSION);
+ argParser.addArgument(version);
+
+ StringArgument encodingStr = new StringArgument("encoding", 'i',
+ "encoding", false, false, true, INFO_ENCODING_PLACEHOLDER
+ .get(), null, null, INFO_DESCRIPTION_ENCODING.get());
+ encodingStr.setPropertyName("encoding");
+ argParser.addArgument(encodingStr);
+
+ dereferencePolicy = new MultiChoiceArgument<DereferenceAliasesPolicy>(
+ "derefpolicy", 'a', "dereferencePolicy", false, true,
+ INFO_DEREFERENCE_POLICE_PLACEHOLDER.get(),
+ DereferenceAliasesPolicy.values(), false,
+ INFO_SEARCH_DESCRIPTION_DEREFERENCE_POLICY.get());
+ dereferencePolicy.setPropertyName("dereferencePolicy");
+ dereferencePolicy.setDefaultValue(DereferenceAliasesPolicy.NEVER);
+ argParser.addArgument(dereferencePolicy);
+
+ typesOnly = new BooleanArgument("typesOnly", 'A', "typesOnly",
+ INFO_DESCRIPTION_TYPES_ONLY.get());
+ typesOnly.setPropertyName("typesOnly");
+ argParser.addArgument(typesOnly);
+
+ sizeLimit = new IntegerArgument("sizeLimit", 'z', "sizeLimit",
+ false, false, true, INFO_SIZE_LIMIT_PLACEHOLDER.get(), 0,
+ null, INFO_SEARCH_DESCRIPTION_SIZE_LIMIT.get());
+ sizeLimit.setPropertyName("sizeLimit");
+ argParser.addArgument(sizeLimit);
+
+ timeLimit = new IntegerArgument("timeLimit", 'l', "timeLimit",
+ false, false, true, INFO_TIME_LIMIT_PLACEHOLDER.get(), 0,
+ null, INFO_SEARCH_DESCRIPTION_TIME_LIMIT.get());
+ timeLimit.setPropertyName("timeLimit");
+ argParser.addArgument(timeLimit);
+
+ dontWrap = new BooleanArgument("dontwrap", 't', "dontWrap",
+ INFO_DESCRIPTION_DONT_WRAP.get());
+ dontWrap.setPropertyName("dontWrap");
+ argParser.addArgument(dontWrap);
+
+ countEntries = new BooleanArgument("countentries", null,
+ "countEntries", INFO_DESCRIPTION_COUNT_ENTRIES.get());
+ countEntries.setPropertyName("countEntries");
+ argParser.addArgument(countEntries);
+
+ BooleanArgument continueOnError = new BooleanArgument(
+ "continueOnError", 'c', "continueOnError",
+ INFO_DESCRIPTION_CONTINUE_ON_ERROR.get());
+ continueOnError.setPropertyName("continueOnError");
+ argParser.addArgument(continueOnError);
+
+ noop = new BooleanArgument("noop", OPTION_SHORT_DRYRUN,
+ OPTION_LONG_DRYRUN, INFO_DESCRIPTION_NOOP.get());
+ noop.setPropertyName(OPTION_LONG_DRYRUN);
+ argParser.addArgument(noop);
+
+ verbose = new BooleanArgument("verbose", 'v', "verbose",
+ INFO_DESCRIPTION_VERBOSE.get());
+ verbose.setPropertyName("verbose");
+ argParser.addArgument(verbose);
+
+ BooleanArgument showUsage = new BooleanArgument("showUsage",
+ OPTION_SHORT_HELP, OPTION_LONG_HELP,
+ INFO_DESCRIPTION_SHOWUSAGE.get());
+ argParser.addArgument(showUsage);
+ argParser.setUsageArgument(showUsage, getOutputStream());
+ }
+ catch (ArgumentException ae)
+ {
+ Message message = ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage());
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ // Parse the command-line arguments provided to this program.
+ try
+ {
+ argParser.parseArguments(args);
+ connectionFactory.validate();
+ }
+ catch (ArgumentException ae)
+ {
+ Message message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
+ println(message);
+ println(argParser.getUsageMessage());
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ // If we should just display usage or version information,
+ // then print it and exit.
+ if (argParser.usageOrVersionDisplayed())
+ {
+ return 0;
+ }
+
+ List<Filter> filters = new LinkedList<Filter>();
+ List<String> attributes = new LinkedList<String>();
+ ArrayList<String> filterAndAttributeStrings = argParser
+ .getTrailingArguments();
+ if (filterAndAttributeStrings.size() > 0)
+ {
+ // the list of trailing arguments should be structured as follow:
+ // - If a filter file is present, trailing arguments are
+ // considered as attributes
+ // - If filter file is not present, the first trailing argument is
+ // considered the filter, the other as attributes.
+ if (!filename.isPresent())
+ {
+ String filterString = filterAndAttributeStrings.remove(0);
+
+ try
+ {
+ filters.add(Filter.valueOf(filterString));
+ }
+ catch (LocalizedIllegalArgumentException e)
+ {
+ println(e.getMessageObject());
+ return ResultCode.CLIENT_SIDE_FILTER_ERROR.intValue();
+ }
+ }
+ // The rest are attributes
+ for (String s : filterAndAttributeStrings)
+ {
+ attributes.add(s);
+ }
+ }
+
+ if (filename.isPresent())
+ {
+ // Read the filter strings.
+ BufferedReader in = null;
+ try
+ {
+ in = new BufferedReader(new FileReader(filename.getValue()));
+ String line = null;
+
+ while ((line = in.readLine()) != null)
+ {
+ if (line.trim().equals(""))
+ {
+ // ignore empty lines.
+ continue;
+ }
+ Filter ldapFilter = Filter.valueOf(line);
+ filters.add(ldapFilter);
+ }
+ }
+ catch (LocalizedIllegalArgumentException e)
+ {
+ println(e.getMessageObject());
+ return ResultCode.CLIENT_SIDE_FILTER_ERROR.intValue();
+ }
+ catch (IOException e)
+ {
+ println(Message.raw(e.toString()));
+ return ResultCode.CLIENT_SIDE_FILTER_ERROR.intValue();
+ }
+ finally
+ {
+ if (in != null)
+ {
+ try
+ {
+ in.close();
+ }
+ catch (IOException ioe)
+ {
+ }
+ }
+ }
+ }
+
+ if (filters.isEmpty())
+ {
+ println(ERR_SEARCH_NO_FILTERS.get());
+ println(argParser.getUsageMessage());
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ SearchScope scope;
+ try
+ {
+ scope = searchScope.getTypedValue();
+ }
+ catch (ArgumentException ex1)
+ {
+ println(ex1.getMessageObject());
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ SearchRequest search;
+ try
+ {
+ search = Requests.newSearchRequest(DN.valueOf(baseDN.getValue()), scope, filters.get(0),
+ attributes.toArray(new String[attributes
+ .size()]));
+ }
+ catch (LocalizedIllegalArgumentException e)
+ {
+ println(e.getMessageObject());
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ // Read the LDAP version number.
+ try
+ {
+ int versionNumber = version.getIntValue();
+ if (versionNumber != 2 && versionNumber != 3)
+ {
+ println(ERR_DESCRIPTION_INVALID_VERSION.get(String
+ .valueOf(versionNumber)));
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ }
+ catch (ArgumentException ae)
+ {
+ println(ERR_DESCRIPTION_INVALID_VERSION.get(String
+ .valueOf(version.getValue())));
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ search.setTypesOnly(typesOnly.isPresent());
+ // searchOptions.setShowOperations(noop.isPresent());
+ // searchOptions.setVerbose(verbose.isPresent());
+ // searchOptions.setContinueOnError(continueOnError.isPresent());
+ // searchOptions.setEncoding(encodingStr.getValue());
+ // searchOptions.setCountMatchingEntries(countEntries.isPresent());
+ try
+ {
+ search.setTimeLimit(timeLimit.getIntValue());
+ search.setSizeLimit(sizeLimit.getIntValue());
+ }
+ catch (ArgumentException ex1)
+ {
+ println(ex1.getMessageObject());
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ try
+ {
+ search.setDereferenceAliasesPolicy(dereferencePolicy
+ .getTypedValue());
+ }
+ catch (ArgumentException ex1)
+ {
+ println(ex1.getMessageObject());
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ if (controlStr.isPresent())
+ {
+ for (String ctrlString : controlStr.getValues())
+ {
+ try
+ {
+ Control ctrl = Utils.getControl(ctrlString);
+ search.addControl(ctrl);
+ }
+ catch (DecodeException de)
+ {
+ Message message = ERR_TOOL_INVALID_CONTROL_STRING
+ .get(ctrlString);
+ println(message);
+ ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ }
+ }
+
+ if (effectiveRightsUser.isPresent())
+ {
+ String authzID = effectiveRightsUser.getValue();
+ if (!authzID.startsWith("dn:"))
+ {
+ Message message = ERR_EFFECTIVERIGHTS_INVALID_AUTHZID
+ .get(authzID);
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ Control effectiveRightsControl = new GetEffectiveRightsRequestControl(
+ false, authzID.substring(3), effectiveRightsAttrs.getValues()
+ .toArray(
+ new String[effectiveRightsAttrs.getValues().size()]));
+ search.addControl(effectiveRightsControl);
+ }
+
+ if (proxyAuthzID.isPresent())
+ {
+ Control proxyControl = new ProxiedAuthV2Control(proxyAuthzID
+ .getValue());
+ search.addControl(proxyControl);
+ }
+
+ if (pSearchInfo.isPresent())
+ {
+ String infoString = StaticUtils.toLowerCase(pSearchInfo
+ .getValue().trim());
+ boolean changesOnly = true;
+ boolean returnECs = true;
+
+ StringTokenizer tokenizer = new StringTokenizer(infoString, ":");
+
+ if (!tokenizer.hasMoreTokens())
+ {
+ Message message = ERR_PSEARCH_MISSING_DESCRIPTOR.get();
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ else
+ {
+ String token = tokenizer.nextToken();
+ if (!token.equals("ps"))
+ {
+ Message message = ERR_PSEARCH_DOESNT_START_WITH_PS.get(String
+ .valueOf(infoString));
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ }
+
+ ArrayList<PersistentSearchChangeType> ct = new ArrayList<PersistentSearchChangeType>(
+ 4);
+ if (tokenizer.hasMoreTokens())
+ {
+ StringTokenizer st = new StringTokenizer(tokenizer.nextToken(),
+ ", ");
+ if (!st.hasMoreTokens())
+ {
+ ct.add(PersistentSearchChangeType.ADD);
+ ct.add(PersistentSearchChangeType.DELETE);
+ ct.add(PersistentSearchChangeType.MODIFY);
+ ct.add(PersistentSearchChangeType.MODIFY_DN);
+ }
+ else
+ do
+ {
+ String token = st.nextToken();
+ if (token.equals("add"))
+ {
+ ct.add(PersistentSearchChangeType.ADD);
+ }
+ else if (token.equals("delete") || token.equals("del"))
+ {
+ ct.add(PersistentSearchChangeType.DELETE);
+ }
+ else if (token.equals("modify") || token.equals("mod"))
+ {
+ ct.add(PersistentSearchChangeType.MODIFY);
+ }
+ else if (token.equals("modifydn") || token.equals("moddn")
+ || token.equals("modrdn"))
+ {
+ ct.add(PersistentSearchChangeType.MODIFY_DN);
+ }
+ else if (token.equals("any") || token.equals("all"))
+ {
+ ct.add(PersistentSearchChangeType.ADD);
+ ct.add(PersistentSearchChangeType.DELETE);
+ ct.add(PersistentSearchChangeType.MODIFY);
+ ct.add(PersistentSearchChangeType.MODIFY_DN);
+ }
+ else
+ {
+ Message message = ERR_PSEARCH_INVALID_CHANGE_TYPE
+ .get(String.valueOf(token));
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ } while (st.hasMoreTokens());
+ }
+
+ if (tokenizer.hasMoreTokens())
+ {
+ String token = tokenizer.nextToken();
+ if (token.equals("1") || token.equals("true")
+ || token.equals("yes"))
+ {
+ changesOnly = true;
+ }
+ else if (token.equals("0") || token.equals("false")
+ || token.equals("no"))
+ {
+ changesOnly = false;
+ }
+ else
+ {
+ Message message = ERR_PSEARCH_INVALID_CHANGESONLY.get(String
+ .valueOf(token));
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ }
+
+ if (tokenizer.hasMoreTokens())
+ {
+ String token = tokenizer.nextToken();
+ if (token.equals("1") || token.equals("true")
+ || token.equals("yes"))
+ {
+ returnECs = true;
+ }
+ else if (token.equals("0") || token.equals("false")
+ || token.equals("no"))
+ {
+ returnECs = false;
+ }
+ else
+ {
+ Message message = ERR_PSEARCH_INVALID_RETURN_ECS.get(String
+ .valueOf(token));
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ }
+
+ PersistentSearchControl psearchControl = new PersistentSearchControl(
+ changesOnly, returnECs, ct
+ .toArray(new PersistentSearchChangeType[ct.size()]));
+ search.addControl(psearchControl);
+ }
+
+ if (assertionFilter.isPresent())
+ {
+ String filterString = assertionFilter.getValue();
+ Filter filter;
+ try
+ {
+ filter = Filter.valueOf(filterString);
+
+ // FIXME -- Change this to the correct OID when the official one
+ // is assigned.
+ Control assertionControl = new AssertionControl(true, filter);
+ search.addControl(assertionControl);
+ }
+ catch (LocalizedIllegalArgumentException le)
+ {
+ Message message = ERR_LDAP_ASSERTION_INVALID_FILTER.get(le
+ .getMessage());
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ }
+
+ if (matchedValuesFilter.isPresent())
+ {
+ LinkedList<String> mvFilterStrings = matchedValuesFilter
+ .getValues();
+ List<Filter> mvFilters = new ArrayList<Filter>();
+ for (String s : mvFilterStrings)
+ {
+ try
+ {
+ Filter f = Filter.valueOf(s);
+ mvFilters.add(f);
+ }
+ catch (LocalizedIllegalArgumentException le)
+ {
+ Message message = ERR_LDAP_MATCHEDVALUES_INVALID_FILTER
+ .get(le.getMessage());
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ }
+
+ MatchedValuesControl mvc = new MatchedValuesControl(true,
+ mvFilters.toArray(new Filter[mvFilters.size()]));
+ search.addControl(mvc);
+ }
+
+ if (sortOrder.isPresent())
+ {
+ try
+ {
+ search.addControl(new ServerSideSortControl.Request(false,
+ sortOrder.getValue()));
+ }
+ catch (DecodeException le)
+ {
+ Message message = ERR_LDAP_SORTCONTROL_INVALID_ORDER.get(le
+ .getMessageObject());
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ }
+
+ if (vlvDescriptor.isPresent())
+ {
+ if (!sortOrder.isPresent())
+ {
+ Message message = ERR_LDAPSEARCH_VLV_REQUIRES_SORT.get(
+ vlvDescriptor.getLongIdentifier(), sortOrder
+ .getLongIdentifier());
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ StringTokenizer tokenizer = new StringTokenizer(vlvDescriptor
+ .getValue(), ":");
+ int numTokens = tokenizer.countTokens();
+ if (numTokens == 3)
+ {
+ try
+ {
+ int beforeCount = Integer.parseInt(tokenizer.nextToken());
+ int afterCount = Integer.parseInt(tokenizer.nextToken());
+ ByteString assertionValue = ByteString.valueOf(tokenizer
+ .nextToken());
+ search.addControl(new VLVControl.Request(beforeCount,
+ afterCount, assertionValue));
+ }
+ catch (Exception e)
+ {
+ Message message = ERR_LDAPSEARCH_VLV_INVALID_DESCRIPTOR.get();
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ }
+ else if (numTokens == 4)
+ {
+ try
+ {
+ int beforeCount = Integer.parseInt(tokenizer.nextToken());
+ int afterCount = Integer.parseInt(tokenizer.nextToken());
+ int offset = Integer.parseInt(tokenizer.nextToken());
+ int contentCount = Integer.parseInt(tokenizer.nextToken());
+ search.addControl(new VLVControl.Request(beforeCount,
+ afterCount, offset, contentCount));
+ }
+ catch (Exception e)
+ {
+ Message message = ERR_LDAPSEARCH_VLV_INVALID_DESCRIPTOR.get();
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ }
+ else
+ {
+ Message message = ERR_LDAPSEARCH_VLV_INVALID_DESCRIPTOR.get();
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ }
+
+ int pageSize = 0;
+ if (simplePageSize.isPresent())
+ {
+ if (filters.size() > 1)
+ {
+ Message message = ERR_PAGED_RESULTS_REQUIRES_SINGLE_FILTER
+ .get();
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ try
+ {
+ pageSize = simplePageSize.getIntValue();
+ search.addControl(new PagedResultsControl(pageSize, ByteString
+ .empty()));
+ }
+ catch (ArgumentException ae)
+ {
+ Message message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+ }
+ /*
+ * if(connectionOptions.useSASLExternal()) {
+ * if(!connectionOptions.useSSL() &&
+ * !connectionOptions.useStartTLS()) { Message message =
+ * ERR_TOOL_SASLEXTERNAL_NEEDS_SSL_OR_TLS.get();
+ * err.println(wrapText(message, MAX_LINE_WIDTH)); return
+ * CLIENT_SIDE_PARAM_ERROR; } if(keyStorePathValue == null) {
+ * Message message = ERR_TOOL_SASLEXTERNAL_NEEDS_KEYSTORE.get();
+ * err.println(wrapText(message, MAX_LINE_WIDTH)); return
+ * CLIENT_SIDE_PARAM_ERROR; } }
+ * connectionOptions.setVerbose(verbose.isPresent());
+ */
+
+ int wrapColumn = 80;
+ if (dontWrap.isPresent())
+ {
+ wrapColumn = 0;
+ }
+
+ if (noop.isPresent())
+ {
+ // We don't actually need to open a connection or perform the
+ // search,
+ // so we're done. We should return 0 to either mean that the
+ // processing
+ // was successful or that there were no matching entries, based on
+ // countEntries.isPresent() (but in either case the return value
+ // should
+ // be zero).
+ return 0;
+ }
+
+ Connection connection;
+ try
+ {
+ connection = connectionFactory.getConnection();
+ }
+ catch (ErrorResultException ere)
+ {
+ return Utils.printErrorMessage(this, ere);
+ }
+
+ Utils.printPasswordPolicyResults(this, connection);
+
+ try
+ {
+ int filterIndex = 0;
+ ldifWriter = new LDIFEntryWriter(getOutputStream())
+ .setWrapColumn(wrapColumn);
+ LDAPSearchResultHandler resultHandler = new LDAPSearchResultHandler();
+ while (true)
+ {
+ Result result;
+ try
+ {
+ result = connection.search(search, resultHandler, null);
+ }
+ catch (InterruptedException e)
+ {
+ // This shouldn't happen because there are no other threads to
+ // interrupt this one.
+ result = Responses.newResult(
+ ResultCode.CLIENT_SIDE_USER_CANCELLED).setCause(e)
+ .setDiagnosticMessage(e.getLocalizedMessage());
+ throw ErrorResultException.wrap(result);
+ }
+
+ Control control = result
+ .getControl(ServerSideSortControl.OID_SERVER_SIDE_SORT_RESPONSE_CONTROL);
+ if (control != null
+ && control instanceof ServerSideSortControl.Response)
+ {
+ ServerSideSortControl.Response dc = (ServerSideSortControl.Response) control;
+ if (dc.getSortResult() != SortResult.SUCCESS)
+ {
+ Message msg = WARN_LDAPSEARCH_SORT_ERROR.get(dc
+ .getSortResult().toString());
+ println(msg);
+ }
+ }
+ control = result
+ .getControl(VLVControl.OID_VLV_RESPONSE_CONTROL);
+ if (control != null && control instanceof VLVControl.Response)
+ {
+ VLVControl.Response dc = (VLVControl.Response) control;
+ if (dc.getVLVResult() == VLVResult.SUCCESS)
+ {
+ Message msg = INFO_LDAPSEARCH_VLV_TARGET_OFFSET.get(dc
+ .getTargetPosition());
+ println(msg);
+
+ msg = INFO_LDAPSEARCH_VLV_CONTENT_COUNT.get(dc
+ .getContentCount());
+ println(msg);
+ }
+ else
+ {
+ Message msg = WARN_LDAPSEARCH_VLV_ERROR.get(dc
+ .getVLVResult().toString());
+ println(msg);
+ }
+ }
+
+ control = result
+ .getControl(PagedResultsControl.OID_PAGED_RESULTS_CONTROL);
+ if (control != null && control instanceof PagedResultsControl)
+ {
+ PagedResultsControl pagedControl = (PagedResultsControl) control;
+ if (pagedControl.getCookie().length() > 0)
+ {
+ if (!isQuiet())
+ {
+ pressReturnToContinue();
+ }
+ pagedControl = new PagedResultsControl(pageSize,
+ pagedControl.getCookie());
+ search
+ .removeControl(PagedResultsControl.OID_PAGED_RESULTS_CONTROL);
+ search.addControl(pagedControl);
+ continue;
+ }
+ }
+
+ println();
+ println(ERR_TOOL_RESULT_CODE.get(result.getResultCode()
+ .intValue(), result.getResultCode().toString()));
+ if ((result.getDiagnosticMessage() != null)
+ && (result.getDiagnosticMessage().length() > 0))
+ {
+ println(Message.raw(result.getDiagnosticMessage()));
+ }
+ if (result.getMatchedDN() != null
+ && result.getMatchedDN().length() > 0)
+ {
+ println(ERR_TOOL_MATCHED_DN.get(result.getMatchedDN()));
+ }
+
+ filterIndex++;
+ if (filterIndex < filters.size())
+ {
+ search.setFilter(filters.get(filterIndex));
+ }
+ else
+ {
+ break;
+ }
+ }
+ if (countEntries.isPresent() && !isQuiet())
+ {
+ Message message = INFO_LDAPSEARCH_MATCHING_ENTRY_COUNT
+ .get(resultHandler.entryCount);
+ println(message);
+ println();
+ }
+ }
+ catch (ErrorResultException ere)
+ {
+ return Utils.printErrorMessage(this, ere);
+ }
+ finally
+ {
+ connection.close();
+ }
+
+ return 0;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested advanced mode.
+ *
+ * @return Returns <code>true</code> if the user has requested
+ * advanced mode.
+ */
+ public boolean isAdvancedMode()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested interactive
+ * behavior.
+ *
+ * @return Returns <code>true</code> if the user has requested
+ * interactive behavior.
+ */
+ public boolean isInteractive()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether or not this console application is running in its
+ * menu-driven mode. This can be used to dictate whether output should
+ * go to the error stream or not. In addition, it may also dictate
+ * whether or not sub-menus should display a cancel option as well as
+ * a quit option.
+ *
+ * @return Returns <code>true</code> if this console application is
+ * running in its menu-driven mode.
+ */
+ public boolean isMenuDrivenMode()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested quiet output.
+ *
+ * @return Returns <code>true</code> if the user has requested quiet
+ * output.
+ */
+ public boolean isQuiet()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested script-friendly
+ * output.
+ *
+ * @return Returns <code>true</code> if the user has requested
+ * script-friendly output.
+ */
+ public boolean isScriptFriendly()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested verbose output.
+ *
+ * @return Returns <code>true</code> if the user has requested verbose
+ * output.
+ */
+ public boolean isVerbose()
+ {
+ return verbose.isPresent();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/tools/ModRate.java b/sdk/src/org/opends/sdk/tools/ModRate.java
new file mode 100644
index 0000000..9643894
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/ModRate.java
@@ -0,0 +1,431 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.ToolMessages.*;
+import static org.opends.server.tools.ToolConstants.*;
+import static org.opends.server.util.StaticUtils.filterExitCode;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.opends.messages.Message;
+import org.opends.sdk.*;
+import org.opends.sdk.requests.ModifyRequest;
+import org.opends.sdk.requests.Requests;
+import org.opends.sdk.responses.Result;
+import org.opends.server.util.cli.ConsoleApplication;
+
+
+
+/**
+ * Modrate benchmarking tool.
+ */
+public final class ModRate extends ConsoleApplication
+{
+ private BooleanArgument verbose;
+
+
+
+ /**
+ * The main method for SearchRate tool.
+ *
+ * @param args
+ * The command-line arguments provided to this program.
+ */
+
+ public static void main(String[] args)
+ {
+ int retCode = mainModRate(args, System.in, System.out, System.err);
+
+ if (retCode != 0)
+ {
+ System.exit(filterExitCode(retCode));
+ }
+ }
+
+
+
+ /**
+ * Parses the provided command-line arguments and uses that
+ * information to run the ldapsearch tool.
+ *
+ * @param args
+ * The command-line arguments provided to this program.
+ * @return The error code.
+ */
+
+ public static int mainSearchRate(String[] args)
+ {
+ return mainModRate(args, System.in, System.out, System.err);
+ }
+
+
+
+ /**
+ * Parses the provided command-line arguments and uses that
+ * information to run the ldapsearch tool.
+ *
+ * @param args
+ * The command-line arguments provided to this program.
+ * @param inStream
+ * The input stream to use for standard input, or
+ * <CODE>null</CODE> if standard input is not needed.
+ * @param outStream
+ * The output stream to use for standard output, or
+ * <CODE>null</CODE> if standard output is not needed.
+ * @param errStream
+ * The output stream to use for standard error, or
+ * <CODE>null</CODE> if standard error is not needed.
+ * @return The error code.
+ */
+
+ public static int mainModRate(String[] args, InputStream inStream,
+ OutputStream outStream, OutputStream errStream)
+
+ {
+ return new ModRate(inStream, outStream, errStream).run(args);
+ }
+
+
+
+ private ModRate(InputStream in, OutputStream out, OutputStream err)
+ {
+ super(in, out, err);
+
+ }
+
+
+
+ private int run(String[] args)
+ {
+ // Create the command-line argument parser for use with this
+ // program.
+ Message toolDescription =
+ Message.raw("This utility can be used to "
+ + "measure modify performance");
+ // TODO: correct usage
+ ArgumentParser argParser =
+ new ArgumentParser(SearchRate.class.getName(), toolDescription,
+ false, true, 1, 0, "[modifyString ...]");
+ ArgumentParserConnectionFactory connectionFactory;
+ ModifyPerformanceRunner runner;
+
+ BooleanArgument showUsage;
+ StringArgument propertiesFileArgument;
+ BooleanArgument noPropertiesFileArgument;
+ StringArgument baseDN;
+
+ try
+ {
+ connectionFactory =
+ new ArgumentParserConnectionFactory(argParser, this);
+ runner = new ModifyPerformanceRunner(argParser, this);
+ propertiesFileArgument =
+ new StringArgument("propertiesFilePath", null,
+ OPTION_LONG_PROP_FILE_PATH, false, false, true,
+ INFO_PROP_FILE_PATH_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_PROP_FILE_PATH.get());
+ argParser.addArgument(propertiesFileArgument);
+ argParser.setFilePropertiesArgument(propertiesFileArgument);
+
+ noPropertiesFileArgument =
+ new BooleanArgument("noPropertiesFileArgument", null,
+ OPTION_LONG_NO_PROP_FILE, INFO_DESCRIPTION_NO_PROP_FILE
+ .get());
+ argParser.addArgument(noPropertiesFileArgument);
+ argParser.setNoPropertiesFileArgument(noPropertiesFileArgument);
+
+ baseDN =
+ new StringArgument("baseDN", OPTION_SHORT_BASEDN,
+ OPTION_LONG_BASEDN, true, false, true,
+ INFO_BASEDN_PLACEHOLDER.get(), null, null,
+ INFO_SEARCH_DESCRIPTION_BASEDN.get());
+ baseDN.setPropertyName(OPTION_LONG_BASEDN);
+ argParser.addArgument(baseDN);
+
+ verbose =
+ new BooleanArgument("verbose", 'v', "verbose",
+ INFO_DESCRIPTION_VERBOSE.get());
+ verbose.setPropertyName("verbose");
+ argParser.addArgument(verbose);
+
+ showUsage =
+ new BooleanArgument("showUsage", OPTION_SHORT_HELP,
+ OPTION_LONG_HELP, INFO_DESCRIPTION_SHOWUSAGE.get());
+ argParser.addArgument(showUsage);
+ argParser.setUsageArgument(showUsage, getOutputStream());
+ }
+ catch (ArgumentException ae)
+ {
+ Message message = ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage());
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ // Parse the command-line arguments provided to this program.
+ try
+ {
+ argParser.parseArguments(args);
+ connectionFactory.validate();
+ runner.validate();
+ }
+ catch (ArgumentException ae)
+ {
+ Message message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
+ println(message);
+ println(argParser.getUsageMessage());
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ // If we should just display usage or version information,
+ // then print it and exit.
+ if (argParser.usageOrVersionDisplayed())
+ {
+ return 0;
+ }
+
+ runner.modStrings =
+ argParser.getTrailingArguments().toArray(
+ new String[argParser.getTrailingArguments().size()]);
+ runner.baseDN = baseDN.getValue();
+
+ try
+ {
+
+ // Try it out to make sure the format string and data sources
+ // match.
+ Object[] data =
+ DataSource.generateData(runner.getDataSources(), null);
+ for (String modString : runner.modStrings)
+ {
+ String.format(modString, data);
+ }
+ String.format(runner.baseDN, data);
+ }
+ catch (Exception ex1)
+ {
+ println(Message.raw("Error formatting filter or base DN: "
+ + ex1.toString()));
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ return runner.run(connectionFactory);
+ }
+
+
+
+ private class ModifyPerformanceRunner extends PerformanceRunner
+ {
+ private String baseDN;
+ private String[] modStrings;
+
+
+
+ private ModifyPerformanceRunner(ArgumentParser argParser,
+ ConsoleApplication app) throws ArgumentException
+ {
+ super(argParser, app);
+ }
+
+
+
+ WorkerThread<?> newWorkerThread(AsynchronousConnection connection,
+ ConnectionFactory<?> connectionFactory)
+ {
+ return new ModifyWorkerThread(connection, connectionFactory);
+ }
+
+
+
+ StatsThread newStatsThread()
+ {
+ return new StatsThread(new String[0]);
+ }
+
+
+
+ private class ModifyWorkerThread extends
+ WorkerThread<ResultHandler<Result, Void>>
+ {
+ private ModifyRequest mr;
+ private Object[] data;
+
+
+
+ private ModifyWorkerThread(AsynchronousConnection connection,
+ ConnectionFactory<?> connectionFactory)
+ {
+ super(connection, connectionFactory);
+ }
+
+
+
+ public ResultHandler<Result, Void> getHandler(long startTime)
+ {
+ return new UpdateStatsResultHandler<Result>(startTime);
+ }
+
+
+
+ public ResultFuture<?> performOperation(
+ AsynchronousConnection connection,
+ ResultHandler<Result, Void> handler, DataSource[] dataSources)
+ {
+ if (dataSources != null)
+ {
+ data = DataSource.generateData(dataSources, data);
+ }
+ mr = newModifyRequest(data);
+ return connection.modify(mr, handler, null);
+ }
+
+
+
+ private ModifyRequest newModifyRequest(Object[] data)
+ {
+ String formattedString;
+ int colonPos;
+ ModifyRequest mr;
+ if (data == null)
+ {
+ mr = Requests.newModifyRequest(baseDN);
+ }
+ else
+ {
+ mr = Requests.newModifyRequest(String.format(baseDN, data));
+ }
+ for (int i = 0; i < modStrings.length; i++)
+ {
+ if (data == null)
+ {
+ formattedString = modStrings[i];
+ }
+ else
+ {
+ formattedString = String.format(modStrings[i], data);
+ }
+ colonPos = formattedString.indexOf(':');
+ if (colonPos > 0)
+ {
+ mr.addChange(ModificationType.REPLACE, formattedString
+ .substring(0, colonPos), formattedString
+ .substring(colonPos + 1));
+ }
+ }
+ return mr;
+ }
+ }
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested advanced mode.
+ *
+ * @return Returns <code>true</code> if the user has requested
+ * advanced mode.
+ */
+ public boolean isAdvancedMode()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested interactive
+ * behavior.
+ *
+ * @return Returns <code>true</code> if the user has requested
+ * interactive behavior.
+ */
+ public boolean isInteractive()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not this console application is running in its
+ * menu-driven mode. This can be used to dictate whether output should
+ * go to the error stream or not. In addition, it may also dictate
+ * whether or not sub-menus should display a cancel option as well as
+ * a quit option.
+ *
+ * @return Returns <code>true</code> if this console application is
+ * running in its menu-driven mode.
+ */
+ public boolean isMenuDrivenMode()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested quiet output.
+ *
+ * @return Returns <code>true</code> if the user has requested quiet
+ * output.
+ */
+ public boolean isQuiet()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested script-friendly
+ * output.
+ *
+ * @return Returns <code>true</code> if the user has requested
+ * script-friendly output.
+ */
+ public boolean isScriptFriendly()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested verbose output.
+ *
+ * @return Returns <code>true</code> if the user has requested verbose
+ * output.
+ */
+ public boolean isVerbose()
+ {
+ return verbose.isPresent();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/tools/MultiChoiceArgument.java b/sdk/src/org/opends/sdk/tools/MultiChoiceArgument.java
new file mode 100644
index 0000000..05ebc4c
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/MultiChoiceArgument.java
@@ -0,0 +1,273 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2008 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.UtilityMessages.ERR_MCARG_VALUE_NOT_ALLOWED;
+
+import java.util.Collection;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+
+
+
+/**
+ * This class defines an argument type that will only accept one or more
+ * of a specific set of string values.
+ */
+final class MultiChoiceArgument<T> extends Argument
+{
+ // Indicates whether argument values should be treated in a
+ // case-sensitive
+ // manner.
+ private boolean caseSensitive;
+
+ // The set of values that will be allowed for use with this argument.
+ private Collection<T> allowedValues;
+
+
+
+ /**
+ * Creates a new string argument with the provided information.
+ *
+ * @param name
+ * The generic name that should be used to refer to this
+ * argument.
+ * @param shortIdentifier
+ * The single-character identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param longIdentifier
+ * The long identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param isRequired
+ * Indicates whether this argument must be specified on the
+ * command line.
+ * @param needsValue
+ * Indicates whether this argument requires a value.
+ * @param valuePlaceholder
+ * The placeholder for the argument value that will be
+ * displayed in usage information, or <CODE>null</CODE> if
+ * this argument does not require a value.
+ * @param allowedValues
+ * The set of values that are allowed for use for this
+ * argument. If they are not to be treated in a
+ * case-sensitive value then they should all be formatted in
+ * lowercase.
+ * @param caseSensitive
+ * Indicates whether the set of allowed values should be
+ * treated in a case-sensitive manner.
+ * @param description
+ * Message for the description of this argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to
+ * create this argument.
+ */
+ public MultiChoiceArgument(String name, Character shortIdentifier,
+ String longIdentifier, boolean isRequired, boolean needsValue,
+ Message valuePlaceholder, Collection<T> allowedValues,
+ boolean caseSensitive, Message description)
+ throws ArgumentException
+ {
+ super(name, shortIdentifier, longIdentifier, isRequired, false,
+ needsValue, valuePlaceholder, null, null, description);
+
+ this.allowedValues = allowedValues;
+ this.caseSensitive = caseSensitive;
+ }
+
+
+
+ /**
+ * Creates a new string argument with the provided information.
+ *
+ * @param name
+ * The generic name that should be used to refer to this
+ * argument.
+ * @param shortIdentifier
+ * The single-character identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param longIdentifier
+ * The long identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param isRequired
+ * Indicates whether this argument must be specified on the
+ * command line.
+ * @param isMultiValued
+ * Indicates whether this argument may be specified more than
+ * once to provide multiple values.
+ * @param needsValue
+ * Indicates whether this argument requires a value.
+ * @param valuePlaceholder
+ * The placeholder for the argument value that will be
+ * displayed in usage information, or <CODE>null</CODE> if
+ * this argument does not require a value.
+ * @param defaultValue
+ * The default value that should be used for this argument if
+ * none is provided in a properties file or on the command
+ * line. This may be <CODE>null</CODE> if there is no generic
+ * default.
+ * @param propertyName
+ * The name of the property in a property file that may be
+ * used to override the default value but will be overridden
+ * by a command-line argument.
+ * @param allowedValues
+ * The set of values that are allowed for use for this
+ * argument. If they are not to be treated in a
+ * case-sensitive value then they should all be formatted in
+ * lowercase.
+ * @param caseSensitive
+ * Indicates whether the set of allowed values should be
+ * treated in a case-sensitive manner.
+ * @param description
+ * Message for the description of this argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to
+ * create this argument.
+ */
+ public MultiChoiceArgument(String name, Character shortIdentifier,
+ String longIdentifier, boolean isRequired, boolean isMultiValued,
+ boolean needsValue, Message valuePlaceholder,
+ String defaultValue, String propertyName,
+ Collection<T> allowedValues, boolean caseSensitive,
+ Message description) throws ArgumentException
+ {
+ super(name, shortIdentifier, longIdentifier, isRequired,
+ isMultiValued, needsValue, valuePlaceholder, defaultValue,
+ propertyName, description);
+
+ this.allowedValues = allowedValues;
+ this.caseSensitive = caseSensitive;
+ }
+
+
+
+ /**
+ * Retrieves the set of allowed values for this argument. The contents
+ * of this set must not be altered by the caller.
+ *
+ * @return The set of allowed values for this argument.
+ */
+ public Collection<T> getAllowedValues()
+ {
+ return allowedValues;
+ }
+
+
+
+ /**
+ * Indicates whether the set of allowed values for this argument
+ * should be treated in a case-sensitive manner.
+ *
+ * @return <CODE>true</CODE> if the values are to be treated in a
+ * case-sensitive manner, or <CODE>false</CODE> if not.
+ */
+ public boolean isCaseSensitive()
+ {
+ return caseSensitive;
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in this
+ * argument.
+ *
+ * @param valueString
+ * The value for which to make the determination.
+ * @param invalidReason
+ * A buffer into which the invalid reason may be written if
+ * the value is not acceptable.
+ * @return <CODE>true</CODE> if the value is acceptable, or
+ * <CODE>false</CODE> if it is not.
+ */
+ public boolean valueIsAcceptable(String valueString,
+ MessageBuilder invalidReason)
+ {
+ for (T o : allowedValues)
+ {
+ if ((caseSensitive && o.toString().equals(valueString))
+ || o.toString().equalsIgnoreCase(valueString))
+ {
+ return true;
+ }
+ }
+ invalidReason.append(ERR_MCARG_VALUE_NOT_ALLOWED.get(getName(),
+ valueString));
+
+ return false;
+ }
+
+
+
+ /**
+ * Specifies the default value that will be used for this argument if
+ * it is not specified on the command line and it is not set from a
+ * properties file.
+ *
+ * @param defaultValue
+ * The default value that will be used for this argument if
+ * it is not specified on the command line and it is not set
+ * from a properties file.
+ */
+ public void setDefaultValue(T defaultValue)
+ {
+ super.setDefaultValue(defaultValue.toString());
+ }
+
+
+
+ /**
+ * Retrieves the string vale for this argument. If it has multiple
+ * values, then the first will be returned. If it does not have any
+ * values, then the default value will be returned.
+ *
+ * @return The string value for this argument, or <CODE>null</CODE> if
+ * there are no values and no default value has been given.
+ * @throws ArgumentException
+ * The value cannot be parsed.
+ */
+ public T getTypedValue() throws ArgumentException
+ {
+ String v = super.getValue();
+ if (v == null)
+ {
+ return null;
+ }
+ for (T o : allowedValues)
+ {
+ if ((caseSensitive && o.toString().equals(v))
+ || o.toString().equalsIgnoreCase(v))
+ {
+ return o;
+ }
+ }
+ // TODO: Some message
+ throw new ArgumentException(null);
+ }
+}
diff --git a/sdk/src/org/opends/sdk/tools/MultiColumnPrinter.java b/sdk/src/org/opends/sdk/tools/MultiColumnPrinter.java
new file mode 100644
index 0000000..0b1b7db
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/MultiColumnPrinter.java
@@ -0,0 +1,519 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Versiance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.tools;
+
+
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+import org.opends.server.util.cli.ConsoleApplication;
+
+
+
+/**
+ * Utility class for printing aligned collumns of text.
+ * <P>
+ * This class allows you to specify:
+ * <UL>
+ * <LI>The number of collumns in the output. This will determine the
+ * dimension of the string arrays passed to add(String[]) or
+ * addTitle(String[]).
+ * <LI>spacing/gap between columns
+ * <LI>character to use for title border (null means no border)
+ * <LI>column alignment. Only LEFT/CENTER is supported for now.
+ * </UL>
+ * <P>
+ * Example usage:
+ *
+ * <PRE>
+ * MyPrinter mp = new MyPrinter(3, 2, "-");
+ * String oneRow[] = new String[3];
+ * oneRow[0] = "User Name";
+ * oneRow[1] = "Email Address";
+ * oneRow[2] = "Phone Number";
+ * mp.addTitle(oneRow);
+ * oneRow[0] = "Bob";
+ * oneRow[1] = "bob@foo.com";
+ * oneRow[2] = "123-4567";
+ * mp.add(oneRow);
+ * oneRow[0] = "John";
+ * oneRow[1] = "john@foo.com";
+ * oneRow[2] = "456-7890";
+ * mp.add(oneRow);
+ * mp.print();
+ * </PRE>
+ * <P>
+ * The above would print:
+ * <P>
+ *
+ * <PRE>
+ * --------------------------------------
+ * User Name Email Address Phone Number
+ * --------------------------------------
+ * Bob bob@foo.com 123-4567
+ * John john@foo.com 456-7890
+ * </PRE>
+ * <P>
+ * This class also supports multi-row titles and having title strings
+ * spanning multiple collumns. Example usage:
+ *
+ * <PRE>
+ * TestPrinter tp = new TestPrinter(4, 2, "-");
+ * String oneRow[] = new String[4];
+ * int[] span = new int[4];
+ * span[0] = 2; // spans 2 collumns
+ * span[1] = 0; // spans 0 collumns
+ * span[2] = 2; // spans 2 collumns
+ * span[3] = 0; // spans 0 collumns
+ * tp.setTitleAlign(CENTER);
+ * oneRow[0] = "Name";
+ * oneRow[1] = "";
+ * oneRow[2] = "Contact";
+ * oneRow[3] = "";
+ * tp.addTitle(oneRow, span);
+ * oneRow[0] = "First";
+ * oneRow[1] = "Last";
+ * oneRow[2] = "Email";
+ * oneRow[3] = "Phone";
+ * tp.addTitle(oneRow);
+ * oneRow[0] = "Bob";
+ * oneRow[1] = "Jones";
+ * oneRow[2] = "bob@foo.com";
+ * oneRow[3] = "123-4567";
+ * tp.add(oneRow);
+ * oneRow[0] = "John";
+ * oneRow[1] = "Doe";
+ * oneRow[2] = "john@foo.com";
+ * oneRow[3] = "456-7890";
+ * tp.add(oneRow);
+ * tp.println();
+ * </PRE>
+ * <P>
+ * The above would print:
+ * <P>
+ *
+ * <PRE>
+ * ------------------------------------
+ * Name Contact
+ * First Last Email Phone
+ * ------------------------------------
+ * Bob Jones bob@foo.com 123-4567
+ * John Doe john@foo.com 456-7890
+ * </PRE>
+ */
+final class MultiColumnPrinter
+{
+
+ final public static int LEFT = 0;
+ final public static int CENTER = 1;
+ final public static int RIGHT = 2;
+
+ private int numCol = 2;
+ private int gap = 4;
+ private int align = CENTER;
+ private int titleAlign = CENTER;
+ private String border = null;
+
+ private Vector<String[]> titleTable = null;
+ private Vector<int[]> titleSpanTable = null;
+ private int curLength[];
+
+ private final ConsoleApplication app;
+
+
+
+ /**
+ * Creates a new MultiColumnPrinter class.
+ *
+ * @param numCol
+ * number of columns
+ * @param gap
+ * gap between each column
+ * @param border
+ * character used to frame the titles
+ * @param align
+ * type of alignment within columns
+ */
+ public MultiColumnPrinter(int numCol, int gap, String border,
+ int align, ConsoleApplication app)
+ {
+
+ titleTable = new Vector<String[]>();
+ titleSpanTable = new Vector<int[]>();
+ curLength = new int[numCol];
+
+ this.numCol = numCol;
+ this.gap = gap;
+ this.border = border;
+ this.align = align;
+ this.titleAlign = LEFT;
+
+ this.app = app;
+ }
+
+
+
+ /**
+ * Creates a sorted new MultiColumnPrinter class using LEFT alignment.
+ *
+ * @param numCol
+ * number of columns
+ * @param gap
+ * gap between each column
+ * @param border
+ * character used to frame the titles
+ */
+ public MultiColumnPrinter(int numCol, int gap, String border,
+ ConsoleApplication app)
+ {
+ this(numCol, gap, border, LEFT, app);
+ }
+
+
+
+ /**
+ * Creates a sorted new MultiColumnPrinter class using LEFT alignment
+ * and with no title border.
+ *
+ * @param numCol
+ * number of columns
+ * @param gap
+ * gap between each column
+ */
+ public MultiColumnPrinter(int numCol, int gap, ConsoleApplication app)
+ {
+ this(numCol, gap, null, LEFT, app);
+ }
+
+
+
+ /**
+ * Adds to the row of strings to be used as the title for the table.
+ *
+ * @param row
+ * Array of strings to print in one row of title.
+ */
+ public void addTitle(String[] row)
+ {
+ if (row == null)
+ return;
+
+ int[] span = new int[row.length];
+ for (int i = 0; i < row.length; i++)
+ {
+ span[i] = 1;
+ }
+
+ addTitle(row, span);
+ }
+
+
+
+ /**
+ * Adds to the row of strings to be used as the title for the table.
+ * Also allows for certain title strings to span multiple collumns The
+ * span parameter is an array of integers which indicate how many
+ * collumns the corresponding title string will occupy. For a row that
+ * is 4 collumns wide, it is possible to have some title strings in a
+ * row to 'span' multiple collumns:
+ * <P>
+ *
+ * <PRE>
+ * ------------------------------------
+ * Name Contact
+ * First Last Email Phone
+ * ------------------------------------
+ * Bob Jones bob@foo.com 123-4567
+ * John Doe john@foo.com 456-7890
+ * </PRE>
+ *
+ * In the example above, the title row has a string 'Name' that spans
+ * 2 collumns. The string 'Contact' also spans 2 collumns. The above
+ * is done by passing in to addTitle() an array that contains:
+ *
+ * <PRE>
+ * span[0] = 2; // spans 2 collumns
+ * span[1] = 0; // spans 0 collumns, ignore
+ * span[2] = 2; // spans 2 collumns
+ * span[3] = 0; // spans 0 collumns, ignore
+ * </PRE>
+ * <P>
+ * A span value of 1 is the default. The method addTitle(String[] row)
+ * basically does:
+ *
+ * <PRE>
+ * int[] span = new int[row.length];
+ * for (int i = 0; i < row.length; i++)
+ * {
+ * span[i] = 1;
+ * }
+ * addTitle(row, span);
+ * </PRE>
+ *
+ * @param row
+ * Array of strings to print in one row of title.
+ * @param span
+ * Array of integers that reflect the number of collumns the
+ * corresponding title string will occupy.
+ */
+ public void addTitle(String[] row, int span[])
+ {
+ // Need to create a new instance of it, otherwise the new values
+ // will always overwrite the old values.
+
+ String[] rowInstance = new String[(row.length)];
+ for (int i = 0; i < row.length; i++)
+ {
+ rowInstance[i] = row[i];
+ }
+ titleTable.addElement(rowInstance);
+
+ titleSpanTable.addElement(span);
+ }
+
+
+
+ /**
+ * Set alignment for title strings
+ *
+ * @param titleAlign
+ */
+ public void setTitleAlign(int titleAlign)
+ {
+ this.titleAlign = titleAlign;
+ }
+
+
+
+ /**
+ * Clears title strings.
+ */
+ public void clearTitle()
+ {
+ titleTable.clear();
+ titleSpanTable.clear();
+ }
+
+
+
+ /**
+ * Prints the table title
+ */
+ public void printTitle()
+ {
+ // Get the longest string for each column and store in curLength[]
+
+ // Scan through title rows
+ Enumeration<String[]> elm = titleTable.elements();
+ Enumeration<int[]> spanEnum = titleSpanTable.elements();
+ while (elm.hasMoreElements())
+ {
+ String[] row = (String[]) elm.nextElement();
+ int[] curSpan = (int[]) spanEnum.nextElement();
+
+ for (int i = 0; i < numCol; i++)
+ {
+ // None of the fields should be null, but if it
+ // happens to be so, replace it with "-".
+ if (row[i] == null)
+ row[i] = "-";
+
+ int len = row[i].length();
+
+ /*
+ * If a title string spans multiple collumns, then the space it
+ * occupies in each collumn is at most len/span (since we have
+ * gap to take into account as well).
+ */
+ int span = curSpan[i], rem = 0;
+ if (span > 1)
+ {
+ rem = len % span;
+ len = len / span;
+ }
+
+ if (curLength[i] < len)
+ {
+ curLength[i] = len;
+
+ if ((span > 1) && ((i + span) <= numCol))
+ {
+ for (int j = i + 1; j < (i + span); ++j)
+ {
+ curLength[j] = len;
+ }
+
+ /*
+ * Add remainder to last collumn in span to avoid round-off
+ * errors.
+ */
+ curLength[(i + span) - 1] += rem;
+ }
+ }
+ }
+ }
+
+ printBorder();
+ elm = titleTable.elements();
+ spanEnum = titleSpanTable.elements();
+
+ while (elm.hasMoreElements())
+ {
+ String[] row = (String[]) elm.nextElement();
+ int[] curSpan = (int[]) spanEnum.nextElement();
+
+ for (int i = 0; i < numCol; i++)
+ {
+ int availableSpace = 0, span = curSpan[i];
+
+ if (span == 0)
+ continue;
+
+ availableSpace = curLength[i];
+
+ if ((span > 1) && ((i + span) <= numCol))
+ {
+ for (int j = i + 1; j < (i + span); ++j)
+ {
+ availableSpace += gap;
+ availableSpace += curLength[j];
+ }
+ }
+
+ if (titleAlign == RIGHT)
+ {
+ int space_before = availableSpace - row[i].length();
+ printSpaces(space_before);
+ app.getOutputStream().print(row[i]);
+ if (i < numCol - 1)
+ printSpaces(gap);
+ }
+ else if (titleAlign == CENTER)
+ {
+ int space_before, space_after;
+ space_before = (availableSpace - row[i].length()) / 2;
+ space_after = availableSpace - row[i].length() - space_before;
+
+ printSpaces(space_before);
+ app.getOutputStream().print(row[i]);
+ printSpaces(space_after);
+ if (i < numCol - 1)
+ printSpaces(gap);
+ }
+ else
+ {
+ app.getOutputStream().print(row[i]);
+ if (i < numCol - 1)
+ printSpaces(availableSpace - row[i].length() + gap);
+ }
+
+ }
+ app.getOutputStream().println("");
+ }
+ printBorder();
+ }
+
+
+
+ /**
+ * Adds one row of text to output.
+ *
+ * @param row
+ * Array of strings to print in one row.
+ */
+ public void printRow(String... row)
+ {
+ for (int i = 0; i < numCol; i++)
+ {
+ if (titleAlign == RIGHT)
+ {
+ int space_before = curLength[i] - row[i].length();
+ printSpaces(space_before);
+ app.getOutputStream().print(row[i]);
+ if (i < numCol - 1)
+ printSpaces(gap);
+ }
+ else if (align == CENTER)
+ {
+ int space1, space2;
+ space1 = (curLength[i] - row[i].length()) / 2;
+ space2 = curLength[i] - row[i].length() - space1;
+
+ printSpaces(space1);
+ app.getOutputStream().print(row[i]);
+ printSpaces(space2);
+ if (i < numCol - 1)
+ printSpaces(gap);
+ }
+ else
+ {
+ app.getOutputStream().print(row[i]);
+ if (i < numCol - 1)
+ printSpaces(curLength[i] - row[i].length() + gap);
+ }
+ }
+ app.getOutputStream().println("");
+ }
+
+
+
+ private void printSpaces(int count)
+ {
+ for (int i = 0; i < count; ++i)
+ {
+ app.getOutputStream().print(" ");
+ }
+ }
+
+
+
+ private void printBorder()
+ {
+ if (border == null)
+ return;
+
+ // For the value in each column
+ for (int i = 0; i < numCol; i++)
+ {
+ for (int j = 0; j < curLength[i]; j++)
+ {
+ app.getOutputStream().print(border);
+ }
+ }
+
+ // For the gap between each column
+ for (int i = 0; i < numCol - 1; i++)
+ {
+ for (int j = 0; j < gap; j++)
+ {
+ app.getOutputStream().print(border);
+ }
+ }
+ app.getOutputStream().println("");
+ }
+}
diff --git a/sdk/src/org/opends/sdk/tools/PerformanceRunner.java b/sdk/src/org/opends/sdk/tools/PerformanceRunner.java
new file mode 100644
index 0000000..3a00dbe
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/PerformanceRunner.java
@@ -0,0 +1,943 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.tools;
+
+
+
+import java.io.IOException;
+import java.lang.management.GarbageCollectorMXBean;
+import java.lang.management.ManagementFactory;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.opends.messages.Message;
+import org.opends.sdk.*;
+import org.opends.sdk.AuthenticatedConnectionFactory.AuthenticatedAsynchronousConnection;
+import org.opends.sdk.responses.Result;
+import org.opends.server.util.cli.ConsoleApplication;
+
+
+
+/**
+ * Benchmark application framework.
+ */
+abstract class PerformanceRunner
+{
+ private final AtomicInteger operationRecentCount =
+ new AtomicInteger();
+ private final AtomicInteger successRecentCount = new AtomicInteger();
+ private final AtomicInteger failedRecentCount = new AtomicInteger();
+ private final AtomicLong waitRecentTime = new AtomicLong();
+ private final AtomicReference<ReversableArray> eTimeBuffer =
+ new AtomicReference<ReversableArray>(new ReversableArray(100000));
+ private final ConsoleApplication app;
+ private final ThreadLocal<DataSource[]> dataSources =
+ new ThreadLocal<DataSource[]>();
+
+ private volatile boolean stopRequested;
+ private int numThreads;
+ private int numConnections;
+ private int targetThroughput;
+ private int maxIterations;
+ private boolean isAsync;
+ private boolean noRebind;
+ private int statsInterval;
+
+ private IntegerArgument numThreadsArgument;
+ private IntegerArgument maxIterationsArgument;
+ private IntegerArgument statsIntervalArgument;
+ private IntegerArgument targetThroughputArgument;
+ private IntegerArgument numConnectionsArgument;
+ private IntegerArgument percentilesArgument;
+ private BooleanArgument keepConnectionsOpen;
+ private BooleanArgument noRebindArgument;
+ private BooleanArgument asyncArgument;
+
+ private StringArgument arguments;
+
+
+
+ PerformanceRunner(ArgumentParser argParser, ConsoleApplication app)
+ throws ArgumentException
+ {
+ this.app = app;
+ numThreadsArgument =
+ new IntegerArgument("numThreads", 't', "numThreads", false,
+ false, true, Message.raw("{numThreads}"), 1, null, true, 1,
+ false, 0, Message
+ .raw("number of search threads per connection"));
+ numThreadsArgument.setPropertyName("numThreads");
+ argParser.addArgument(numThreadsArgument);
+
+ numConnectionsArgument =
+ new IntegerArgument("numConnections", 'c', "numConnections",
+ false, false, true, Message.raw("{numConnections}"), 1,
+ null, true, 1, false, 0, Message
+ .raw("number of connections"));
+ numThreadsArgument.setPropertyName("numConnections");
+ argParser.addArgument(numConnectionsArgument);
+
+ maxIterationsArgument =
+ new IntegerArgument("maxIterations", 'm', "maxIterations",
+ false, false, true, Message.raw("{maxIterations}"), 0,
+ null, Message
+ .raw("max searches per thread, 0 for unlimited"));
+ numThreadsArgument.setPropertyName("maxIterations");
+ argParser.addArgument(maxIterationsArgument);
+
+ statsIntervalArgument =
+ new IntegerArgument(
+ "statInterval",
+ 'i',
+ "statInterval",
+ false,
+ false,
+ true,
+ Message.raw("{statInterval}"),
+ 5,
+ null,
+ true,
+ 1,
+ false,
+ 0,
+ Message
+ .raw("Display results each specified number of seconds"));
+ numThreadsArgument.setPropertyName("statInterval");
+ argParser.addArgument(statsIntervalArgument);
+
+ targetThroughputArgument =
+ new IntegerArgument("targetThroughput", 'M',
+ "targetThroughput", false, false, true, Message
+ .raw("{targetThroughput}"), 0, null, Message
+ .raw("Target average throughput to achieve"));
+ targetThroughputArgument.setPropertyName("targetThroughput");
+ argParser.addArgument(targetThroughputArgument);
+
+ percentilesArgument =
+ new IntegerArgument("percentile", 'e', "percentile", false,
+ true, Message.raw("{percentile}"), true, 50, true, 100,
+ Message.raw("Calculate max response time for a "
+ + "percentile of operations"));
+ percentilesArgument.setPropertyName("percentile");
+ argParser.addArgument(percentilesArgument);
+
+ keepConnectionsOpen =
+ new BooleanArgument("keepConnectionsOpen", 'f',
+ "keepConnectionsOpen", Message.raw("keep connections open"));
+ keepConnectionsOpen.setPropertyName("keepConnectionsOpen");
+ argParser.addArgument(keepConnectionsOpen);
+
+ noRebindArgument =
+ new BooleanArgument("noRebind", 'F', "noRebind", Message
+ .raw("keep connections open and don't rebind"));
+ keepConnectionsOpen.setPropertyName("noRebind");
+ argParser.addArgument(noRebindArgument);
+
+ asyncArgument =
+ new BooleanArgument("asynchronous", 'A', "asynchronous",
+ Message.raw("asynch, don't wait for results"));
+ keepConnectionsOpen.setPropertyName("asynchronous");
+ argParser.addArgument(asyncArgument);
+
+ arguments =
+ new StringArgument(
+ "arguments",
+ 'g',
+ "arguments",
+ false,
+ true,
+ true,
+ Message.raw("{arguments}"),
+ null,
+ null,
+ Message
+ .raw("arguments for variables in the filter and/or base DN"));
+ arguments.setPropertyName("arguments");
+ argParser.addArgument(arguments);
+ }
+
+
+
+ public void validate() throws ArgumentException
+ {
+ numConnections = numConnectionsArgument.getIntValue();
+ numThreads = numThreadsArgument.getIntValue();
+ maxIterations = maxIterationsArgument.getIntValue();
+ statsInterval = statsIntervalArgument.getIntValue() * 1000;
+ targetThroughput = targetThroughputArgument.getIntValue();
+
+ isAsync = asyncArgument.isPresent();
+ noRebind = noRebindArgument.isPresent();
+
+ if (!noRebindArgument.isPresent() && this.numThreads > 1)
+ {
+ throw new ArgumentException(Message.raw("--"
+ + noRebindArgument.getLongIdentifier()
+ + " must be used if --"
+ + numThreadsArgument.getLongIdentifier() + " is > 1"));
+ }
+
+ if (!noRebindArgument.isPresent() && asyncArgument.isPresent())
+ {
+ throw new ArgumentException(Message.raw("--"
+ + noRebindArgument.getLongIdentifier()
+ + " must be used when using --"
+ + asyncArgument.getLongIdentifier()));
+ }
+
+ try
+ {
+ DataSource.parse(arguments.getValues());
+ }
+ catch (IOException ioe)
+ {
+ throw new ArgumentException(Message
+ .raw("Error occured while parsing arguments: "
+ + ioe.toString()));
+ }
+ }
+
+
+
+ final int run(ConnectionFactory<?> connectionFactory)
+ {
+ List<Thread> threads = new ArrayList<Thread>();
+
+ AsynchronousConnection connection = null;
+ Thread thread;
+ try
+ {
+ for (int i = 0; i < numConnections; i++)
+ {
+ if (keepConnectionsOpen.isPresent()
+ || noRebindArgument.isPresent())
+ {
+ connection =
+ connectionFactory.getAsynchronousConnection(null, null)
+ .get();
+ }
+ for (int j = 0; j < numThreads; j++)
+ {
+ thread = newWorkerThread(connection, connectionFactory);
+
+ threads.add(thread);
+ thread.start();
+ }
+ }
+
+ Thread statsThread = newStatsThread();
+ statsThread.start();
+
+ for (Thread t : threads)
+ {
+ t.join();
+ }
+ stopRequested = true;
+ statsThread.join();
+ }
+ catch (InterruptedException e)
+ {
+ stopRequested = true;
+ }
+ catch (ErrorResultException e)
+ {
+ stopRequested = true;
+ app.println(Message.raw(e.getResult().getDiagnosticMessage()));
+ }
+
+ return 0;
+ }
+
+
+
+ final DataSource[] getDataSources()
+ {
+ try
+ {
+ return DataSource.parse(arguments.getValues());
+ }
+ catch (IOException ioe)
+ {
+ // Ignore as this shouldn've been handled eariler
+ }
+ return new DataSource[0];
+ }
+
+
+
+ abstract WorkerThread<?> newWorkerThread(
+ AsynchronousConnection connection,
+ ConnectionFactory<?> connectionFactory);
+
+
+
+ abstract StatsThread newStatsThread();
+
+
+
+ class UpdateStatsResultHandler<S extends Result> implements
+ ResultHandler<S, Void>
+ {
+ private long eTime;
+
+
+
+ UpdateStatsResultHandler(long eTime)
+ {
+ this.eTime = eTime;
+ }
+
+
+
+ public void handleResult(Void p, S result)
+ {
+ successRecentCount.getAndIncrement();
+ eTime = System.nanoTime() - eTime;
+ waitRecentTime.getAndAdd(eTime);
+ synchronized (this)
+ {
+ ReversableArray array = eTimeBuffer.get();
+ if (array.remaining() == 0)
+ {
+ array.set(array.size() - 1, eTime);
+ }
+ else
+ {
+ array.append(eTime);
+ }
+ }
+ }
+
+
+
+ public void handleErrorResult(Void p, ErrorResultException error)
+ {
+ failedRecentCount.getAndIncrement();
+ app.println(Message.raw(error.getResult().toString()));
+ }
+
+
+
+ public long getETime()
+ {
+ return eTime;
+ }
+ }
+
+
+
+ abstract class WorkerThread<R extends ResultHandler<?, ?>> extends
+ Thread
+ {
+ private int count;
+ private final AsynchronousConnection connection;
+ private final ConnectionFactory<?> connectionFactory;
+
+
+
+ WorkerThread(AsynchronousConnection connection,
+ ConnectionFactory<?> connectionFactory)
+ {
+ super("Worker Thread");
+ this.connection = connection;
+ this.connectionFactory = connectionFactory;
+ }
+
+
+
+ public abstract ResultFuture<?> performOperation(
+ AsynchronousConnection connection, R handler,
+ DataSource[] dataSources);
+
+
+
+ public abstract R getHandler(long startTime);
+
+
+
+ public void run()
+ {
+ if (dataSources.get() == null)
+ {
+ try
+ {
+ dataSources.set(DataSource.parse(arguments.getValues()));
+ }
+ catch (IOException ioe)
+ {
+ // Ignore as this shouldn've been handled eariler
+ }
+ }
+
+ ResultFuture<?> future;
+ AsynchronousConnection connection;
+ R handler;
+
+ double targetTimeInMS =
+ (1.0 / (targetThroughput / (numThreads * numConnections))) * 1000.0;
+ double sleepTimeInMS = 0;
+ long start;
+ while (!stopRequested
+ && !(maxIterations > 0 && count >= maxIterations))
+ {
+ start = System.nanoTime();
+ handler = getHandler(start);
+
+ if (this.connection == null)
+ {
+ try
+ {
+ connection =
+ connectionFactory.getAsynchronousConnection(null, null)
+ .get();
+ }
+ catch (InterruptedException e)
+ {
+ // Ignore and check stop requested
+ continue;
+ }
+ catch (ErrorResultException e)
+ {
+ app.println(Message.raw(e.getResult()
+ .getDiagnosticMessage()));
+ if (e.getCause() != null && app.isVerbose())
+ {
+ e.getCause().printStackTrace(app.getErrorStream());
+ }
+ stopRequested = true;
+ break;
+ }
+ }
+ else
+ {
+ connection = this.connection;
+ if (!noRebind
+ && connection instanceof AuthenticatedAsynchronousConnection)
+ {
+ AuthenticatedAsynchronousConnection ac =
+ (AuthenticatedAsynchronousConnection) connection;
+ try
+ {
+ ac.rebind(null, null).get();
+ }
+ catch (InterruptedException e)
+ {
+ // Ignore and check stop requested
+ continue;
+ }
+ catch (ErrorResultException e)
+ {
+ app.println(Message.raw(e.getResult().toString()));
+ if (e.getCause() != null && app.isVerbose())
+ {
+ e.getCause().printStackTrace(app.getErrorStream());
+ }
+ stopRequested = true;
+ break;
+ }
+ }
+ }
+ future =
+ performOperation(connection, handler, dataSources.get());
+ operationRecentCount.getAndIncrement();
+ count++;
+ if (!isAsync)
+ {
+ try
+ {
+ future.get();
+ }
+ catch (InterruptedException e)
+ {
+ // Ignore and check stop requested
+ continue;
+ }
+ catch (ErrorResultException e)
+ {
+ if (e.getCause() instanceof IOException)
+ {
+ e.getCause().printStackTrace(app.getErrorStream());
+ stopRequested = true;
+ break;
+ }
+ // Ignore. Handled by result handler
+ }
+ if (this.connection == null)
+ {
+ connection.close();
+ }
+ }
+ if (targetThroughput > 0)
+ {
+ try
+ {
+ if (sleepTimeInMS > 1)
+ {
+ sleep((long) Math.floor(sleepTimeInMS));
+ }
+ }
+ catch (InterruptedException e)
+ {
+ continue;
+ }
+
+ sleepTimeInMS +=
+ targetTimeInMS
+ - ((System.nanoTime() - start) / 1000000.0);
+ if (sleepTimeInMS < -60000)
+ {
+ // If we fall behind by 60 seconds, just forget about
+ // catching up
+ sleepTimeInMS = -60000;
+ }
+ }
+ }
+ }
+ }
+
+
+
+ class StatsThread extends Thread
+ {
+ protected final String[] EMPTY_STRINGS = new String[0];
+ private final MultiColumnPrinter printer;
+ private final List<GarbageCollectorMXBean> beans;
+ private final Set<Double> percentiles;
+ private final int numColumns;
+
+ private ReversableArray etimes = new ReversableArray(100000);
+ private final ReversableArray array = new ReversableArray(200000);
+
+ protected long totalSuccessCount;
+ protected long totalOperationCount;
+ protected long totalFailedCount;
+ protected long totalWaitTime;
+ protected int successCount;
+ protected int searchCount;
+ protected int failedCount;
+ protected long waitTime;
+
+ protected long lastStatTime;
+ protected long lastGCDuration;
+ protected double recentDuration;
+ protected double averageDuration;
+
+
+
+ public StatsThread(String[] additionalColumns)
+ {
+ super("Stats Thread");
+ TreeSet<Double> pSet = new TreeSet<Double>();
+ if (!percentilesArgument.isPresent())
+ {
+ pSet.add(.1);
+ pSet.add(.01);
+ pSet.add(.001);
+ }
+ else
+ {
+ for (String percentile : percentilesArgument.getValues())
+ {
+ pSet.add(100.0 - Double.parseDouble(percentile));
+ }
+ }
+ this.percentiles = pSet.descendingSet();
+ numColumns =
+ 5 + this.percentiles.size() + additionalColumns.length
+ + (isAsync ? 1 : 0);
+ printer =
+ new MultiColumnPrinter(numColumns, 2, "-",
+ MultiColumnPrinter.RIGHT, app);
+ printer.setTitleAlign(MultiColumnPrinter.RIGHT);
+
+ String[] title = new String[numColumns];
+ Arrays.fill(title, "");
+ title[0] = "Throughput";
+ title[2] = "Response Time";
+ int[] span = new int[numColumns];
+ span[0] = 2;
+ span[1] = 0;
+ span[2] = 2 + this.percentiles.size();
+ Arrays.fill(span, 3, 4 + this.percentiles.size(), 0);
+ Arrays.fill(span, 4 + this.percentiles.size(), span.length, 1);
+ printer.addTitle(title, span);
+ title = new String[numColumns];
+ Arrays.fill(title, "");
+ title[0] = "(ops/second)";
+ title[2] = "(milliseconds)";
+ printer.addTitle(title, span);
+ title = new String[numColumns];
+ title[0] = "recent";
+ title[1] = "average";
+ title[2] = "recent";
+ title[3] = "average";
+ int i = 4;
+ for (Double percentile : this.percentiles)
+ {
+ title[i++] = Double.toString(100.0 - percentile) + "%";
+ }
+ title[i++] = "err/sec";
+ if (isAsync)
+ {
+ title[i++] = "req/res";
+ }
+ for (String column : additionalColumns)
+ {
+ title[i++] = column;
+ }
+ span = new int[numColumns];
+ Arrays.fill(span, 1);
+ printer.addTitle(title, span);
+ beans = ManagementFactory.getGarbageCollectorMXBeans();
+ }
+
+
+
+ String[] getAdditionalColumns()
+ {
+ return EMPTY_STRINGS;
+ }
+
+
+
+ @Override
+ public void run()
+ {
+ printer.printTitle();
+
+ String[] strings = new String[numColumns];
+
+ long startTime = System.currentTimeMillis();
+ long statTime = startTime;
+ long gcDuration = 0;
+ for (GarbageCollectorMXBean bean : beans)
+ {
+ gcDuration += bean.getCollectionTime();
+ }
+ while (!stopRequested)
+ {
+ try
+ {
+ sleep(statsInterval);
+ }
+ catch (InterruptedException ie)
+ {
+ // Ignore.
+ }
+
+ lastStatTime = statTime;
+ statTime = System.currentTimeMillis();
+
+ lastGCDuration = gcDuration;
+ gcDuration = 0;
+ for (GarbageCollectorMXBean bean : beans)
+ {
+ gcDuration += bean.getCollectionTime();
+ }
+
+ successCount = successRecentCount.getAndSet(0);
+ searchCount = operationRecentCount.getAndSet(0);
+ failedCount = failedRecentCount.getAndSet(0);
+ waitTime = waitRecentTime.getAndSet(0);
+ totalSuccessCount += successCount;
+ totalOperationCount += searchCount;
+ totalFailedCount += failedCount;
+ totalWaitTime += waitTime;
+ recentDuration = statTime - lastStatTime;
+ averageDuration = statTime - startTime;
+ recentDuration -= gcDuration - lastGCDuration;
+ averageDuration -= gcDuration;
+ recentDuration /= 1000.0;
+ averageDuration /= 1000.0;
+ strings[0] =
+ String.format("%.1f", successCount / recentDuration);
+ strings[1] =
+ String.format("%.1f", totalSuccessCount / averageDuration);
+ strings[2] =
+ String.format("%.3f",
+ (waitTime - (gcDuration - lastGCDuration))
+ / successCount / 1000000.0);
+ strings[3] =
+ String.format("%.3f", (totalWaitTime - gcDuration)
+ / totalSuccessCount / 1000000.0);
+
+ boolean changed = false;
+ etimes = eTimeBuffer.getAndSet(etimes);
+ int appendLength = Math.min(array.remaining(), etimes.size());
+ if (appendLength > 0)
+ {
+ array.append(etimes, appendLength);
+ for (int i = array.size - appendLength; i < array.size; i++)
+ {
+ array.siftUp(0, i);
+ }
+ changed = true;
+ }
+
+ // Our window buffer is now full. Replace smallest with anything
+ // larger
+ // and re-heapify
+ for (int i = appendLength; i < etimes.size(); i++)
+ {
+ if (etimes.get(i) > array.get(0))
+ {
+ array.set(0, etimes.get(i));
+ array.siftDown(0, array.size() - 1);
+ changed = true;
+ }
+ }
+ etimes.clear();
+
+ if (changed)
+ {
+ // Perform heapsort
+ int i = array.size() - 1;
+ while (i > 0)
+ {
+ array.swap(i, 0);
+ array.siftDown(0, i - 1);
+ i--;
+ }
+ array.reverse();
+ }
+
+ // Now everything is ordered from smallest to largest
+ int index;
+ int i = 4;
+ for (Double percent : percentiles)
+ {
+ index =
+ array.size()
+ - (int) Math.floor((percent / 100.0)
+ * totalSuccessCount) - 1;
+ if (index < 0)
+ {
+ strings[i++] =
+ String.format("*%.3f", array.get(0) / 1000000.0);
+ }
+ else
+ {
+ strings[i++] =
+ String.format("%.3f", array.get(index) / 1000000.0);
+ }
+ }
+ strings[i++] =
+ String.format("%.1f", totalFailedCount / averageDuration);
+ if (isAsync)
+ {
+ strings[i++] =
+ String
+ .format("%.1f", (double) searchCount / successCount);
+ }
+ for (String column : getAdditionalColumns())
+ {
+ strings[i++] = column;
+ }
+ printer.printRow(strings);
+ }
+ }
+ }
+
+
+
+ private static class ReversableArray
+ {
+ private final long[] array;
+ private boolean reversed;
+ private int size;
+
+
+
+ public ReversableArray(int capacity)
+ {
+ this.array = new long[capacity];
+ }
+
+
+
+ public void set(int index, long value)
+ {
+ if (index >= size)
+ {
+ throw new IndexOutOfBoundsException();
+ }
+ if (!reversed)
+ {
+ array[index] = value;
+ }
+ else
+ {
+ array[size - index - 1] = value;
+ }
+ }
+
+
+
+ public long get(int index)
+ {
+ if (index >= size)
+ {
+ throw new IndexOutOfBoundsException();
+ }
+ if (!reversed)
+ {
+ return array[index];
+ }
+ else
+ {
+ return array[size - index - 1];
+ }
+ }
+
+
+
+ public int size()
+ {
+ return size;
+ }
+
+
+
+ public void reverse()
+ {
+ reversed = !reversed;
+ }
+
+
+
+ public void append(long value)
+ {
+ if (size == array.length)
+ {
+ throw new IndexOutOfBoundsException();
+ }
+
+ if (!reversed)
+ {
+ array[size] = value;
+ }
+ else
+ {
+ System.arraycopy(array, 0, array, 1, size);
+ array[0] = value;
+ }
+ size++;
+ }
+
+
+
+ public void append(ReversableArray a, int length)
+ {
+ if (length > a.size() || length > remaining())
+ {
+ throw new IndexOutOfBoundsException();
+ }
+ if (!reversed)
+ {
+ System.arraycopy(a.array, 0, array, size, length);
+ }
+ else
+ {
+ System.arraycopy(array, 0, array, length, size);
+ System.arraycopy(a.array, 0, array, 0, length);
+ }
+ size += length;
+ }
+
+
+
+ public int remaining()
+ {
+ return array.length - size;
+ }
+
+
+
+ public void clear()
+ {
+ size = 0;
+ }
+
+
+
+ public void siftDown(int start, int end)
+ {
+ int root = start;
+ int child;
+ while (root * 2 + 1 <= end)
+ {
+ child = root * 2 + 1;
+ if (child + 1 <= end && get(child) > get(child + 1))
+ {
+ child = child + 1;
+ }
+ if (get(root) > get(child))
+ {
+ swap(root, child);
+ root = child;
+ }
+ else
+ {
+ return;
+ }
+ }
+ }
+
+
+
+ public void siftUp(int start, int end)
+ {
+ int child = end;
+ int parent;
+ while (child > start)
+ {
+ parent = (int) Math.floor((child - 1) / 2);
+ if (get(parent) > get(child))
+ {
+ swap(parent, child);
+ child = parent;
+ }
+ else
+ {
+ return;
+ }
+ }
+ }
+
+
+
+ private void swap(int i, int i2)
+ {
+ long temp = get(i);
+ set(i, get(i2));
+ set(i2, temp);
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/tools/SearchRate.java b/sdk/src/org/opends/sdk/tools/SearchRate.java
new file mode 100644
index 0000000..b3c157e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/SearchRate.java
@@ -0,0 +1,519 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.ToolMessages.*;
+import static org.opends.server.tools.ToolConstants.*;
+import static org.opends.server.util.StaticUtils.filterExitCode;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.opends.messages.Message;
+import org.opends.sdk.*;
+import org.opends.sdk.requests.Requests;
+import org.opends.sdk.requests.SearchRequest;
+import org.opends.sdk.responses.*;
+import org.opends.server.util.cli.ConsoleApplication;
+
+
+
+/**
+ * Searchrate benchmarking tool.
+ */
+public final class SearchRate extends ConsoleApplication
+{
+ private BooleanArgument verbose;
+
+
+
+ /**
+ * The main method for SearchRate tool.
+ *
+ * @param args
+ * The command-line arguments provided to this program.
+ */
+
+ public static void main(String[] args)
+ {
+ int retCode =
+ mainSearchRate(args, System.in, System.out, System.err);
+
+ if (retCode != 0)
+ {
+ System.exit(filterExitCode(retCode));
+ }
+ }
+
+
+
+ /**
+ * Parses the provided command-line arguments and uses that
+ * information to run the ldapsearch tool.
+ *
+ * @param args
+ * The command-line arguments provided to this program.
+ * @return The error code.
+ */
+
+ public static int mainSearchRate(String[] args)
+ {
+ return mainSearchRate(args, System.in, System.out, System.err);
+ }
+
+
+
+ /**
+ * Parses the provided command-line arguments and uses that
+ * information to run the ldapsearch tool.
+ *
+ * @param args
+ * The command-line arguments provided to this program.
+ * @param inStream
+ * The input stream to use for standard input, or
+ * <CODE>null</CODE> if standard input is not needed.
+ * @param outStream
+ * The output stream to use for standard output, or
+ * <CODE>null</CODE> if standard output is not needed.
+ * @param errStream
+ * The output stream to use for standard error, or
+ * <CODE>null</CODE> if standard error is not needed.
+ * @return The error code.
+ */
+
+ public static int mainSearchRate(String[] args, InputStream inStream,
+ OutputStream outStream, OutputStream errStream)
+
+ {
+ return new SearchRate(inStream, outStream, errStream).run(args);
+ }
+
+
+
+ private SearchRate(InputStream in, OutputStream out, OutputStream err)
+ {
+ super(in, out, err);
+
+ }
+
+
+
+ private int run(String[] args)
+ {
+ // Create the command-line argument parser for use with this
+ // program.
+ Message toolDescription =
+ Message.raw("This utility can be used to "
+ + "measure search performance");
+ // TODO: correct usage
+ ArgumentParser argParser =
+ new ArgumentParser(SearchRate.class.getName(), toolDescription,
+ false, true, 1, 0, "[filter] [attributes ...]");
+
+ ArgumentParserConnectionFactory connectionFactory;
+ SearchPerformanceRunner runner;
+
+ StringArgument baseDN;
+ MultiChoiceArgument<SearchScope> searchScope;
+ MultiChoiceArgument<DereferenceAliasesPolicy> dereferencePolicy;
+ BooleanArgument showUsage;
+ StringArgument propertiesFileArgument;
+ BooleanArgument noPropertiesFileArgument;
+
+ try
+ {
+ connectionFactory =
+ new ArgumentParserConnectionFactory(argParser, this);
+ runner = new SearchPerformanceRunner(argParser, this);
+
+ propertiesFileArgument =
+ new StringArgument("propertiesFilePath", null,
+ OPTION_LONG_PROP_FILE_PATH, false, false, true,
+ INFO_PROP_FILE_PATH_PLACEHOLDER.get(), null, null,
+ INFO_DESCRIPTION_PROP_FILE_PATH.get());
+ argParser.addArgument(propertiesFileArgument);
+ argParser.setFilePropertiesArgument(propertiesFileArgument);
+
+ noPropertiesFileArgument =
+ new BooleanArgument("noPropertiesFileArgument", null,
+ OPTION_LONG_NO_PROP_FILE, INFO_DESCRIPTION_NO_PROP_FILE
+ .get());
+ argParser.addArgument(noPropertiesFileArgument);
+ argParser.setNoPropertiesFileArgument(noPropertiesFileArgument);
+
+ showUsage =
+ new BooleanArgument("showUsage", OPTION_SHORT_HELP,
+ OPTION_LONG_HELP, INFO_DESCRIPTION_SHOWUSAGE.get());
+ argParser.addArgument(showUsage);
+ argParser.setUsageArgument(showUsage, getOutputStream());
+
+ baseDN =
+ new StringArgument("baseDN", OPTION_SHORT_BASEDN,
+ OPTION_LONG_BASEDN, true, false, true,
+ INFO_BASEDN_PLACEHOLDER.get(), null, null,
+ INFO_SEARCH_DESCRIPTION_BASEDN.get());
+ baseDN.setPropertyName(OPTION_LONG_BASEDN);
+ argParser.addArgument(baseDN);
+
+ searchScope =
+ new MultiChoiceArgument<SearchScope>("searchScope", 's',
+ "searchScope", false, true, INFO_SEARCH_SCOPE_PLACEHOLDER
+ .get(), SearchScope.values(), false,
+ INFO_SEARCH_DESCRIPTION_SEARCH_SCOPE.get());
+ searchScope.setPropertyName("searchScope");
+ searchScope.setDefaultValue(SearchScope.WHOLE_SUBTREE);
+ argParser.addArgument(searchScope);
+
+ dereferencePolicy =
+ new MultiChoiceArgument<DereferenceAliasesPolicy>(
+ "derefpolicy", 'a', "dereferencePolicy", false, true,
+ INFO_DEREFERENCE_POLICE_PLACEHOLDER.get(),
+ DereferenceAliasesPolicy.values(), false,
+ INFO_SEARCH_DESCRIPTION_DEREFERENCE_POLICY.get());
+ dereferencePolicy.setPropertyName("dereferencePolicy");
+ dereferencePolicy.setDefaultValue(DereferenceAliasesPolicy.NEVER);
+ argParser.addArgument(dereferencePolicy);
+
+ verbose =
+ new BooleanArgument("verbose", 'v', "verbose",
+ INFO_DESCRIPTION_VERBOSE.get());
+ verbose.setPropertyName("verbose");
+ argParser.addArgument(verbose);
+ }
+ catch (ArgumentException ae)
+ {
+ Message message = ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage());
+ println(message);
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ // Parse the command-line arguments provided to this program.
+ try
+ {
+ argParser.parseArguments(args);
+ connectionFactory.validate();
+ runner.validate();
+ }
+ catch (ArgumentException ae)
+ {
+ Message message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
+ println(message);
+ println(argParser.getUsageMessage());
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ // If we should just display usage or version information,
+ // then print it and exit.
+ if (argParser.usageOrVersionDisplayed())
+ {
+ return 0;
+ }
+
+ List<String> attributes = new LinkedList<String>();
+ ArrayList<String> filterAndAttributeStrings =
+ argParser.getTrailingArguments();
+ if (filterAndAttributeStrings.size() > 0)
+ {
+ // the list of trailing arguments should be structured as follow:
+ // the first trailing argument is
+ // considered the filter, the other as attributes.
+ runner.filter = filterAndAttributeStrings.remove(0);
+ // The rest are attributes
+ for (String s : filterAndAttributeStrings)
+ {
+ attributes.add(s);
+ }
+ }
+ runner.attributes =
+ attributes.toArray(new String[attributes.size()]);
+ runner.baseDN = baseDN.getValue();
+ try
+ {
+ runner.scope = searchScope.getTypedValue();
+ runner.dereferencesAliasesPolicy =
+ dereferencePolicy.getTypedValue();
+ }
+ catch (ArgumentException ex1)
+ {
+ println(ex1.getMessageObject());
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ try
+ {
+ // Try it out to make sure the format string and data sources
+ // match.
+ Object[] data =
+ DataSource.generateData(runner.getDataSources(), null);
+ String.format(runner.filter, data);
+ String.format(runner.baseDN, data);
+ }
+ catch (Exception ex1)
+ {
+ println(Message.raw("Error formatting filter or base DN: "
+ + ex1.toString()));
+ return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+ }
+
+ return runner.run(connectionFactory);
+ }
+
+ private final AtomicInteger entryRecentCount = new AtomicInteger();
+
+
+
+ private class SearchPerformanceRunner extends PerformanceRunner
+ {
+ private String filter;
+ private String baseDN;
+ private SearchScope scope;
+ private DereferenceAliasesPolicy dereferencesAliasesPolicy;
+ private String[] attributes;
+
+
+
+ private SearchPerformanceRunner(ArgumentParser argParser,
+ ConsoleApplication app) throws ArgumentException
+ {
+ super(argParser, app);
+ }
+
+
+
+ WorkerThread<?> newWorkerThread(AsynchronousConnection connection,
+ ConnectionFactory<?> connectionFactory)
+ {
+ return new SearchWorkerThread(connection, connectionFactory);
+ }
+
+
+
+ StatsThread newStatsThread()
+ {
+ return new SearchStatsThread();
+ }
+
+
+
+ private class SearchStatsHandler extends
+ UpdateStatsResultHandler<Result> implements
+ SearchResultHandler<Void>
+ {
+ private SearchStatsHandler(long eTime)
+ {
+ super(eTime);
+ }
+
+
+
+ public void handleEntry(Void p, SearchResultEntry entry)
+ {
+ entryRecentCount.getAndIncrement();
+ }
+
+
+
+ public void handleReference(Void p,
+ SearchResultReference reference)
+ {
+ }
+ }
+
+
+
+ private class SearchWorkerThread extends
+ WorkerThread<SearchStatsHandler>
+ {
+ private SearchRequest sr;
+ private Object[] data;
+
+
+
+ private SearchWorkerThread(AsynchronousConnection connection,
+ ConnectionFactory<?> connectionFactory)
+ {
+ super(connection, connectionFactory);
+ }
+
+
+
+ public SearchStatsHandler getHandler(long startTime)
+ {
+ return new SearchStatsHandler(startTime);
+ }
+
+
+
+ public ResultFuture<?> performOperation(
+ AsynchronousConnection connection,
+ SearchStatsHandler handler, DataSource[] dataSources)
+ {
+ if (sr == null)
+ {
+ if (dataSources == null)
+ {
+ sr = Requests.newSearchRequest(baseDN, scope, filter,
+ attributes);
+ }
+ else
+ {
+ data = DataSource.generateData(dataSources, data);
+ sr =
+ Requests.newSearchRequest(String.format(baseDN, data), scope,
+ String.format(filter, data), attributes);
+ }
+ sr.setDereferenceAliasesPolicy(dereferencesAliasesPolicy);
+ }
+ else if (dataSources != null)
+ {
+ data = DataSource.generateData(dataSources, data);
+ sr.setFilter(String.format(filter, data));
+ sr.setName(String.format(baseDN, data));
+ }
+ return connection.search(sr, handler, handler, null);
+ }
+ }
+
+
+
+ private class SearchStatsThread extends StatsThread
+ {
+ private long totalEntryCount;
+ private final String[] extraColumn;
+
+
+
+ private SearchStatsThread()
+ {
+ super(new String[] { "Entries/Srch" });
+ extraColumn = new String[1];
+ }
+
+
+
+ String[] getAdditionalColumns()
+ {
+ int entryCount = entryRecentCount.getAndSet(0);
+ totalEntryCount += entryCount;
+ extraColumn[0] =
+ String.format("%.1f", (double) entryCount / successCount);
+ return extraColumn;
+ }
+ }
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested advanced mode.
+ *
+ * @return Returns <code>true</code> if the user has requested
+ * advanced mode.
+ */
+ public boolean isAdvancedMode()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested interactive
+ * behavior.
+ *
+ * @return Returns <code>true</code> if the user has requested
+ * interactive behavior.
+ */
+ public boolean isInteractive()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not this console application is running in its
+ * menu-driven mode. This can be used to dictate whether output should
+ * go to the error stream or not. In addition, it may also dictate
+ * whether or not sub-menus should display a cancel option as well as
+ * a quit option.
+ *
+ * @return Returns <code>true</code> if this console application is
+ * running in its menu-driven mode.
+ */
+ public boolean isMenuDrivenMode()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested quiet output.
+ *
+ * @return Returns <code>true</code> if the user has requested quiet
+ * output.
+ */
+ public boolean isQuiet()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested script-friendly
+ * output.
+ *
+ * @return Returns <code>true</code> if the user has requested
+ * script-friendly output.
+ */
+ public boolean isScriptFriendly()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Indicates whether or not the user has requested verbose output.
+ *
+ * @return Returns <code>true</code> if the user has requested verbose
+ * output.
+ */
+ public boolean isVerbose()
+ {
+ return verbose.isPresent();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/tools/StringArgument.java b/sdk/src/org/opends/sdk/tools/StringArgument.java
new file mode 100644
index 0000000..b341a5e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/StringArgument.java
@@ -0,0 +1,150 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2008 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.tools;
+
+
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+
+
+
+/**
+ * This class defines an argument type that will accept any string
+ * value.
+ */
+final class StringArgument extends Argument
+{
+ /**
+ * Creates a new string argument with the provided information.
+ *
+ * @param name
+ * The generic name that should be used to refer to this
+ * argument.
+ * @param shortIdentifier
+ * The single-character identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param longIdentifier
+ * The long identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param isRequired
+ * Indicates whether this argument must be specified on the
+ * command line.
+ * @param needsValue
+ * Indicates whether this argument requires a value.
+ * @param valuePlaceholder
+ * The placeholder for the argument value that will be
+ * displayed in usage information, or <CODE>null</CODE> if
+ * this argument does not require a value.
+ * @param description
+ * Message for the description of this argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to
+ * create this argument.
+ */
+ public StringArgument(String name, Character shortIdentifier,
+ String longIdentifier, boolean isRequired, boolean needsValue,
+ Message valuePlaceholder, Message description)
+ throws ArgumentException
+ {
+ super(name, shortIdentifier, longIdentifier, isRequired, false,
+ needsValue, valuePlaceholder, null, null, description);
+ }
+
+
+
+ /**
+ * Creates a new string argument with the provided information.
+ *
+ * @param name
+ * The generic name that should be used to refer to this
+ * argument.
+ * @param shortIdentifier
+ * The single-character identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param longIdentifier
+ * The long identifier for this argument, or
+ * <CODE>null</CODE> if there is none.
+ * @param isRequired
+ * Indicates whether this argument must be specified on the
+ * command line.
+ * @param isMultiValued
+ * Indicates whether this argument may be specified more than
+ * once to provide multiple values.
+ * @param needsValue
+ * Indicates whether this argument requires a value.
+ * @param valuePlaceholder
+ * The placeholder for the argument value that will be
+ * displayed in usage information, or <CODE>null</CODE> if
+ * this argument does not require a value.
+ * @param defaultValue
+ * The default value that should be used for this argument if
+ * none is provided in a properties file or on the command
+ * line. This may be <CODE>null</CODE> if there is no generic
+ * default.
+ * @param propertyName
+ * The name of the property in a property file that may be
+ * used to override the default value but will be overridden
+ * by a command-line argument.
+ * @param description
+ * Message for the description of this argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to
+ * create this argument.
+ */
+ public StringArgument(String name, Character shortIdentifier,
+ String longIdentifier, boolean isRequired, boolean isMultiValued,
+ boolean needsValue, Message valuePlaceholder,
+ String defaultValue, String propertyName, Message description)
+ throws ArgumentException
+ {
+ super(name, shortIdentifier, longIdentifier, isRequired,
+ isMultiValued, needsValue, valuePlaceholder, defaultValue,
+ propertyName, description);
+ }
+
+
+
+ /**
+ * Indicates whether the provided value is acceptable for use in this
+ * argument.
+ *
+ * @param valueString
+ * The value for which to make the determination.
+ * @param invalidReason
+ * A buffer into which the invalid reason may be written if
+ * the value is not acceptable.
+ * @return <CODE>true</CODE> if the value is acceptable, or
+ * <CODE>false</CODE> if it is not.
+ */
+ public boolean valueIsAcceptable(String valueString,
+ MessageBuilder invalidReason)
+ {
+ // All values will be acceptable for this argument.
+ return true;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/tools/Utils.java b/sdk/src/org/opends/sdk/tools/Utils.java
new file mode 100644
index 0000000..da476c3
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/Utils.java
@@ -0,0 +1,476 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2008 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.ToolMessages.*;
+import static org.opends.messages.UtilityMessages.INFO_TIME_IN_DAYS_HOURS_MINUTES_SECONDS;
+import static org.opends.messages.UtilityMessages.INFO_TIME_IN_HOURS_MINUTES_SECONDS;
+import static org.opends.messages.UtilityMessages.INFO_TIME_IN_MINUTES_SECONDS;
+import static org.opends.messages.UtilityMessages.INFO_TIME_IN_SECONDS;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.Connection;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.ErrorResultException;
+import org.opends.sdk.AuthenticatedConnectionFactory.AuthenticatedConnection;
+import org.opends.sdk.controls.*;
+import org.opends.sdk.responses.BindResult;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.server.util.cli.ConsoleApplication;
+
+
+
+/**
+ * This class provides utility functions for all the client side tools.
+ */
+final class Utils
+{
+ // Prevent instantiation.
+ private Utils()
+ {
+ // Do nothing.
+ }
+
+
+
+ /**
+ * Parse the specified command line argument to create the appropriate
+ * LDAPControl. The argument string should be in the format
+ * controloid[:criticality[:value|::b64value|:<fileurl]]
+ *
+ * @param argString
+ * The argument string containing the encoded control
+ * information.
+ * @return The control decoded from the provided string, or
+ * <CODE>null</CODE> if an error occurs while parsing the
+ * argument value.
+ * @throws org.opends.sdk.DecodeException
+ * If an error occurs.
+ */
+ public static GenericControl getControl(String argString)
+ throws DecodeException
+ {
+ String controlOID = null;
+ boolean controlCriticality = false;
+ ByteString controlValue = null;
+
+ int idx = argString.indexOf(":");
+
+ if (idx < 0)
+ {
+ controlOID = argString;
+ }
+ else
+ {
+ controlOID = argString.substring(0, idx);
+ }
+
+ String lowerOID = StaticUtils.toLowerCase(controlOID);
+ if (lowerOID.equals("accountusable")
+ || lowerOID.equals("accountusability"))
+ {
+ controlOID = AccountUsabilityControl.OID_ACCOUNT_USABLE_CONTROL;
+ }
+ else if (lowerOID.equals("authzid")
+ || lowerOID.equals("authorizationidentity"))
+ {
+ controlOID = AuthorizationIdentityControl.OID_AUTHZID_REQUEST;
+ }
+ else if (lowerOID.equals("noop") || lowerOID.equals("no-op"))
+ {
+ // controlOID = OID_LDAP_NOOP_OPENLDAP_ASSIGNED;
+ }
+ else if (lowerOID.equals("subentries"))
+ {
+ // controlOID = OID_LDAP_SUBENTRIES;
+ }
+ else if (lowerOID.equals("managedsait"))
+ {
+ // controlOID = OID_MANAGE_DSAIT_CONTROL;
+ }
+ else if (lowerOID.equals("pwpolicy")
+ || lowerOID.equals("passwordpolicy"))
+ {
+ controlOID = PasswordPolicyControl.OID_PASSWORD_POLICY_CONTROL;
+ }
+ else if (lowerOID.equals("subtreedelete")
+ || lowerOID.equals("treedelete"))
+ {
+ controlOID = SubtreeDeleteControl.OID_SUBTREE_DELETE_CONTROL;
+ }
+ else if (lowerOID.equals("realattrsonly")
+ || lowerOID.equals("realattributesonly"))
+ {
+ // controlOID = OID_REAL_ATTRS_ONLY;
+ }
+ else if (lowerOID.equals("virtualattrsonly")
+ || lowerOID.equals("virtualattributesonly"))
+ {
+ // controlOID = OID_VIRTUAL_ATTRS_ONLY;
+ }
+ else if (lowerOID.equals("effectiverights")
+ || lowerOID.equals("geteffectiverights"))
+ {
+ controlOID = GetEffectiveRightsRequestControl.OID_GET_EFFECTIVE_RIGHTS;
+ }
+
+ if (idx < 0)
+ {
+ return new GenericControl(controlOID);
+ }
+
+ String remainder = argString.substring(idx + 1, argString.length());
+
+ idx = remainder.indexOf(":");
+ if (idx == -1)
+ {
+ if (remainder.equalsIgnoreCase("true"))
+ {
+ controlCriticality = true;
+ }
+ else if (remainder.equalsIgnoreCase("false"))
+ {
+ controlCriticality = false;
+ }
+ else
+ {
+ // TODO: I18N
+ throw DecodeException.error(Message
+ .raw("Invalid format for criticality value:" + remainder));
+ }
+ return new GenericControl(controlOID, controlCriticality);
+
+ }
+
+ String critical = remainder.substring(0, idx);
+ if (critical.equalsIgnoreCase("true"))
+ {
+ controlCriticality = true;
+ }
+ else if (critical.equalsIgnoreCase("false"))
+ {
+ controlCriticality = false;
+ }
+ else
+ {
+ // TODO: I18N
+ throw DecodeException.error(Message
+ .raw("Invalid format for criticality value:" + critical));
+ }
+
+ String valString = remainder.substring(idx + 1, remainder.length());
+ if (valString.charAt(0) == ':')
+ {
+ controlValue = ByteString.valueOf(valString.substring(1,
+ valString.length()));
+ }
+ else if (valString.charAt(0) == '<')
+ {
+ // Read data from the file.
+ String filePath = valString.substring(1, valString.length());
+ try
+ {
+ byte[] val = readBytesFromFile(filePath);
+ controlValue = ByteString.wrap(val);
+ }
+ catch (Exception e)
+ {
+ return null;
+ }
+ }
+ else
+ {
+ controlValue = ByteString.valueOf(valString);
+ }
+
+ return new GenericControl(controlOID, controlCriticality,
+ controlValue);
+ }
+
+
+
+ /**
+ * Read the data from the specified file and return it in a byte
+ * array.
+ *
+ * @param filePath
+ * The path to the file that should be read.
+ * @return A byte array containing the contents of the requested file.
+ * @throws IOException
+ * If a problem occurs while trying to read the specified
+ * file.
+ */
+ public static byte[] readBytesFromFile(String filePath)
+ throws IOException
+ {
+ byte[] val = null;
+ FileInputStream fis = null;
+ try
+ {
+ File file = new File(filePath);
+ fis = new FileInputStream(file);
+ long length = file.length();
+ val = new byte[(int) length];
+ // Read in the bytes
+ int offset = 0;
+ int numRead = 0;
+ while (offset < val.length
+ && (numRead = fis.read(val, offset, val.length - offset)) >= 0)
+ {
+ offset += numRead;
+ }
+
+ // Ensure all the bytes have been read in
+ if (offset < val.length)
+ {
+ throw new IOException("Could not completely read file "
+ + filePath);
+ }
+
+ return val;
+ }
+ finally
+ {
+ if (fis != null)
+ {
+ fis.close();
+ }
+ }
+ }
+
+
+
+ /**
+ * Prints a multi-line error message with the provided information to
+ * the given print stream.
+ *
+ * @param app
+ * The console app to use to write the error message.
+ * @param ere
+ * The error result.
+ * @return The error code.
+ */
+ public static int printErrorMessage(ConsoleApplication app,
+ ErrorResultException ere)
+ {
+ // if ((ere.getMessage() != null) && (ere.getMessage().length() >
+ // 0))
+ // {
+ // app.println(Message.raw(ere.getMessage()));
+ // }
+
+ if (ere.getResult().getResultCode().intValue() >= 0)
+ {
+ app.println(ERR_TOOL_RESULT_CODE.get(ere.getResult()
+ .getResultCode().intValue(), ere.getResult().getResultCode()
+ .toString()));
+ }
+
+ if ((ere.getResult().getDiagnosticMessage() != null)
+ && (ere.getResult().getDiagnosticMessage().length() > 0))
+ {
+ app.println(ERR_TOOL_ERROR_MESSAGE.get(ere.getResult()
+ .getDiagnosticMessage()));
+ }
+
+ if (ere.getResult().getMatchedDN() != null
+ && ere.getResult().getMatchedDN().length() > 0)
+ {
+ app.println(ERR_TOOL_MATCHED_DN.get(ere.getResult()
+ .getMatchedDN()));
+ }
+
+ if (app.isVerbose() && ere.getResult().getCause() != null)
+ {
+ ere.getResult().getCause().printStackTrace(app.getErrorStream());
+ }
+
+ return ere.getResult().getResultCode().intValue();
+ }
+
+
+
+ /**
+ * Retrieves a user-friendly string that indicates the length of time
+ * (in days, hours, minutes, and seconds) in the specified number of
+ * seconds.
+ *
+ * @param numSeconds
+ * The number of seconds to be converted to a more
+ * user-friendly value.
+ * @return The user-friendly representation of the specified number of
+ * seconds.
+ */
+ public static Message secondsToTimeString(int numSeconds)
+ {
+ if (numSeconds < 60)
+ {
+ // We can express it in seconds.
+ return INFO_TIME_IN_SECONDS.get(numSeconds);
+ }
+ else if (numSeconds < 3600)
+ {
+ // We can express it in minutes and seconds.
+ int m = numSeconds / 60;
+ int s = numSeconds % 60;
+ return INFO_TIME_IN_MINUTES_SECONDS.get(m, s);
+ }
+ else if (numSeconds < 86400)
+ {
+ // We can express it in hours, minutes, and seconds.
+ int h = numSeconds / 3600;
+ int m = (numSeconds % 3600) / 60;
+ int s = numSeconds % 3600 % 60;
+ return INFO_TIME_IN_HOURS_MINUTES_SECONDS.get(h, m, s);
+ }
+ else
+ {
+ // We can express it in days, hours, minutes, and seconds.
+ int d = numSeconds / 86400;
+ int h = (numSeconds % 86400) / 3600;
+ int m = (numSeconds % 86400 % 3600) / 60;
+ int s = numSeconds % 86400 % 3600 % 60;
+ return INFO_TIME_IN_DAYS_HOURS_MINUTES_SECONDS.get(d, h, m, s);
+ }
+ }
+
+
+
+ public static void printPasswordPolicyResults(ConsoleApplication app,
+ Connection connection)
+ {
+ if (connection instanceof AuthenticatedConnection)
+ {
+ AuthenticatedConnection conn = (AuthenticatedConnection) connection;
+ BindResult result = conn.getAuthenticatedBindResult();
+
+ Control control = result
+ .getControl(AuthorizationIdentityControl.OID_AUTHZID_RESPONSE);
+ if (control != null)
+ {
+ AuthorizationIdentityControl.Response dc = (AuthorizationIdentityControl.Response) control;
+ Message message = INFO_BIND_AUTHZID_RETURNED.get(dc
+ .getAuthorizationID());
+ app.println(message);
+ }
+ control = result
+ .getControl(PasswordExpiredControl.OID_NS_PASSWORD_EXPIRED);
+ if (control != null)
+ {
+ Message message = INFO_BIND_PASSWORD_EXPIRED.get();
+ app.println(message);
+ }
+ control = result
+ .getControl(PasswordExpiringControl.OID_NS_PASSWORD_EXPIRING);
+ if (control != null)
+ {
+ PasswordExpiringControl dc = (PasswordExpiringControl) control;
+ Message timeString = Utils.secondsToTimeString(dc
+ .getSecondsUntilExpiration());
+ Message message = INFO_BIND_PASSWORD_EXPIRING.get(timeString);
+ app.println(message);
+ }
+ control = result
+ .getControl(PasswordPolicyControl.OID_PASSWORD_POLICY_CONTROL);
+ if (control != null)
+ {
+ PasswordPolicyControl.Response dc = (PasswordPolicyControl.Response) control;
+ PasswordPolicyErrorType errorType = dc.getErrorType();
+ if (errorType == PasswordPolicyErrorType.PASSWORD_EXPIRED)
+ {
+ Message message = INFO_BIND_PASSWORD_EXPIRED.get();
+ app.println(message);
+ }
+ else if (errorType == PasswordPolicyErrorType.ACCOUNT_LOCKED)
+ {
+ Message message = INFO_BIND_ACCOUNT_LOCKED.get();
+ app.println(message);
+ }
+ else if (errorType == PasswordPolicyErrorType.CHANGE_AFTER_RESET)
+ {
+
+ Message message = INFO_BIND_MUST_CHANGE_PASSWORD.get();
+ app.println(message);
+ }
+
+ PasswordPolicyWarningType warningType = dc.getWarningType();
+ if (warningType == PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION)
+ {
+ Message timeString = Utils.secondsToTimeString(dc
+ .getWarningValue());
+ Message message = INFO_BIND_PASSWORD_EXPIRING.get(timeString);
+ app.println(message);
+ }
+ else if (warningType == PasswordPolicyWarningType.GRACE_LOGINS_REMAINING)
+ {
+ Message message = INFO_BIND_GRACE_LOGINS_REMAINING.get(dc
+ .getWarningValue());
+ app.println(message);
+ }
+ }
+ }
+ }
+
+
+
+ /**
+ * Filters the provided value to ensure that it is appropriate for use
+ * as an exit code. Exit code values are generally only allowed to be
+ * between 0 and 255, so any value outside of this range will be
+ * converted to 255, which is the typical exit code used to indicate
+ * an overflow value.
+ *
+ * @param exitCode
+ * The exit code value to be processed.
+ * @return An integer value between 0 and 255, inclusive. If the
+ * provided exit code was already between 0 and 255, then the
+ * original value will be returned. If the provided value was
+ * out of this range, then 255 will be returned.
+ */
+ public static int filterExitCode(int exitCode)
+ {
+ if (exitCode < 0)
+ {
+ return 255;
+ }
+ else if (exitCode > 255)
+ {
+ return 255;
+ }
+ else
+ {
+ return exitCode;
+ }
+ }
+}
diff --git a/sdk/src/org/opends/sdk/util/ASCIICharProp.java b/sdk/src/org/opends/sdk/util/ASCIICharProp.java
new file mode 100644
index 0000000..621da56
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/ASCIICharProp.java
@@ -0,0 +1,372 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.util;
+
+
+
+/**
+ * A {@code ASCIICharProp} provides fast access to ASCII character
+ * properties. In particular, the ability to query whether or not a
+ * character is a letter, a digit, hexadecimal character, as well as
+ * various methods for performing character conversions.
+ * <p>
+ * The methods in this class do not perform memory allocations nor
+ * calculations and so can be used safely in high performance
+ * situations.
+ */
+public final class ASCIICharProp implements Comparable<ASCIICharProp>
+{
+ private final char c;
+ private final char upperCaseChar;
+ private final char lowerCaseChar;
+ private final boolean isUpperCaseChar;
+ private final boolean isLowerCaseChar;
+ private final boolean isDigit;
+ private final boolean isLetter;
+ private final boolean isKeyChar;
+ private final boolean isHexChar;
+ private final int hexValue;
+ private final int decimalValue;
+ private final String stringValue;
+
+ private static final ASCIICharProp[] CHAR_PROPS =
+ new ASCIICharProp[128];
+
+ static
+ {
+ for (int i = 0; i < 128; i++)
+ {
+ CHAR_PROPS[i] = new ASCIICharProp((char) i);
+ }
+ }
+
+
+
+ /**
+ * Returns the character properties for the provided ASCII character.
+ * If a non-ASCII character is provided then this method returns
+ * {@code null}.
+ *
+ * @param c
+ * The ASCII character.
+ * @return The character properties for the provided ASCII character,
+ * or {@code null} if {@code c} is greater than {@code \u007F}
+ * .
+ */
+ public static ASCIICharProp valueOf(char c)
+ {
+ if (c < 128)
+ {
+ return CHAR_PROPS[c];
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+
+
+ /**
+ * Returns the character properties for the provided ASCII character.
+ * If a non-ASCII character is provided then this method returns
+ * {@code null}.
+ *
+ * @param c
+ * The ASCII character.
+ * @return The character properties for the provided ASCII character,
+ * or {@code null} if {@code c} is less than zero or greater
+ * than {@code \u007F} .
+ */
+ public static ASCIICharProp valueOf(int c)
+ {
+ if (c >= 0 && c < 128)
+ {
+ return CHAR_PROPS[c];
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+
+
+ private ASCIICharProp(char c)
+ {
+ this.c = c;
+ this.stringValue = new String(new char[] { c });
+
+ if (c >= 'a' && c <= 'z')
+ {
+ this.upperCaseChar = (char) (c - 32);
+ this.lowerCaseChar = c;
+ this.isUpperCaseChar = false;
+ this.isLowerCaseChar = true;
+ this.isDigit = false;
+ this.isLetter = true;
+ this.isKeyChar = true;
+ this.decimalValue = -1;
+ if (c >= 'a' && c <= 'f')
+ {
+ this.isHexChar = true;
+ this.hexValue = c - 87;
+ }
+ else
+ {
+ this.isHexChar = false;
+ this.hexValue = -1;
+ }
+ }
+ else if (c >= 'A' && c <= 'Z')
+ {
+ this.upperCaseChar = c;
+ this.lowerCaseChar = (char) (c + 32);
+ this.isUpperCaseChar = true;
+ this.isLowerCaseChar = false;
+ this.isDigit = false;
+ this.isLetter = true;
+ this.isKeyChar = true;
+ this.decimalValue = -1;
+ if (c >= 'A' && c <= 'F')
+ {
+ this.isHexChar = true;
+ this.hexValue = c - 55;
+ }
+ else
+ {
+ this.isHexChar = false;
+ this.hexValue = -1;
+ }
+ }
+ else if (c >= '0' && c <= '9')
+ {
+ this.upperCaseChar = c;
+ this.lowerCaseChar = c;
+ this.isUpperCaseChar = false;
+ this.isLowerCaseChar = false;
+ this.isDigit = true;
+ this.isLetter = false;
+ this.isKeyChar = true;
+ this.isHexChar = true;
+ this.hexValue = c - 48;
+ this.decimalValue = c - 48;
+ }
+ else
+ {
+ this.upperCaseChar = c;
+ this.lowerCaseChar = c;
+ this.isUpperCaseChar = false;
+ this.isLowerCaseChar = false;
+ this.isDigit = false;
+ this.isLetter = false;
+ this.isKeyChar = c == '-';
+ this.isHexChar = false;
+ this.hexValue = -1;
+ this.decimalValue = -1;
+ }
+ }
+
+
+
+ /**
+ * Returns the char value associated with this {@code ASCIICharProp}.
+ *
+ * @return The char value associated with this {@code ASCIICharProp}.
+ */
+ public char charValue()
+ {
+ return c;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int compareTo(ASCIICharProp o)
+ {
+ return c - o.c;
+ }
+
+
+
+ /**
+ * Returns the decimal value associated with this {@code
+ * ASCIICharProp}, or {@code -1} if the value is not a decimal digit.
+ *
+ * @return The decimal value associated with this {@code
+ * ASCIICharProp}, or {@code -1} if the value is not a decimal
+ * digit.
+ */
+ public int decimalValue()
+ {
+ return decimalValue;
+ }
+
+
+
+ /**
+ * Returns the hexadecimal value associated with this {@code
+ * ASCIICharProp} , or {@code -1} if the value is not a hexadecimal
+ * digit.
+ *
+ * @return The hexadecimal value associated with this {@code
+ * ASCIICharProp} , or {@code -1} if the value is not a
+ * hexadecimal digit.
+ */
+ public int hexValue()
+ {
+ return hexValue;
+ }
+
+
+
+ /**
+ * Indicates whether or not the char value associated with this
+ * {@code ASCIICharProp} is a decimal digit.
+ *
+ * @return {@code true} if the char value associated with this {@code
+ * ASCIICharProp} is a decimal digit.
+ */
+ public boolean isDigit()
+ {
+ return isDigit;
+ }
+
+
+
+ /**
+ * Indicates whether or not the char value associated with this
+ * {@code ASCIICharProp} is a hexadecimal digit.
+ *
+ * @return {@code true} if the char value associated with this {@code
+ * ASCIICharProp} is a hexadecimal digit.
+ */
+ public boolean isHexDigit()
+ {
+ return isHexChar;
+ }
+
+
+
+ /**
+ * Indicates whether or not the char value associated with this
+ * {@code ASCIICharProp} is a {@code keychar} as defined in RFC 4512.
+ * A {@code keychar} is a letter, a digit, or a hyphen.
+ *
+ * @return {@code true} if the char value associated with this {@code
+ * ASCIICharProp} is a {@code keychar}.
+ */
+ public boolean isKeyChar()
+ {
+ return isKeyChar;
+ }
+
+
+
+ /**
+ * Indicates whether or not the char value associated with this
+ * {@code ASCIICharProp} is a letter.
+ *
+ * @return {@code true} if the char value associated with this {@code
+ * ASCIICharProp} is a letter.
+ */
+ public boolean isLetter()
+ {
+ return isLetter;
+ }
+
+
+
+ /**
+ * Indicates whether or not the char value associated with this
+ * {@code ASCIICharProp} is a lower-case character.
+ *
+ * @return {@code true} if the char value associated with this {@code
+ * ASCIICharProp} is a lower-case character.
+ */
+ public boolean isLowerCase()
+ {
+ return isLowerCaseChar;
+ }
+
+
+
+ /**
+ * Indicates whether or not the char value associated with this
+ * {@code ASCIICharProp} is an upper-case character.
+ *
+ * @return {@code true} if the char value associated with this {@code
+ * ASCIICharProp} is an upper-case character.
+ */
+ public boolean isUpperCase()
+ {
+ return isUpperCaseChar;
+ }
+
+
+
+ /**
+ * Returns the lower-case char value associated with this {@code
+ * ASCIICharProp}.
+ *
+ * @return The lower-case char value associated with this {@code
+ * ASCIICharProp}.
+ */
+ public char toLowerCase()
+ {
+ return lowerCaseChar;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return stringValue;
+ }
+
+
+
+ /**
+ * Returns the upper-case char value associated with this {@code
+ * ASCIICharProp}.
+ *
+ * @return The upper-case char value associated with this {@code
+ * ASCIICharProp}.
+ */
+ public char toUpperCase()
+ {
+ return upperCaseChar;
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/util/Base64.java b/sdk/src/org/opends/sdk/util/Base64.java
new file mode 100755
index 0000000..7d8d6bb
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/Base64.java
@@ -0,0 +1,388 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.util;
+
+
+
+import static org.opends.messages.UtilityMessages.ERR_BASE64_DECODE_INVALID_CHARACTER;
+import static org.opends.messages.UtilityMessages.ERR_BASE64_DECODE_INVALID_LENGTH;
+import static org.opends.sdk.util.Validator.ensureNotNull;
+
+import org.opends.messages.Message;
+
+
+
+/**
+ * This class provides methods for performing base64 encoding and
+ * decoding. Base64 is a mechanism for encoding binary data in ASCII
+ * form by converting sets of three bytes with eight significant bits
+ * each to sets of four bytes with six significant bits each.
+ */
+public final class Base64
+{
+ /**
+ * The set of characters that may be used in base64-encoded values.
+ */
+ private static final char[] BASE64_ALPHABET =
+ ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ + "0123456789+/").toCharArray();
+
+
+
+ /**
+ * Prevent instance creation.
+ */
+ private Base64()
+ {
+ // No implementation required.
+ }
+
+
+
+ /**
+ * Encodes the provided data as a base64 string.
+ *
+ * @param bytes
+ * The data to be encoded.
+ * @return The base64 encoded representation of {@code bytes}.
+ * @throws NullPointerException
+ * If {@code bytes} was {@code null}.
+ */
+ public static String encode(ByteSequence bytes)
+ throws NullPointerException
+ {
+ ensureNotNull(bytes);
+
+ StringBuilder buffer = new StringBuilder(4 * bytes.length() / 3);
+
+ int pos = 0;
+ int iterations = bytes.length() / 3;
+ for (int i = 0; i < iterations; i++)
+ {
+ int value =
+ ((bytes.byteAt(pos++) & 0xFF) << 16)
+ | ((bytes.byteAt(pos++) & 0xFF) << 8)
+ | (bytes.byteAt(pos++) & 0xFF);
+
+ buffer.append(BASE64_ALPHABET[(value >>> 18) & 0x3F]);
+ buffer.append(BASE64_ALPHABET[(value >>> 12) & 0x3F]);
+ buffer.append(BASE64_ALPHABET[(value >>> 6) & 0x3F]);
+ buffer.append(BASE64_ALPHABET[value & 0x3F]);
+ }
+
+ switch (bytes.length() % 3)
+ {
+ case 1:
+ buffer.append(BASE64_ALPHABET[(bytes.byteAt(pos) >>> 2) & 0x3F]);
+ buffer.append(BASE64_ALPHABET[(bytes.byteAt(pos) << 4) & 0x3F]);
+ buffer.append("==");
+ break;
+ case 2:
+ int value =
+ ((bytes.byteAt(pos++) & 0xFF) << 8)
+ | (bytes.byteAt(pos) & 0xFF);
+ buffer.append(BASE64_ALPHABET[(value >>> 10) & 0x3F]);
+ buffer.append(BASE64_ALPHABET[(value >>> 4) & 0x3F]);
+ buffer.append(BASE64_ALPHABET[(value << 2) & 0x3F]);
+ buffer.append("=");
+ break;
+ }
+
+ return buffer.toString();
+ }
+
+
+
+ /**
+ * Decodes the provided base64 encoded data.
+ *
+ * @param base64
+ * The base64 encoded data.
+ * @return The decoded data.
+ * @throws LocalizedIllegalArgumentException
+ * If a problem occurs while attempting to decode {@code
+ * base64}.
+ * @throws NullPointerException
+ * If {@code base64} was {@code null}.
+ */
+ public static ByteString decode(String base64)
+ throws LocalizedIllegalArgumentException, NullPointerException
+ {
+ ensureNotNull(base64);
+
+ // The encoded value must have length that is a multiple of four
+ // bytes.
+ int length = base64.length();
+ if ((length % 4) != 0)
+ {
+ Message message = ERR_BASE64_DECODE_INVALID_LENGTH.get(base64);
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ ByteStringBuilder builder = new ByteStringBuilder(length);
+ for (int i = 0; i < length; i += 4)
+ {
+ boolean append = true;
+ int value = 0;
+
+ for (int j = 0; j < 4; j++)
+ {
+ switch (base64.charAt(i + j))
+ {
+ case 'A':
+ value <<= 6;
+ break;
+ case 'B':
+ value = (value << 6) | 0x01;
+ break;
+ case 'C':
+ value = (value << 6) | 0x02;
+ break;
+ case 'D':
+ value = (value << 6) | 0x03;
+ break;
+ case 'E':
+ value = (value << 6) | 0x04;
+ break;
+ case 'F':
+ value = (value << 6) | 0x05;
+ break;
+ case 'G':
+ value = (value << 6) | 0x06;
+ break;
+ case 'H':
+ value = (value << 6) | 0x07;
+ break;
+ case 'I':
+ value = (value << 6) | 0x08;
+ break;
+ case 'J':
+ value = (value << 6) | 0x09;
+ break;
+ case 'K':
+ value = (value << 6) | 0x0A;
+ break;
+ case 'L':
+ value = (value << 6) | 0x0B;
+ break;
+ case 'M':
+ value = (value << 6) | 0x0C;
+ break;
+ case 'N':
+ value = (value << 6) | 0x0D;
+ break;
+ case 'O':
+ value = (value << 6) | 0x0E;
+ break;
+ case 'P':
+ value = (value << 6) | 0x0F;
+ break;
+ case 'Q':
+ value = (value << 6) | 0x10;
+ break;
+ case 'R':
+ value = (value << 6) | 0x11;
+ break;
+ case 'S':
+ value = (value << 6) | 0x12;
+ break;
+ case 'T':
+ value = (value << 6) | 0x13;
+ break;
+ case 'U':
+ value = (value << 6) | 0x14;
+ break;
+ case 'V':
+ value = (value << 6) | 0x15;
+ break;
+ case 'W':
+ value = (value << 6) | 0x16;
+ break;
+ case 'X':
+ value = (value << 6) | 0x17;
+ break;
+ case 'Y':
+ value = (value << 6) | 0x18;
+ break;
+ case 'Z':
+ value = (value << 6) | 0x19;
+ break;
+ case 'a':
+ value = (value << 6) | 0x1A;
+ break;
+ case 'b':
+ value = (value << 6) | 0x1B;
+ break;
+ case 'c':
+ value = (value << 6) | 0x1C;
+ break;
+ case 'd':
+ value = (value << 6) | 0x1D;
+ break;
+ case 'e':
+ value = (value << 6) | 0x1E;
+ break;
+ case 'f':
+ value = (value << 6) | 0x1F;
+ break;
+ case 'g':
+ value = (value << 6) | 0x20;
+ break;
+ case 'h':
+ value = (value << 6) | 0x21;
+ break;
+ case 'i':
+ value = (value << 6) | 0x22;
+ break;
+ case 'j':
+ value = (value << 6) | 0x23;
+ break;
+ case 'k':
+ value = (value << 6) | 0x24;
+ break;
+ case 'l':
+ value = (value << 6) | 0x25;
+ break;
+ case 'm':
+ value = (value << 6) | 0x26;
+ break;
+ case 'n':
+ value = (value << 6) | 0x27;
+ break;
+ case 'o':
+ value = (value << 6) | 0x28;
+ break;
+ case 'p':
+ value = (value << 6) | 0x29;
+ break;
+ case 'q':
+ value = (value << 6) | 0x2A;
+ break;
+ case 'r':
+ value = (value << 6) | 0x2B;
+ break;
+ case 's':
+ value = (value << 6) | 0x2C;
+ break;
+ case 't':
+ value = (value << 6) | 0x2D;
+ break;
+ case 'u':
+ value = (value << 6) | 0x2E;
+ break;
+ case 'v':
+ value = (value << 6) | 0x2F;
+ break;
+ case 'w':
+ value = (value << 6) | 0x30;
+ break;
+ case 'x':
+ value = (value << 6) | 0x31;
+ break;
+ case 'y':
+ value = (value << 6) | 0x32;
+ break;
+ case 'z':
+ value = (value << 6) | 0x33;
+ break;
+ case '0':
+ value = (value << 6) | 0x34;
+ break;
+ case '1':
+ value = (value << 6) | 0x35;
+ break;
+ case '2':
+ value = (value << 6) | 0x36;
+ break;
+ case '3':
+ value = (value << 6) | 0x37;
+ break;
+ case '4':
+ value = (value << 6) | 0x38;
+ break;
+ case '5':
+ value = (value << 6) | 0x39;
+ break;
+ case '6':
+ value = (value << 6) | 0x3A;
+ break;
+ case '7':
+ value = (value << 6) | 0x3B;
+ break;
+ case '8':
+ value = (value << 6) | 0x3C;
+ break;
+ case '9':
+ value = (value << 6) | 0x3D;
+ break;
+ case '+':
+ value = (value << 6) | 0x3E;
+ break;
+ case '/':
+ value = (value << 6) | 0x3F;
+ break;
+ case '=':
+ append = false;
+ switch (j)
+ {
+ case 2:
+ builder.append((byte) ((value >>> 4) & 0xFF));
+ break;
+ case 3:
+ builder.append((byte) ((value >>> 10) & 0xFF));
+ builder.append((byte) ((value >>> 2) & 0xFF));
+ break;
+ }
+ break;
+ default:
+ Message message =
+ ERR_BASE64_DECODE_INVALID_CHARACTER.get(base64, base64
+ .charAt(i + j));
+ throw new LocalizedIllegalArgumentException(message);
+ }
+
+ if (!append)
+ {
+ break;
+ }
+ }
+
+ if (append)
+ {
+ builder.append((byte) ((value >>> 16) & 0xFF));
+ builder.append((byte) ((value >>> 8) & 0xFF));
+ builder.append((byte) (value & 0xFF));
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ return builder.toByteString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/util/ByteSequence.java b/sdk/src/org/opends/sdk/util/ByteSequence.java
new file mode 100755
index 0000000..922db42
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/ByteSequence.java
@@ -0,0 +1,348 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.util;
+
+
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+
+
+/**
+ * A {@code ByteSequence} is a readable sequence of byte values. This
+ * interface provides uniform, read-only access to many different kinds
+ * of byte sequences.
+ */
+public interface ByteSequence extends Comparable<ByteSequence>
+{
+
+ /**
+ * Returns a {@link ByteSequenceReader} which can be used to
+ * incrementally read and decode data from this byte sequence.
+ * <p>
+ * <b>NOTE:</b> any concurrent changes to the underlying byte sequence
+ * (if mutable) may cause subsequent reads to overrun and fail.
+ *
+ * @return The {@link ByteSequenceReader} which can be used to
+ * incrementally read and decode data from this byte sequence.
+ */
+ ByteSequenceReader asReader();
+
+
+
+ /**
+ * Returns the byte value at the specified index.
+ * <p>
+ * An index ranges from zero to {@code length() - 1}. The first byte
+ * value of the sequence is at index zero, the next at index one, and
+ * so on, as for array indexing.
+ *
+ * @param index
+ * The index of the byte to be returned.
+ * @return The byte value at the specified index.
+ * @throws IndexOutOfBoundsException
+ * If the index argument is negative or not less than
+ * length().
+ */
+ byte byteAt(int index) throws IndexOutOfBoundsException;
+
+
+
+ /**
+ * Compares this byte sequence with the specified byte array
+ * sub-sequence for order. Returns a negative integer, zero, or a
+ * positive integer depending on whether this byte sequence is less
+ * than, equal to, or greater than the specified byte array
+ * sub-sequence.
+ *
+ * @param b
+ * The byte array to compare.
+ * @param offset
+ * The offset of the sub-sequence in the byte array to be
+ * compared; must be non-negative and no larger than {@code
+ * b.length} .
+ * @param length
+ * The length of the sub-sequence in the byte array to be
+ * compared; must be non-negative and no larger than {@code
+ * b.length - offset}.
+ * @return A negative integer, zero, or a positive integer depending
+ * on whether this byte sequence is less than, equal to, or
+ * greater than the specified byte array sub-sequence.
+ * @throws IndexOutOfBoundsException
+ * If {@code offset} is negative or if {@code length} is
+ * negative or if {@code offset + length} is greater than
+ * {@code b.length}.
+ */
+ int compareTo(byte[] b, int offset, int length)
+ throws IndexOutOfBoundsException;
+
+
+
+ /**
+ * Compares this byte sequence with the specified byte sequence for
+ * order. Returns a negative integer, zero, or a positive integer
+ * depending on whether this byte sequence is less than, equal to, or
+ * greater than the specified object.
+ *
+ * @param o
+ * The byte sequence to be compared.
+ * @return A negative integer, zero, or a positive integer depending
+ * on whether this byte sequence is less than, equal to, or
+ * greater than the specified object.
+ */
+ int compareTo(ByteSequence o);
+
+
+
+ /**
+ * Copies the contents of this byte sequence to the provided byte
+ * array.
+ * <p>
+ * Copying will stop when either the entire content of this sequence
+ * has been copied or if the end of the provided byte array has been
+ * reached.
+ * <p>
+ * An invocation of the form:
+ *
+ * <pre>
+ * src.copyTo(b)
+ * </pre>
+ *
+ * Behaves in exactly the same way as the invocation:
+ *
+ * <pre>
+ * src.copyTo(b, 0);
+ * </pre>
+ *
+ * @param b
+ * The byte array to which bytes are to be copied.
+ * @return The byte array.
+ */
+ byte[] copyTo(byte[] b);
+
+
+
+ /**
+ * Copies the contents of this byte sequence to the specified location
+ * in the provided byte array.
+ * <p>
+ * Copying will stop when either the entire content of this sequence
+ * has been copied or if the end of the provided byte array has been
+ * reached.
+ * <p>
+ * An invocation of the form:
+ *
+ * <pre>
+ * src.copyTo(b, offset)
+ * </pre>
+ *
+ * Behaves in exactly the same way as the invocation:
+ *
+ * <pre>
+ * int len = Math.min(src.length(), b.length - offset);
+ * for (int i = 0; i < len; i++)
+ * b[offset + i] = src.get(i);
+ * </pre>
+ *
+ * Except that it is potentially much more efficient.
+ *
+ * @param b
+ * The byte array to which bytes are to be copied.
+ * @param offset
+ * The offset within the array of the first byte to be
+ * written; must be non-negative and no larger than b.length.
+ * @return The byte array.
+ * @throws IndexOutOfBoundsException
+ * If {@code offset} is negative.
+ */
+ byte[] copyTo(byte[] b, int offset) throws IndexOutOfBoundsException;
+
+
+
+ /**
+ * Appends the entire contents of this byte sequence to the provided
+ * {@link ByteStringBuilder}.
+ *
+ * @param builder
+ * The builder to copy to.
+ * @return The builder.
+ */
+ ByteStringBuilder copyTo(ByteStringBuilder builder);
+
+
+
+ /**
+ * Copies the entire contents of this byte sequence to the provided
+ * {@code OutputStream}.
+ *
+ * @param stream
+ * The {@code OutputStream} to copy to.
+ * @return The {@code OutputStream}.
+ * @throws IOException
+ * If an error occurs while writing to the {@code
+ * OutputStream}.
+ */
+ OutputStream copyTo(OutputStream stream) throws IOException;
+
+
+
+ /**
+ * Indicates whether the provided byte array sub-sequence is equal to
+ * this byte sequence. In order for it to be considered equal, the
+ * provided byte array sub-sequence must contain the same bytes in the
+ * same order.
+ *
+ * @param b
+ * The byte array for which to make the determination.
+ * @param offset
+ * The offset of the sub-sequence in the byte array to be
+ * compared; must be non-negative and no larger than {@code
+ * b.length} .
+ * @param length
+ * The length of the sub-sequence in the byte array to be
+ * compared; must be non-negative and no larger than {@code
+ * b.length - offset}.
+ * @return {@code true} if the content of the provided byte array
+ * sub-sequence is equal to that of this byte sequence, or
+ * {@code false} if not.
+ * @throws IndexOutOfBoundsException
+ * If {@code offset} is negative or if {@code length} is
+ * negative or if {@code offset + length} is greater than
+ * {@code b.length}.
+ */
+ boolean equals(byte[] b, int offset, int length)
+ throws IndexOutOfBoundsException;
+
+
+
+ /**
+ * Indicates whether the provided object is equal to this byte
+ * sequence. In order for it to be considered equal, the provided
+ * object must be a byte sequence containing the same bytes in the
+ * same order.
+ *
+ * @param o
+ * The object for which to make the determination.
+ * @return {@code true} if the provided object is a byte sequence
+ * whose content is equal to that of this byte sequence, or
+ * {@code false} if not.
+ */
+ boolean equals(Object o);
+
+
+
+ /**
+ * Returns a hash code for this byte sequence. It will be the sum of
+ * all of the bytes contained in the byte sequence.
+ *
+ * @return A hash code for this byte sequence.
+ */
+ int hashCode();
+
+
+
+ /**
+ * Returns the length of this byte sequence.
+ *
+ * @return The length of this byte sequence.
+ */
+ int length();
+
+
+
+ /**
+ * Returns a new byte sequence that is a subsequence of this byte
+ * sequence.
+ * <p>
+ * The subsequence starts with the byte value at the specified {@code
+ * start} index and ends with the byte value at index {@code end - 1}.
+ * The length (in bytes) of the returned sequence is {@code end -
+ * start}, so if {@code start == end} then an empty sequence is
+ * returned.
+ * <p>
+ * <b>NOTE:</b> changes to the underlying byte sequence (if mutable)
+ * may render the returned sub-sequence invalid.
+ *
+ * @param start
+ * The start index, inclusive.
+ * @param end
+ * The end index, exclusive.
+ * @return The newly created byte subsequence.
+ * @throws IndexOutOfBoundsException
+ * If {@code start} or {@code end} are negative, if {@code
+ * end} is greater than {@code length()}, or if {@code
+ * start} is greater than {@code end}.
+ */
+ ByteSequence subSequence(int start, int end)
+ throws IndexOutOfBoundsException;
+
+
+
+ /**
+ * Returns a byte array containing the bytes in this sequence in the
+ * same order as this sequence. The length of the byte array will be
+ * the length of this sequence.
+ * <p>
+ * An invocation of the form:
+ *
+ * <pre>
+ * src.toByteArray()
+ * </pre>
+ *
+ * Behaves in exactly the same way as the invocation:
+ *
+ * <pre>
+ * src.copyTo(new byte[src.length()]);
+ * </pre>
+ *
+ * @return A byte array consisting of exactly this sequence of bytes.
+ */
+ byte[] toByteArray();
+
+
+
+ /**
+ * Returns the {@link ByteString} representation of this byte
+ * sequence.
+ *
+ * @return The {@link ByteString} representation of this byte
+ * sequence.
+ */
+ ByteString toByteString();
+
+
+
+ /**
+ * Returns the UTF-8 decoded string representation of this byte
+ * sequence. If UTF-8 decoding fails, the platform's default encoding
+ * will be used.
+ *
+ * @return The string representation of this byte sequence.
+ */
+ String toString();
+}
diff --git a/sdk/src/org/opends/sdk/util/ByteSequenceOutputStream.java b/sdk/src/org/opends/sdk/util/ByteSequenceOutputStream.java
new file mode 100644
index 0000000..7f77f30
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/ByteSequenceOutputStream.java
@@ -0,0 +1,113 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2008 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An adapter class that allows writing to an byte string builder
+ * with the outputstream interface.
+ */
+public final class ByteSequenceOutputStream extends OutputStream {
+
+ private final ByteStringBuilder buffer;
+
+ /**
+ * Creates a new byte string builder output stream.
+ *
+ * @param buffer
+ * The underlying byte string builder.
+ */
+ public ByteSequenceOutputStream(ByteStringBuilder buffer)
+ {
+ this.buffer = buffer;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void write(int i) throws IOException {
+ buffer.append(((byte) (i & 0xFF)));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void write(byte[] bytes) throws IOException {
+ buffer.append(bytes);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void write(byte[] bytes, int i, int i1) throws IOException {
+ buffer.append(bytes, i, i1);
+ }
+
+ /**
+ * Gets the length of the underlying byte string builder.
+ *
+ * @return The length of the underlying byte string builder.
+ */
+ public int length() {
+ return buffer.length();
+ }
+
+
+
+ /**
+ * Writes the content of the underlying byte string builder to the
+ * provided output stream.
+ *
+ * @param stream
+ * The output stream.
+ * @throws IOException
+ * If an I/O error occurs. In particular, an {@code
+ * IOException} is thrown if the output stream is closed.
+ */
+ public void writeTo(OutputStream stream) throws IOException
+ {
+ buffer.copyTo(stream);
+ }
+
+ /**
+ * Resets this output stream such that the underlying byte string
+ * builder is empty.
+ */
+ public void reset()
+ {
+ buffer.clear();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void close() throws IOException {
+ buffer.clear();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/util/ByteSequenceReader.java b/sdk/src/org/opends/sdk/util/ByteSequenceReader.java
new file mode 100755
index 0000000..35ca336
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/ByteSequenceReader.java
@@ -0,0 +1,510 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.util;
+
+
+
+/**
+ * An interface for iteratively reading date from a {@link ByteSequence}
+ * . {@code ByteSequenceReader} must be created using the associated
+ * {@code ByteSequence}'s {@code asReader()} method.
+ */
+public final class ByteSequenceReader
+{
+
+ // The current position in the byte sequence.
+ private int pos = 0;
+
+ // The underlying byte sequence.
+ private final ByteSequence sequence;
+
+
+
+ /**
+ * Creates a new byte sequence reader whose source is the provided
+ * byte sequence.
+ * <p>
+ * <b>NOTE:</b> any concurrent changes to the underlying byte sequence
+ * (if mutable) may cause subsequent reads to overrun and fail.
+ * <p>
+ * This constructor is package private: construction must be performed
+ * using {@link ByteSequence#asReader()}.
+ *
+ * @param sequence
+ * The byte sequence to be read.
+ */
+ ByteSequenceReader(ByteSequence sequence)
+ {
+ this.sequence = sequence;
+ }
+
+
+
+ /**
+ * Relative get method. Reads the byte at the current position.
+ *
+ * @return The byte at this reader's current position.
+ * @throws IndexOutOfBoundsException
+ * If there are fewer bytes remaining in this reader than
+ * are required to satisfy the request, that is, if {@code
+ * remaining() < 1}.
+ */
+ public byte get() throws IndexOutOfBoundsException
+ {
+ final byte b = sequence.byteAt(pos);
+ pos++;
+ return b;
+ }
+
+
+
+ /**
+ * Relative bulk get method. This method transfers bytes from this
+ * reader into the given destination array. An invocation of this
+ * method of the form:
+ *
+ * <pre>
+ * src.get(b);
+ * </pre>
+ *
+ * Behaves in exactly the same way as the invocation:
+ *
+ * <pre>
+ * src.get(b, 0, b.length);
+ * </pre>
+ *
+ * @param b
+ * The byte array into which bytes are to be written.
+ * @throws IndexOutOfBoundsException
+ * If there are fewer bytes remaining in this reader than
+ * are required to satisfy the request, that is, if {@code
+ * remaining() < b.length}.
+ */
+ public void get(byte[] b) throws IndexOutOfBoundsException
+ {
+ get(b, 0, b.length);
+ }
+
+
+
+ /**
+ * Relative bulk get method. Copies {@code length} bytes from this
+ * reader into the given array, starting at the current position of
+ * this reader and at the given {@code offset} in the array. The
+ * position of this reader is then incremented by {@code length}. In
+ * other words, an invocation of this method of the form:
+ *
+ * <pre>
+ * src.get(b, offset, length);
+ * </pre>
+ *
+ * Has exactly the same effect as the loop:
+ *
+ * <pre>
+ * for (int i = offset; i < offset + length; i++)
+ * b[i] = src.get();
+ * </pre>
+ *
+ * Except that it first checks that there are sufficient bytes in this
+ * buffer and it is potentially much more efficient.
+ *
+ * @param b
+ * The byte array into which bytes are to be written.
+ * @param offset
+ * The offset within the array of the first byte to be
+ * written; must be non-negative and no larger than {@code
+ * b.length}.
+ * @param length
+ * The number of bytes to be written to the given array; must
+ * be non-negative and no larger than {@code b.length} .
+ * @throws IndexOutOfBoundsException
+ * If there are fewer bytes remaining in this reader than
+ * are required to satisfy the request, that is, if {@code
+ * remaining() < length}.
+ */
+ public void get(byte[] b, int offset, int length)
+ throws IndexOutOfBoundsException
+ {
+ if (offset < 0 || length < 0 || offset + length > b.length
+ || length > remaining())
+ {
+ throw new IndexOutOfBoundsException();
+ }
+
+ sequence.subSequence(pos, pos + length).copyTo(b, offset);
+ pos += length;
+ }
+
+
+
+ /**
+ * Relative get method for reading a multi-byte BER length. Reads the
+ * next one to five bytes at this reader's current position, composing
+ * them into a integer value and then increments the position by the
+ * number of bytes read.
+ *
+ * @return The integer value representing the length at this reader's
+ * current position.
+ * @throws IndexOutOfBoundsException
+ * If there are fewer bytes remaining in this reader than
+ * are required to satisfy the request.
+ */
+ public int getBERLength() throws IndexOutOfBoundsException
+ {
+ // Make sure we have at least one byte to read.
+ int newPos = pos + 1;
+ if (newPos > sequence.length())
+ {
+ throw new IndexOutOfBoundsException();
+ }
+
+ int length = sequence.byteAt(pos) & 0x7F;
+ if (length != sequence.byteAt(pos))
+ {
+ // Its a multi-byte length
+ final int numLengthBytes = length;
+ newPos = pos + 1 + numLengthBytes;
+ // Make sure we have the bytes needed
+ if (numLengthBytes > 4 || newPos > sequence.length())
+ {
+ // Shouldn't have more than 4 bytes
+ throw new IndexOutOfBoundsException();
+ }
+
+ length = 0x00;
+ for (int i = pos + 1; i < newPos; i++)
+ {
+ length = length << 8 | sequence.byteAt(i) & 0xFF;
+ }
+ }
+
+ pos = newPos;
+ return length;
+ }
+
+
+
+ /**
+ * Relative bulk get method. Returns a {@link ByteSequence} whose
+ * content is the next {@code length} bytes from this reader, starting
+ * at the current position of this reader. The position of this reader
+ * is then incremented by {@code length}.
+ * <p>
+ * <b>NOTE:</b> The value returned from this method should NEVER be
+ * cached as it prevents the contents of the underlying byte stream
+ * from being garbage collected.
+ *
+ * @param length
+ * The length of the byte sequence to be returned.
+ * @return The byte sequence whose content is the next {@code length}
+ * bytes from this reader.
+ * @throws IndexOutOfBoundsException
+ * If there are fewer bytes remaining in this reader than
+ * are required to satisfy the request, that is, if {@code
+ * remaining() < length}.
+ */
+ public ByteSequence getByteSequence(int length)
+ throws IndexOutOfBoundsException
+ {
+ final int newPos = pos + length;
+ final ByteSequence subSequence = sequence.subSequence(pos, newPos);
+ pos = newPos;
+ return subSequence;
+ }
+
+
+
+ /**
+ * Relative bulk get method. Returns a {@link ByteString} whose
+ * content is the next {@code length} bytes from this reader, starting
+ * at the current position of this reader. The position of this reader
+ * is then incremented by {@code length}.
+ * <p>
+ * An invocation of this method of the form:
+ *
+ * <pre>
+ * src.getByteString(length);
+ * </pre>
+ *
+ * Has exactly the same effect as:
+ *
+ * <pre>
+ * src.getByteSequence(length).toByteString();
+ * </pre>
+ *
+ * <b>NOTE:</b> The value returned from this method should NEVER be
+ * cached as it prevents the contents of the underlying byte stream
+ * from being garbage collected.
+ *
+ * @param length
+ * The length of the byte string to be returned.
+ * @return The byte string whose content is the next {@code length}
+ * bytes from this reader.
+ * @throws IndexOutOfBoundsException
+ * If there are fewer bytes remaining in this reader than
+ * are required to satisfy the request, that is, if {@code
+ * remaining() < length}.
+ */
+ public ByteString getByteString(int length)
+ throws IndexOutOfBoundsException
+ {
+ return getByteSequence(length).toByteString();
+ }
+
+
+
+ /**
+ * Relative get method for reading an integer value. Reads the next
+ * four bytes at this reader's current position, composing them into
+ * an integer value according to big-endian byte order, and then
+ * increments the position by four.
+ *
+ * @return The integer value at this reader's current position.
+ * @throws IndexOutOfBoundsException
+ * If there are fewer bytes remaining in this reader than
+ * are required to satisfy the request, that is, if {@code
+ * remaining() < 4}.
+ */
+ public int getInt() throws IndexOutOfBoundsException
+ {
+ if (remaining() < 4)
+ {
+ throw new IndexOutOfBoundsException();
+ }
+
+ int v = 0;
+ for (int i = 0; i < 4; i++)
+ {
+ v <<= 8;
+ v |= sequence.byteAt(pos++) & 0xFF;
+ }
+
+ return v;
+ }
+
+
+
+ /**
+ * Relative get method for reading a long value. Reads the next eight
+ * bytes at this reader's current position, composing them into a long
+ * value according to big-endian byte order, and then increments the
+ * position by eight.
+ *
+ * @return The long value at this reader's current position.
+ * @throws IndexOutOfBoundsException
+ * If there are fewer bytes remaining in this reader than
+ * are required to satisfy the request, that is, if {@code
+ * remaining() < 8}.
+ */
+ public long getLong() throws IndexOutOfBoundsException
+ {
+ if (remaining() < 8)
+ {
+ throw new IndexOutOfBoundsException();
+ }
+
+ long v = 0;
+ for (int i = 0; i < 8; i++)
+ {
+ v <<= 8;
+ v |= sequence.byteAt(pos++) & 0xFF;
+ }
+
+ return v;
+ }
+
+
+
+ /**
+ * Relative get method for reading an short value. Reads the next 2
+ * bytes at this reader's current position, composing them into an
+ * short value according to big-endian byte order, and then increments
+ * the position by two.
+ *
+ * @return The integer value at this reader's current position.
+ * @throws IndexOutOfBoundsException
+ * If there are fewer bytes remaining in this reader than
+ * are required to satisfy the request, that is, if {@code
+ * remaining() < 2}.
+ */
+ public short getShort() throws IndexOutOfBoundsException
+ {
+ if (remaining() < 2)
+ {
+ throw new IndexOutOfBoundsException();
+ }
+
+ short v = 0;
+ for (int i = 0; i < 2; i++)
+ {
+ v <<= 8;
+ v |= sequence.byteAt(pos++) & 0xFF;
+ }
+
+ return v;
+ }
+
+
+
+ /**
+ * Relative get method for reading a UTF-8 encoded string. Reads the
+ * next number of specified bytes at this reader's current position,
+ * decoding them into a string using UTF-8 and then increments the
+ * position by the number of bytes read. If UTF-8 decoding fails, the
+ * platform's default encoding will be used.
+ *
+ * @param length
+ * The number of bytes to read and decode.
+ * @return The string value at the reader's current position.
+ * @throws IndexOutOfBoundsException
+ * If there are fewer bytes remaining in this reader than
+ * are required to satisfy the request, that is, if {@code
+ * remaining() < length}.
+ */
+ public String getString(int length) throws IndexOutOfBoundsException
+ {
+ if (remaining() < length)
+ {
+ throw new IndexOutOfBoundsException();
+ }
+
+ final int newPos = pos + length;
+ final String str =
+ sequence.subSequence(pos, pos + length).toString();
+ pos = newPos;
+ return str;
+ }
+
+
+
+ /**
+ * Returns this reader's position.
+ *
+ * @return The position of this reader.
+ */
+ public int position()
+ {
+ return pos;
+ }
+
+
+
+ /**
+ * Sets this reader's position.
+ *
+ * @param pos
+ * The new position value; must be non-negative and no larger
+ * than the length of the underlying byte sequence.
+ * @throws IndexOutOfBoundsException
+ * If the position is negative or larger than the length of
+ * the underlying byte sequence.
+ */
+ public void position(int pos) throws IndexOutOfBoundsException
+ {
+ if (pos > sequence.length() || pos < 0)
+ {
+ throw new IndexOutOfBoundsException();
+ }
+
+ this.pos = pos;
+ }
+
+
+
+ /**
+ * Returns the number of bytes between the current position and the
+ * end of the underlying byte sequence.
+ *
+ * @return The number of bytes between the current position and the
+ * end of the underlying byte sequence.
+ */
+ public int remaining()
+ {
+ return sequence.length() - pos;
+ }
+
+
+
+ /**
+ * Rewinds this reader's position to zero.
+ * <p>
+ * An invocation of this method of the form:
+ *
+ * <pre>
+ * src.rewind();
+ * </pre>
+ *
+ * Has exactly the same effect as:
+ *
+ * <pre>
+ * src.position(0);
+ * </pre>
+ */
+ public void rewind()
+ {
+ position(0);
+ }
+
+
+
+ /**
+ * Skips the given number of bytes. Negative values are allowed.
+ * <p>
+ * An invocation of this method of the form:
+ *
+ * <pre>
+ * src.skip(length);
+ * </pre>
+ *
+ * Has exactly the same effect as:
+ *
+ * <pre>
+ * src.position(position() + length);
+ * </pre>
+ *
+ * @param length
+ * The number of bytes to skip.
+ * @throws IndexOutOfBoundsException
+ * If the new position is less than 0 or greater than the
+ * length of the underlying byte sequence.
+ */
+ public void skip(int length) throws IndexOutOfBoundsException
+ {
+ position(pos + length);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return sequence.toString();
+ }
+}
diff --git a/sdk/src/org/opends/sdk/util/ByteString.java b/sdk/src/org/opends/sdk/util/ByteString.java
new file mode 100755
index 0000000..c626b2d
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/ByteString.java
@@ -0,0 +1,681 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.util;
+
+
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.logging.Level;
+
+
+
+/**
+ * An immutable sequence of bytes backed by a byte array.
+ */
+public final class ByteString implements ByteSequence
+{
+
+ // Singleton empty byte string.
+ private static final ByteString EMPTY = wrap(new byte[0]);
+
+
+
+ /**
+ * Returns an empty byte string.
+ *
+ * @return An empty byte string.
+ */
+ public static ByteString empty()
+ {
+ return EMPTY;
+ }
+
+
+
+ /**
+ * Returns a byte string containing the big-endian encoded bytes of
+ * the provided integer.
+ *
+ * @param i
+ * The integer to encode.
+ * @return The byte string containing the big-endian encoded bytes of
+ * the provided integer.
+ */
+ public static ByteString valueOf(int i)
+ {
+ final byte[] bytes = new byte[4];
+ for (int j = 3; j >= 0; j--)
+ {
+ bytes[j] = (byte) (i & 0xFF);
+ i >>>= 8;
+ }
+ return wrap(bytes);
+ }
+
+
+
+ /**
+ * Returns a byte string containing the big-endian encoded bytes of
+ * the provided long.
+ *
+ * @param l
+ * The long to encode.
+ * @return The byte string containing the big-endian encoded bytes of
+ * the provided long.
+ */
+ public static ByteString valueOf(long l)
+ {
+ final byte[] bytes = new byte[8];
+ for (int i = 7; i >= 0; i--)
+ {
+ bytes[i] = (byte) (l & 0xFF);
+ l >>>= 8;
+ }
+ return wrap(bytes);
+ }
+
+
+
+ /**
+ * Returns a byte string containing the provided object. If the object
+ * is an instance of {@code ByteSequence} then it is converted to a
+ * byte string using the {@code toByteString()} method. Otherwise a
+ * new byte string is created containing the UTF-8 encoded bytes of
+ * the string representation of the provided object.
+ *
+ * @param o
+ * The object to use.
+ * @return The byte string containing the provided object.
+ */
+ public static ByteString valueOf(Object o)
+ {
+ if (o instanceof ByteSequence)
+ {
+ return ((ByteSequence) o).toByteString();
+ }
+ else
+ {
+ return wrap(StaticUtils.getBytes(o.toString()));
+ }
+ }
+
+
+
+ /**
+ * Returns a byte string containing the UTF-8 encoded bytes of the
+ * provided string.
+ *
+ * @param s
+ * The string to use.
+ * @return The byte string with the encoded bytes of the provided
+ * string.
+ */
+ public static ByteString valueOf(String s)
+ {
+ return wrap(StaticUtils.getBytes(s));
+ }
+
+
+
+ /**
+ * Returns a byte string that wraps the provided byte array.
+ * <p>
+ * <b>NOTE:</b> this method takes ownership of the provided byte array
+ * and, therefore, the byte array MUST NOT be altered directly after
+ * this method returns.
+ *
+ * @param b
+ * The byte array to wrap.
+ * @return The byte string that wraps the given byte array.
+ */
+ public static ByteString wrap(byte[] b)
+ {
+ return new ByteString(b, 0, b.length);
+ }
+
+
+
+ /**
+ * Returns a byte string that wraps a subsequence of the provided byte
+ * array.
+ * <p>
+ * <b>NOTE:</b> this method takes ownership of the provided byte array
+ * and, therefore, the byte array MUST NOT be altered directly after
+ * this method returns.
+ *
+ * @param b
+ * The byte array to wrap.
+ * @param offset
+ * The offset of the byte array to be used; must be
+ * non-negative and no larger than {@code b.length} .
+ * @param length
+ * The length of the byte array to be used; must be
+ * non-negative and no larger than {@code b.length - offset}.
+ * @return The byte string that wraps the given byte array.
+ * @throws IndexOutOfBoundsException
+ * If {@code offset} is negative or if {@code length} is
+ * negative or if {@code offset + length} is greater than
+ * {@code b.length}.
+ */
+ public static ByteString wrap(byte[] b, int offset, int length)
+ throws IndexOutOfBoundsException
+ {
+ checkArrayBounds(b, offset, length);
+ return new ByteString(b, offset, length);
+ }
+
+
+
+ /**
+ * Checks the array bounds of the provided byte array sub-sequence,
+ * throwing an {@code IndexOutOfBoundsException} if they are illegal.
+ *
+ * @param b
+ * The byte array.
+ * @param offset
+ * The offset of the byte array to be checked; must be
+ * non-negative and no larger than {@code b.length}.
+ * @param length
+ * The length of the byte array to be checked; must be
+ * non-negative and no larger than {@code b.length - offset}.
+ * @throws IndexOutOfBoundsException
+ * If {@code offset} is negative or if {@code length} is
+ * negative or if {@code offset + length} is greater than
+ * {@code b.length}.
+ */
+ static void checkArrayBounds(byte[] b, int offset, int length)
+ throws IndexOutOfBoundsException
+ {
+ if (offset < 0 || offset > b.length || length < 0
+ || offset + length > b.length || offset + length < 0)
+ {
+ throw new IndexOutOfBoundsException();
+ }
+ }
+
+
+
+ /**
+ * Compares two byte array sub-sequences and returns a value that
+ * indicates their relative order.
+ *
+ * @param b1
+ * The byte array containing the first sub-sequence.
+ * @param offset1
+ * The offset of the first byte array sub-sequence.
+ * @param length1
+ * The length of the first byte array sub-sequence.
+ * @param b2
+ * The byte array containing the second sub-sequence.
+ * @param offset2
+ * The offset of the second byte array sub-sequence.
+ * @param length2
+ * The length of the second byte array sub-sequence.
+ * @return A negative integer if first byte array sub-sequence should
+ * come before the second byte array sub-sequence in ascending
+ * order, a positive integer if the first byte array
+ * sub-sequence should come after the byte array sub-sequence
+ * in ascending order, or zero if there is no difference
+ * between the two byte array sub-sequences with regard to
+ * ordering.
+ */
+ static int compareTo(byte[] b1, int offset1, int length1, byte[] b2,
+ int offset2, int length2)
+ {
+ int count = Math.min(length1, length2);
+ int i = offset1;
+ int j = offset2;
+ while (count-- != 0)
+ {
+ final int firstByte = 0xFF & b1[i++];
+ final int secondByte = 0xFF & b2[j++];
+ if (firstByte != secondByte)
+ {
+ return firstByte - secondByte;
+ }
+ }
+ return length1 - length2;
+ }
+
+
+
+ /**
+ * Indicates whether two byte array sub-sequences are equal. In order
+ * for them to be considered equal, they must contain the same bytes
+ * in the same order.
+ *
+ * @param b1
+ * The byte array containing the first sub-sequence.
+ * @param offset1
+ * The offset of the first byte array sub-sequence.
+ * @param length1
+ * The length of the first byte array sub-sequence.
+ * @param b2
+ * The byte array containing the second sub-sequence.
+ * @param offset2
+ * The offset of the second byte array sub-sequence.
+ * @param length2
+ * The length of the second byte array sub-sequence.
+ * @return {@code true} if the two byte array sub-sequences have the
+ * same content, or {@code false} if not.
+ */
+ static boolean equals(byte[] b1, int offset1, int length1, byte[] b2,
+ int offset2, int length2)
+ {
+ if (length1 != length2)
+ {
+ return false;
+ }
+
+ int i = offset1;
+ int j = offset2;
+ int count = length1;
+ while (count-- != 0)
+ {
+ if (b1[i++] != b2[j++])
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+
+
+ /**
+ * Returns a hash code for the provided byte array sub-sequence.
+ *
+ * @param b
+ * The byte array.
+ * @param offset
+ * The offset of the byte array sub-sequence.
+ * @param length
+ * The length of the byte array sub-sequence.
+ * @return A hash code for the provided byte array sub-sequence.
+ */
+ static int hashCode(byte[] b, int offset, int length)
+ {
+ int hashCode = 1;
+ int i = offset;
+ int count = length;
+ while (count-- != 0)
+ {
+ hashCode = 31 * hashCode + b[i++];
+ }
+ return hashCode;
+ }
+
+
+
+ /**
+ * Returns the UTF-8 decoded string representation of the provided
+ * byte array sub-sequence. If UTF-8 decoding fails, the platform's
+ * default encoding will be used.
+ *
+ * @param b
+ * The byte array.
+ * @param offset
+ * The offset of the byte array sub-sequence.
+ * @param length
+ * The length of the byte array sub-sequence.
+ * @return The string representation of the byte array sub-sequence.
+ */
+ static String toString(byte[] b, int offset, int length)
+ {
+ String stringValue;
+ try
+ {
+ stringValue = new String(b, offset, length, "UTF-8");
+ }
+ catch (final Exception e)
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ StaticUtils.DEBUG_LOG.warning("Unable to decode ByteString "
+ + "bytes as UTF-8 string: " + e.toString());
+ }
+
+ stringValue = new String(b, offset, length);
+ }
+
+ return stringValue;
+ }
+
+ // These are package private so that compression and crypto
+ // functionality may directly access the fields.
+
+ // The buffer where data is stored.
+ final byte[] buffer;
+
+ // The number of bytes to expose from the buffer.
+ final int length;
+
+ // The start index of the range of bytes to expose through this byte
+ // string.
+ final int offset;
+
+
+
+ /**
+ * Creates a new byte string that wraps a subsequence of the provided
+ * byte array.
+ * <p>
+ * <b>NOTE:</b> this method takes ownership of the provided byte array
+ * and, therefore, the byte array MUST NOT be altered directly after
+ * this method returns.
+ *
+ * @param b
+ * The byte array to wrap.
+ * @param offset
+ * The offset of the byte array to be used; must be
+ * non-negative and no larger than {@code b.length} .
+ * @param length
+ * The length of the byte array to be used; must be
+ * non-negative and no larger than {@code b.length - offset}.
+ */
+ private ByteString(byte[] b, int offset, int length)
+ {
+ this.buffer = b;
+ this.offset = offset;
+ this.length = length;
+ }
+
+
+
+ /**
+ * Returns a {@link ByteSequenceReader} which can be used to
+ * incrementally read and decode data from this byte string.
+ *
+ * @return The {@link ByteSequenceReader} which can be used to
+ * incrementally read and decode data from this byte string.
+ */
+ public ByteSequenceReader asReader()
+ {
+ return new ByteSequenceReader(this);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public byte byteAt(int index) throws IndexOutOfBoundsException
+ {
+ if (index >= length || index < 0)
+ {
+ throw new IndexOutOfBoundsException();
+ }
+ return buffer[offset + index];
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int compareTo(byte[] b, int offset, int length)
+ throws IndexOutOfBoundsException
+ {
+ checkArrayBounds(b, offset, length);
+ return compareTo(this.buffer, this.offset, this.length, b, offset,
+ length);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int compareTo(ByteSequence o)
+ {
+ if (this == o)
+ {
+ return 0;
+ }
+ return -o.compareTo(buffer, offset, length);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public byte[] copyTo(byte[] b)
+ {
+ copyTo(b, 0);
+ return b;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public byte[] copyTo(byte[] b, int offset)
+ throws IndexOutOfBoundsException
+ {
+ if (offset < 0)
+ {
+ throw new IndexOutOfBoundsException();
+ }
+ System.arraycopy(buffer, this.offset, b, offset, Math.min(length,
+ b.length - offset));
+ return b;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteStringBuilder copyTo(ByteStringBuilder builder)
+ {
+ builder.append(buffer, offset, length);
+ return builder;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public OutputStream copyTo(OutputStream stream) throws IOException
+ {
+ stream.write(buffer, offset, length);
+ return stream;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean equals(byte[] b, int offset, int length)
+ throws IndexOutOfBoundsException
+ {
+ checkArrayBounds(b, offset, length);
+ return equals(this.buffer, this.offset, this.length, b, offset,
+ length);
+ }
+
+
+
+ /**
+ * Indicates whether the provided object is equal to this byte string.
+ * In order for it to be considered equal, the provided object must be
+ * a byte sequence containing the same bytes in the same order.
+ *
+ * @param o
+ * The object for which to make the determination.
+ * @return {@code true} if the provided object is a byte sequence
+ * whose content is equal to that of this byte string, or
+ * {@code false} if not.
+ */
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ {
+ return true;
+ }
+ else if (o instanceof ByteSequence)
+ {
+ final ByteSequence other = (ByteSequence) o;
+ return other.equals(buffer, offset, length);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+
+ /**
+ * Returns a hash code for this byte string. It will be the sum of all
+ * of the bytes contained in the byte string.
+ *
+ * @return A hash code for this byte string.
+ */
+ @Override
+ public int hashCode()
+ {
+ return hashCode(buffer, offset, length);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int length()
+ {
+ return length;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteString subSequence(int start, int end)
+ throws IndexOutOfBoundsException
+ {
+ if (start < 0 || start > end || end > length)
+ {
+ throw new IndexOutOfBoundsException();
+ }
+ return new ByteString(buffer, offset + start, end - start);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public byte[] toByteArray()
+ {
+ return copyTo(new byte[length]);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteString toByteString()
+ {
+ return this;
+ }
+
+
+
+ /**
+ * Returns the integer value represented by the first four bytes of
+ * this byte string in big-endian order.
+ *
+ * @return The integer value represented by the first four bytes of
+ * this byte string in big-endian order.
+ * @throws IndexOutOfBoundsException
+ * If this byte string has less than four bytes.
+ */
+ public int toInt() throws IndexOutOfBoundsException
+ {
+ if (length < 4)
+ {
+ throw new IndexOutOfBoundsException();
+ }
+
+ int v = 0;
+ for (int i = 0; i < 4; i++)
+ {
+ v <<= 8;
+ v |= buffer[offset + i] & 0xFF;
+ }
+ return v;
+ }
+
+
+
+ /**
+ * Returns the long value represented by the first eight bytes of this
+ * byte string in big-endian order.
+ *
+ * @return The long value represented by the first eight bytes of this
+ * byte string in big-endian order.
+ * @throws IndexOutOfBoundsException
+ * If this byte string has less than eight bytes.
+ */
+ public long toLong() throws IndexOutOfBoundsException
+ {
+ if (length < 8)
+ {
+ throw new IndexOutOfBoundsException();
+ }
+
+ long v = 0;
+ for (int i = 0; i < 8; i++)
+ {
+ v <<= 8;
+ v |= buffer[offset + i] & 0xFF;
+ }
+ return v;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return toString(buffer, offset, length);
+ }
+}
diff --git a/sdk/src/org/opends/sdk/util/ByteStringBuilder.java b/sdk/src/org/opends/sdk/util/ByteStringBuilder.java
new file mode 100755
index 0000000..efb5ed0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/ByteStringBuilder.java
@@ -0,0 +1,1108 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.util;
+
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.logging.Level;
+
+
+
+/**
+ * A mutable sequence of bytes backed by a byte array.
+ */
+public final class ByteStringBuilder implements ByteSequence
+{
+
+ /**
+ * A sub-sequence of the parent byte string builder. The sub-sequence
+ * will be robust against all updates to the byte string builder
+ * except for invocations of the method {@code clear()}.
+ */
+ private final class SubSequence implements ByteSequence
+ {
+
+ // The length of the sub-sequence.
+ private final int subLength;
+
+ // The offset of the sub-sequence.
+ private final int subOffset;
+
+
+
+ /**
+ * Creates a new sub-sequence.
+ *
+ * @param offset
+ * The offset of the sub-sequence.
+ * @param length
+ * The length of the sub-sequence.
+ */
+ private SubSequence(int offset, int length)
+ {
+ this.subOffset = offset;
+ this.subLength = length;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteSequenceReader asReader()
+ {
+ return new ByteSequenceReader(this);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public byte byteAt(int index) throws IndexOutOfBoundsException
+ {
+ if (index >= subLength || index < 0)
+ {
+ throw new IndexOutOfBoundsException();
+ }
+
+ // Protect against reallocation: use builder's buffer.
+ return buffer[subOffset + index];
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int compareTo(byte[] b, int offset, int length)
+ throws IndexOutOfBoundsException
+ {
+ ByteString.checkArrayBounds(b, offset, length);
+
+ // Protect against reallocation: use builder's buffer.
+ return ByteString.compareTo(buffer, subOffset, subLength, b,
+ offset, length);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int compareTo(ByteSequence o)
+ {
+ if (this == o)
+ {
+ return 0;
+ }
+
+ // Protect against reallocation: use builder's buffer.
+ return -o.compareTo(buffer, subOffset, subLength);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public byte[] copyTo(byte[] b)
+ {
+ copyTo(b, 0);
+ return b;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public byte[] copyTo(byte[] b, int offset)
+ throws IndexOutOfBoundsException
+ {
+ if (offset < 0)
+ {
+ throw new IndexOutOfBoundsException();
+ }
+
+ // Protect against reallocation: use builder's buffer.
+ System.arraycopy(buffer, subOffset, b, offset, Math.min(
+ subLength, b.length - offset));
+ return b;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteStringBuilder copyTo(ByteStringBuilder builder)
+ {
+ // Protect against reallocation: use builder's buffer.
+ return builder.append(buffer, subOffset, subLength);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public OutputStream copyTo(OutputStream stream) throws IOException
+ {
+ // Protect against reallocation: use builder's buffer.
+ stream.write(buffer, subOffset, subLength);
+ return stream;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean equals(byte[] b, int offset, int length)
+ throws IndexOutOfBoundsException
+ {
+ ByteString.checkArrayBounds(b, offset, length);
+
+ // Protect against reallocation: use builder's buffer.
+ return ByteString.equals(buffer, subOffset, subLength, b, offset,
+ length);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ {
+ return true;
+ }
+ else if (o instanceof ByteSequence)
+ {
+ final ByteSequence other = (ByteSequence) o;
+
+ // Protect against reallocation: use builder's buffer.
+ return other.equals(buffer, subOffset, subLength);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode()
+ {
+ // Protect against reallocation: use builder's buffer.
+ return ByteString.hashCode(buffer, subOffset, subLength);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int length()
+ {
+ return subLength;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteSequence subSequence(int start, int end)
+ throws IndexOutOfBoundsException
+ {
+ if (start < 0 || start > end || end > subLength)
+ {
+ throw new IndexOutOfBoundsException();
+ }
+
+ return new SubSequence(subOffset + start, end - start);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public byte[] toByteArray()
+ {
+ return copyTo(new byte[subLength]);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteString toByteString()
+ {
+ // Protect against reallocation: use builder's buffer.
+ final byte[] b = new byte[subLength];
+ System.arraycopy(buffer, subOffset, b, 0, subLength);
+ return ByteString.wrap(b);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ // Protect against reallocation: use builder's buffer.
+ return ByteString.toString(buffer, subOffset, subLength);
+ }
+ }
+
+ // These are package private so that compression and crypto
+ // functionality may directly access the fields.
+
+ // The buffer where data is stored.
+ byte[] buffer;
+
+ // The number of bytes to expose from the buffer.
+ int length;
+
+
+
+ /**
+ * Creates a new byte string builder with an initial capacity of 32
+ * bytes.
+ */
+ public ByteStringBuilder()
+ {
+ // Initially create a 32 byte buffer.
+ this(32);
+ }
+
+
+
+ /**
+ * Creates a new byte string builder with the specified initial
+ * capacity.
+ *
+ * @param capacity
+ * The initial capacity.
+ * @throws IllegalArgumentException
+ * If the {@code capacity} is negative.
+ */
+ public ByteStringBuilder(int capacity)
+ throws IllegalArgumentException
+ {
+ if (capacity < 0)
+ {
+ throw new IllegalArgumentException();
+ }
+
+ this.buffer = new byte[capacity];
+ this.length = 0;
+ }
+
+
+
+ /**
+ * Appends the provided byte to this byte string builder.
+ *
+ * @param b
+ * The byte to be appended to this byte string builder.
+ * @return This byte string builder.
+ */
+ public ByteStringBuilder append(byte b)
+ {
+ ensureAdditionalCapacity(1);
+ buffer[length++] = b;
+ return this;
+ }
+
+
+
+ /**
+ * Appends the provided byte array to this byte string builder.
+ * <p>
+ * An invocation of the form:
+ *
+ * <pre>
+ * src.append(b)
+ * </pre>
+ *
+ * Behaves in exactly the same way as the invocation:
+ *
+ * <pre>
+ * src.append(b, 0, b.length);
+ * </pre>
+ *
+ * @param b
+ * The byte array to be appended to this byte string builder.
+ * @return This byte string builder.
+ */
+ public ByteStringBuilder append(byte[] b)
+ {
+ return append(b, 0, b.length);
+ }
+
+
+
+ /**
+ * Appends the provided byte array to this byte string builder.
+ *
+ * @param b
+ * The byte array to be appended to this byte string builder.
+ * @param offset
+ * The offset of the byte array to be used; must be
+ * non-negative and no larger than {@code b.length} .
+ * @param length
+ * The length of the byte array to be used; must be
+ * non-negative and no larger than {@code b.length - offset}.
+ * @return This byte string builder.
+ * @throws IndexOutOfBoundsException
+ * If {@code offset} is negative or if {@code length} is
+ * negative or if {@code offset + length} is greater than
+ * {@code b.length}.
+ */
+ public ByteStringBuilder append(byte[] b, int offset, int length)
+ throws IndexOutOfBoundsException
+ {
+ ByteString.checkArrayBounds(b, offset, length);
+
+ if (length != 0)
+ {
+ ensureAdditionalCapacity(length);
+ System.arraycopy(b, offset, buffer, this.length, length);
+ this.length += length;
+ }
+
+ return this;
+ }
+
+
+
+ /**
+ * Appends the provided {@code ByteBuffer} to this byte string
+ * builder.
+ *
+ * @param buffer
+ * The byte buffer to be appended to this byte string
+ * builder.
+ * @param length
+ * The number of bytes to be appended from {@code buffer}.
+ * @return This byte string builder.
+ * @throws IndexOutOfBoundsException
+ * If {@code length} is less than zero or greater than
+ * {@code buffer.remaining()}.
+ */
+ public ByteStringBuilder append(ByteBuffer buffer, int length)
+ throws IndexOutOfBoundsException
+ {
+ if (length < 0 || length > buffer.remaining())
+ {
+ throw new IndexOutOfBoundsException();
+ }
+
+ if (length != 0)
+ {
+ ensureAdditionalCapacity(length);
+ buffer.get(this.buffer, this.length, length);
+ this.length += length;
+ }
+
+ return this;
+ }
+
+
+
+ /**
+ * Appends the provided {@link ByteSequence} to this byte string
+ * builder.
+ *
+ * @param bytes
+ * The byte sequence to be appended to this byte string
+ * builder.
+ * @return This byte string builder.
+ */
+ public ByteStringBuilder append(ByteSequence bytes)
+ {
+ return bytes.copyTo(this);
+ }
+
+
+
+ /**
+ * Appends the provided {@link ByteSequenceReader} to this byte string
+ * builder.
+ *
+ * @param reader
+ * The byte sequence reader to be appended to this byte
+ * string builder.
+ * @param length
+ * The number of bytes to be appended from {@code reader}.
+ * @return This byte string builder.
+ * @throws IndexOutOfBoundsException
+ * If {@code length} is less than zero or greater than
+ * {@code reader.remaining()}.
+ */
+ public ByteStringBuilder append(ByteSequenceReader reader, int length)
+ throws IndexOutOfBoundsException
+ {
+ if (length < 0 || length > reader.remaining())
+ {
+ throw new IndexOutOfBoundsException();
+ }
+
+ if (length != 0)
+ {
+ ensureAdditionalCapacity(length);
+ reader.get(buffer, this.length, length);
+ this.length += length;
+ }
+
+ return this;
+ }
+
+
+
+ /**
+ * Appends the provided {@code InputStream} to this byte string
+ * builder.
+ *
+ * @param stream
+ * The input stream to be appended to this byte string
+ * builder.
+ * @param length
+ * The maximum number of bytes to be appended from {@code
+ * buffer}.
+ * @return The number of bytes read from the input stream, or {@code
+ * -1} if the end of the input stream has been reached.
+ * @throws IndexOutOfBoundsException
+ * If {@code length} is less than zero.
+ * @throws IOException
+ * If an I/O error occurs.
+ */
+ public int append(InputStream stream, int length)
+ throws IndexOutOfBoundsException, IOException
+ {
+ if (length < 0)
+ {
+ throw new IndexOutOfBoundsException();
+ }
+
+ ensureAdditionalCapacity(length);
+ final int bytesRead = stream.read(buffer, this.length, length);
+ if (bytesRead > 0)
+ {
+ this.length += bytesRead;
+ }
+
+ return bytesRead;
+ }
+
+
+
+ /**
+ * Appends the big-endian encoded bytes of the provided integer to
+ * this byte string builder.
+ *
+ * @param i
+ * The integer whose big-endian encoding is to be appended to
+ * this byte string builder.
+ * @return This byte string builder.
+ */
+ public ByteStringBuilder append(int i)
+ {
+ ensureAdditionalCapacity(4);
+ for (int j = length + 3; j >= length; j--)
+ {
+ buffer[j] = (byte) (i & 0xFF);
+ i >>>= 8;
+ }
+ length += 4;
+ return this;
+ }
+
+
+
+ /**
+ * Appends the big-endian encoded bytes of the provided long to this
+ * byte string builder.
+ *
+ * @param l
+ * The long whose big-endian encoding is to be appended to
+ * this byte string builder.
+ * @return This byte string builder.
+ */
+ public ByteStringBuilder append(long l)
+ {
+ ensureAdditionalCapacity(8);
+ for (int i = length + 7; i >= length; i--)
+ {
+ buffer[i] = (byte) (l & 0xFF);
+ l >>>= 8;
+ }
+ length += 8;
+ return this;
+ }
+
+
+
+ /**
+ * Appends the big-endian encoded bytes of the provided short to this
+ * byte string builder.
+ *
+ * @param i
+ * The short whose big-endian encoding is to be appended to
+ * this byte string builder.
+ * @return This byte string builder.
+ */
+ public ByteStringBuilder append(short i)
+ {
+ ensureAdditionalCapacity(2);
+ for (int j = length + 1; j >= length; j--)
+ {
+ buffer[j] = (byte) (i & 0xFF);
+ i >>>= 8;
+ }
+ length += 2;
+ return this;
+ }
+
+
+
+ /**
+ * Appends the UTF-8 encoded bytes of the provided string to this byte
+ * string builder.
+ *
+ * @param s
+ * The string whose UTF-8 encoding is to be appended to this
+ * byte string builder.
+ * @return This byte string builder.
+ */
+ public ByteStringBuilder append(String s)
+ {
+ if (s == null)
+ {
+ return this;
+ }
+
+ // Assume that each char is 1 byte
+ final int len = s.length();
+ ensureAdditionalCapacity(len);
+
+ for (int i = 0; i < len; i++)
+ {
+ final char c = s.charAt(i);
+ final byte b = (byte) (c & 0x0000007F);
+
+ if (c == b)
+ {
+ buffer[this.length + i] = b;
+ }
+ else
+ {
+ // There is a multi-byte char. Defer to JDK
+ try
+ {
+ return append(s.getBytes("UTF-8"));
+ }
+ catch (final Exception e)
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.WARNING))
+ {
+ StaticUtils.DEBUG_LOG.warning("Unable to encode String "
+ + "to UTF-8 bytes: " + e.toString());
+ }
+
+ return append(s.getBytes());
+ }
+ }
+ }
+
+ // The 1 byte char assumption was correct
+ this.length += len;
+ return this;
+ }
+
+
+
+ /**
+ * Appends the ASN.1 BER length encoding representation of the
+ * provided integer to this byte string builder.
+ *
+ * @param length
+ * The value to encode using the BER length encoding rules.
+ * @return This byte string builder.
+ */
+ public ByteStringBuilder appendBERLength(int length)
+ {
+ if ((length & 0x0000007F) == length)
+ {
+ ensureAdditionalCapacity(1);
+
+ buffer[this.length++] = (byte) (length & 0xFF);
+ }
+ else if ((length & 0x000000FF) == length)
+ {
+ ensureAdditionalCapacity(2);
+
+ buffer[this.length++] = (byte) 0x81;
+ buffer[this.length++] = (byte) (length & 0xFF);
+ }
+ else if ((length & 0x0000FFFF) == length)
+ {
+ ensureAdditionalCapacity(3);
+
+ buffer[this.length++] = (byte) 0x82;
+ buffer[this.length++] = (byte) (length >> 8 & 0xFF);
+ buffer[this.length++] = (byte) (length & 0xFF);
+ }
+ else if ((length & 0x00FFFFFF) == length)
+ {
+ ensureAdditionalCapacity(4);
+
+ buffer[this.length++] = (byte) 0x83;
+ buffer[this.length++] = (byte) (length >> 16 & 0xFF);
+ buffer[this.length++] = (byte) (length >> 8 & 0xFF);
+ buffer[this.length++] = (byte) (length & 0xFF);
+ }
+ else
+ {
+ ensureAdditionalCapacity(5);
+
+ buffer[this.length++] = (byte) 0x84;
+ buffer[this.length++] = (byte) (length >> 24 & 0xFF);
+ buffer[this.length++] = (byte) (length >> 16 & 0xFF);
+ buffer[this.length++] = (byte) (length >> 8 & 0xFF);
+ buffer[this.length++] = (byte) (length & 0xFF);
+ }
+ return this;
+ }
+
+
+
+ /**
+ * Returns a {@link ByteSequenceReader} which can be used to
+ * incrementally read and decode data from this byte string builder.
+ * <p>
+ * <b>NOTE:</b> all concurrent updates to this byte string builder are
+ * supported with the exception of {@link #clear()}. Any invocations
+ * of {@link #clear()} must be accompanied by a subsequent call to
+ * {@code ByteSequenceReader.rewind()}.
+ *
+ * @return The {@link ByteSequenceReader} which can be used to
+ * incrementally read and decode data from this byte string
+ * builder.
+ * @see #clear()
+ */
+ public ByteSequenceReader asReader()
+ {
+ return new ByteSequenceReader(this);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public byte byteAt(int index) throws IndexOutOfBoundsException
+ {
+ if (index >= length || index < 0)
+ {
+ throw new IndexOutOfBoundsException();
+ }
+ return buffer[index];
+ }
+
+
+
+ /**
+ * Sets the length of this byte string builder to zero.
+ * <p>
+ * <b>NOTE:</b> if this method is called, then {@code
+ * ByteSequenceReader.rewind()} must also be called on any associated
+ * byte sequence readers in order for them to remain valid.
+ *
+ * @return This byte string builder.
+ * @see #asReader()
+ */
+ public ByteStringBuilder clear()
+ {
+ length = 0;
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int compareTo(byte[] b, int offset, int length)
+ throws IndexOutOfBoundsException
+ {
+ ByteString.checkArrayBounds(b, offset, length);
+ return ByteString.compareTo(this.buffer, 0, this.length, b, offset,
+ length);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int compareTo(ByteSequence o)
+ {
+ if (this == o)
+ {
+ return 0;
+ }
+ return -o.compareTo(buffer, 0, length);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public byte[] copyTo(byte[] b)
+ {
+ copyTo(b, 0);
+ return b;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public byte[] copyTo(byte[] b, int offset)
+ throws IndexOutOfBoundsException
+ {
+ if (offset < 0)
+ {
+ throw new IndexOutOfBoundsException();
+ }
+ System.arraycopy(buffer, 0, b, offset, Math.min(length, b.length
+ - offset));
+ return b;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteStringBuilder copyTo(ByteStringBuilder builder)
+ {
+ builder.append(buffer, 0, length);
+ return builder;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public OutputStream copyTo(OutputStream stream) throws IOException
+ {
+ stream.write(buffer, 0, length);
+ return stream;
+ }
+
+
+
+ /**
+ * Ensures that the specified number of additional bytes will fit in
+ * this byte string builder and resizes it if necessary.
+ *
+ * @param size
+ * The number of additional bytes.
+ * @return This byte string builder.
+ */
+ public ByteStringBuilder ensureAdditionalCapacity(int size)
+ {
+ final int newCount = this.length + size;
+ if (newCount > buffer.length)
+ {
+ final byte[] newbuffer =
+ new byte[Math.max(buffer.length << 1, newCount)];
+ System.arraycopy(buffer, 0, newbuffer, 0, buffer.length);
+ buffer = newbuffer;
+ }
+ return this;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean equals(byte[] b, int offset, int length)
+ throws IndexOutOfBoundsException
+ {
+ ByteString.checkArrayBounds(b, offset, length);
+ return ByteString.equals(this.buffer, 0, this.length, b, offset,
+ length);
+ }
+
+
+
+ /**
+ * Indicates whether the provided object is equal to this byte string
+ * builder. In order for it to be considered equal, the provided
+ * object must be a byte sequence containing the same bytes in the
+ * same order.
+ *
+ * @param o
+ * The object for which to make the determination.
+ * @return {@code true} if the provided object is a byte sequence
+ * whose content is equal to that of this byte string builder,
+ * or {@code false} if not.
+ */
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ {
+ return true;
+ }
+ else if (o instanceof ByteSequence)
+ {
+ final ByteSequence other = (ByteSequence) o;
+ return other.equals(buffer, 0, length);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+
+ /**
+ * Returns the byte array that backs this byte string builder.
+ * Modifications to this byte string builder's content may cause the
+ * returned array's content to be modified, and vice versa.
+ * <p>
+ * Note that the length of the returned array is only guaranteed to be
+ * the same as the length of this byte string builder immediately
+ * after a call to {@link #trimToSize()}.
+ * <p>
+ * In addition, subsequent modifications to this byte string builder
+ * may cause the backing byte array to be reallocated thus decoupling
+ * the returned byte array from this byte string builder.
+ *
+ * @return The byte array that backs this byte string builder.
+ */
+ public byte[] getBackingArray()
+ {
+ return buffer;
+ }
+
+
+
+ /**
+ * Returns a hash code for this byte string builder. It will be the
+ * sum of all of the bytes contained in the byte string builder.
+ * <p>
+ * <b>NOTE:</b> subsequent changes to this byte string builder will
+ * invalidate the returned hash code.
+ *
+ * @return A hash code for this byte string builder.
+ */
+ @Override
+ public int hashCode()
+ {
+ return ByteString.hashCode(buffer, 0, length);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int length()
+ {
+ return length;
+ }
+
+
+
+ /**
+ * Sets the length of this byte string builder.
+ * <p>
+ * If the <code>newLength</code> argument is less than the current
+ * length, the length is changed to the specified length.
+ * <p>
+ * If the <code>newLength</code> argument is greater than or equal to
+ * the current length, then the capacity is increased and sufficient
+ * null bytes are appended so that length becomes the
+ * <code>newLength</code> argument.
+ * <p>
+ * The <code>newLength</code> argument must be greater than or equal
+ * to <code>0</code>.
+ *
+ * @param newLength
+ * The new length.
+ * @return This byte string builder.
+ * @throws IndexOutOfBoundsException
+ * If the <code>newLength</code> argument is negative.
+ */
+ public ByteStringBuilder setLength(int newLength)
+ {
+ if (newLength < 0)
+ {
+ throw new IndexOutOfBoundsException("Negative newLength: "
+ + newLength);
+ }
+
+ if (newLength > length)
+ {
+ ensureAdditionalCapacity(newLength - length);
+
+ // Pad with zeros.
+ for (int i = length; i < newLength; i++)
+ {
+ buffer[i] = 0;
+ }
+ }
+ length = newLength;
+
+ return this;
+ }
+
+
+
+ /**
+ * Returns a new byte sequence that is a subsequence of this byte
+ * sequence.
+ * <p>
+ * The subsequence starts with the byte value at the specified {@code
+ * start} index and ends with the byte value at index {@code end - 1}.
+ * The length (in bytes) of the returned sequence is {@code end -
+ * start}, so if {@code start == end} then an empty sequence is
+ * returned.
+ * <p>
+ * <b>NOTE:</b> the returned sub-sequence will be robust against all
+ * updates to the byte string builder except for invocations of the
+ * method {@link #clear()}. If a permanent immutable byte sequence is
+ * required then callers should invoke {@code toByteString()} on the
+ * returned byte sequence.
+ *
+ * @param start
+ * The start index, inclusive.
+ * @param end
+ * The end index, exclusive.
+ * @return The newly created byte subsequence.
+ * @throws IndexOutOfBoundsException
+ * If {@code start} or {@code end} are negative, if {@code
+ * end} is greater than {@code length()}, or if {@code
+ * start} is greater than {@code end}.
+ */
+ public ByteSequence subSequence(int start, int end)
+ throws IndexOutOfBoundsException
+ {
+ if (start < 0 || start > end || end > length)
+ {
+ throw new IndexOutOfBoundsException();
+ }
+
+ return new SubSequence(start, end - start);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public byte[] toByteArray()
+ {
+ return copyTo(new byte[length]);
+ }
+
+
+
+ /**
+ * Returns the {@link ByteString} representation of this byte string
+ * builder. Subsequent changes to this byte string builder will not
+ * modify the returned {@link ByteString}.
+ *
+ * @return The {@link ByteString} representation of this byte
+ * sequence.
+ */
+ public ByteString toByteString()
+ {
+ final byte[] b = new byte[length];
+ System.arraycopy(buffer, 0, b, 0, length);
+ return ByteString.wrap(b);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return ByteString.toString(buffer, 0, length);
+ }
+
+
+
+ /**
+ * Attempts to reduce storage used for this byte string builder. If
+ * the buffer is larger than necessary to hold its current sequence of
+ * bytes, then it may be resized to become more space efficient.
+ *
+ * @return This byte string builder.
+ */
+ public ByteStringBuilder trimToSize()
+ {
+ if (buffer.length > length)
+ {
+ final byte[] newBuffer = new byte[length];
+ System.arraycopy(buffer, 0, newBuffer, 0, length);
+ buffer = newBuffer;
+ }
+ return this;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/util/Function.java b/sdk/src/org/opends/sdk/util/Function.java
new file mode 100644
index 0000000..bb8157e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/Function.java
@@ -0,0 +1,58 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.util;
+
+
+
+/**
+ * Functions transform input values of type {@code M} to output values
+ * of type {@code N}.
+ *
+ * @param <M>
+ * The type of input values transformed by this function.
+ * @param <N>
+ * The type of output values return by this function.
+ * @param <P>
+ * The type of the additional parameter to this function's
+ * {@code apply} method. Use {@link java.lang.Void} for
+ * functions that do not need an additional parameter.
+ */
+public interface Function<M, N, P>
+{
+ /**
+ * Applies this function to the provided input value of type {@code M}
+ * , returning an output value of type {@code N}.
+ *
+ * @param value
+ * The value to be transformed.
+ * @param p
+ * A function specified parameter.
+ * @return The result of the transformation.
+ */
+ N apply(M value, P p);
+}
diff --git a/sdk/src/org/opends/sdk/util/Functions.java b/sdk/src/org/opends/sdk/util/Functions.java
new file mode 100644
index 0000000..02bbea9
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/Functions.java
@@ -0,0 +1,345 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.util;
+
+
+
+import org.opends.sdk.AttributeDescription;
+import org.opends.sdk.DN;
+import org.opends.sdk.schema.Schema;
+
+
+
+/**
+ * Common {@link Function} implementations.
+ */
+public final class Functions
+{
+
+ private static final class FixedFunction<M, N, P> implements
+ Function<M, N, Void>
+ {
+ private final Function<M, N, P> function;
+ private final P parameter;
+
+
+
+ private FixedFunction(Function<M, N, P> function, P p)
+ {
+ this.function = function;
+ this.parameter = p;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public N apply(M value, Void p)
+ {
+ return function.apply(value, parameter);
+ }
+
+ }
+
+ private static final Function<ByteString, AttributeDescription, Schema> BYTESTRING_TO_ATTRIBUTE_DESCRIPTION =
+ new Function<ByteString, AttributeDescription, Schema>()
+ {
+
+ public AttributeDescription apply(ByteString value, Schema p)
+ {
+ // FIXME: what should we do if parsing fails?
+ return AttributeDescription.valueOf(value.toString(), p);
+ }
+ };
+
+ private static final Function<ByteString, Boolean, Void> BYTESTRING_TO_BOOLEAN =
+ new Function<ByteString, Boolean, Void>()
+ {
+
+ public Boolean apply(ByteString value, Void p)
+ {
+ String valueString =
+ StaticUtils.toLowerCase(value.toString());
+
+ if (valueString.equals("true") || valueString.equals("yes")
+ || valueString.equals("on") || valueString.equals("1"))
+ {
+ return Boolean.TRUE;
+ }
+ else if (valueString.equals("false")
+ || valueString.equals("no") || valueString.equals("off")
+ || valueString.equals("0"))
+ {
+ return Boolean.FALSE;
+ }
+ else
+ {
+ throw new NumberFormatException("Invalid boolean value \""
+ + valueString + "\"");
+ }
+ }
+ };
+
+ private static final Function<ByteString, DN, Schema> BYTESTRING_TO_DN =
+ new Function<ByteString, DN, Schema>()
+ {
+
+ public DN apply(ByteString value, Schema p)
+ {
+ // FIXME: what should we do if parsing fails?
+
+ // FIXME: we should have a ByteString valueOf implementation.
+ return DN.valueOf(value.toString(), p);
+ }
+ };
+
+ private static final Function<ByteString, Integer, Void> BYTESTRING_TO_INTEGER =
+ new Function<ByteString, Integer, Void>()
+ {
+
+ public Integer apply(ByteString value, Void p)
+ {
+ // We do not use ByteString.toInt() as we are string based.
+ return Integer.valueOf(value.toString());
+ }
+ };
+
+ private static final Function<ByteString, Long, Void> BYTESTRING_TO_LONG =
+ new Function<ByteString, Long, Void>()
+ {
+
+ public Long apply(ByteString value, Void p)
+ {
+ // We do not use ByteString.toLong() as we are string based.
+ return Long.valueOf(value.toString());
+ }
+ };
+
+ private static final Function<ByteString, String, Void> BYTESTRING_TO_STRING =
+ new Function<ByteString, String, Void>()
+ {
+
+ public String apply(ByteString value, Void p)
+ {
+ return value.toString();
+ }
+ };
+
+ private static final Function<String, String, Void> NORMALIZE_STRING =
+ new Function<String, String, Void>()
+ {
+
+ public String apply(String value, Void p)
+ {
+ return StaticUtils.toLowerCase(value).trim();
+ }
+ };
+
+
+
+ /**
+ * Returns a function which which always invokes {@code function} with
+ * {@code p}.
+ *
+ * @param <M>
+ * The type of input values transformed by this function.
+ * @param <N>
+ * The type of output values return by this function.
+ * @param <P>
+ * The type of the additional parameter to this function's
+ * {@code apply} method. Use {@link java.lang.Void} for
+ * functions that do not need an additional parameter.
+ * @param function
+ * The function to wrap.
+ * @param p
+ * The parameter which will always be passed to {@code
+ * function}.
+ * @return A function which which always invokes {@code function} with
+ * {@code p}.
+ */
+ public static <M, N, P> Function<M, N, Void> fixedFunction(
+ Function<M, N, P> function, P p)
+ {
+ return new FixedFunction<M, N, P>(function, p);
+ }
+
+
+
+ /**
+ * Returns a function which converts a {@code String} to lower case
+ * using {@link StaticUtils#toLowerCase} and then trims it.
+ *
+ * @return A function which converts a {@code String} to lower case
+ * using {@link StaticUtils#toLowerCase} and then trims it.
+ */
+ public static Function<String, String, Void> normalizeString()
+ {
+ return NORMALIZE_STRING;
+ }
+
+
+
+ /**
+ * Returns a function which parses the string representation of a
+ * {@code ByteString} as an {@code AttributeDescription} using the
+ * default schema. Invalid values will result in a {@code
+ * LocalizedIllegalArgumentException}.
+ *
+ * @return A function which parses the string representation of a
+ * {@code ByteString} as an {@code AttributeDescription}.
+ */
+ public static Function<ByteString, AttributeDescription, Void> valueToAttributeDescription()
+ {
+ return fixedFunction(BYTESTRING_TO_ATTRIBUTE_DESCRIPTION, Schema
+ .getDefaultSchema());
+ }
+
+
+
+ /**
+ * Returns a function which parses the string representation of a
+ * {@code ByteString} as an {@code AttributeDescription} using the
+ * provided schema. Invalid values will result in a {@code
+ * LocalizedIllegalArgumentException}.
+ *
+ * @param schema
+ * The schema to use for decoding attribute descriptions.
+ * @return A function which parses the string representation of a
+ * {@code ByteString} as an {@code AttributeDescription}.
+ */
+ public static Function<ByteString, AttributeDescription, Void> valueToAttributeDescription(
+ Schema schema)
+ {
+ return fixedFunction(BYTESTRING_TO_ATTRIBUTE_DESCRIPTION, schema);
+ }
+
+
+
+ /**
+ * Returns a function which parses the string representation of a
+ * {@code ByteString} to a {@code Boolean}. The function will accept
+ * the values {@code 0}, {@code false}, {@code no}, {@code off},
+ * {@code 1}, {@code true}, {@code yes}, {@code on}. All other values
+ * will result in a {@code NumberFormatException}.
+ *
+ * @return A function which transforms a {@code ByteString} to a
+ * {@code Boolean}.
+ */
+ public static Function<ByteString, Boolean, Void> valueToBoolean()
+ {
+ return BYTESTRING_TO_BOOLEAN;
+ }
+
+
+
+ /**
+ * Returns a function which parses the string representation of a
+ * {@code ByteString} as a {@code DN} using the default schema.
+ * Invalid values will result in a {@code
+ * LocalizedIllegalArgumentException}.
+ *
+ * @return A function which parses the string representation of a
+ * {@code ByteString} as an {@code DN}.
+ */
+ public static Function<ByteString, DN, Void> valueToDN()
+ {
+ return fixedFunction(BYTESTRING_TO_DN, Schema.getDefaultSchema());
+ }
+
+
+
+ /**
+ * Returns a function which parses the string representation of a
+ * {@code ByteString} as a {@code DN} using the provided schema.
+ * Invalid values will result in a {@code
+ * LocalizedIllegalArgumentException}.
+ *
+ * @param schema
+ * The schema to use for decoding DNs.
+ * @return A function which parses the string representation of a
+ * {@code ByteString} as an {@code DN}.
+ */
+ public static Function<ByteString, DN, Void> valueToDN(Schema schema)
+ {
+ return fixedFunction(BYTESTRING_TO_DN, schema);
+ }
+
+
+
+ /**
+ * Returns a function which parses the string representation of a
+ * {@code ByteString} as an {@code Integer}. Invalid values will
+ * result in a {@code NumberFormatException}.
+ *
+ * @return A function which parses the string representation of a
+ * {@code ByteString} as an {@code Integer}.
+ */
+ public static Function<ByteString, Integer, Void> valueToInteger()
+ {
+ return BYTESTRING_TO_INTEGER;
+ }
+
+
+
+ /**
+ * Returns a function which parses the string representation of a
+ * {@code ByteString} as a {@code Long}. Invalid values will result in
+ * a {@code NumberFormatException}.
+ *
+ * @return A function which parses the string representation of a
+ * {@code ByteString} as a {@code Long}.
+ */
+ public static Function<ByteString, Long, Void> valueToLong()
+ {
+ return BYTESTRING_TO_LONG;
+ }
+
+
+
+ /**
+ * Returns a function which parses a {@code ByteString} as a UTF-8
+ * encoded {@code String}.
+ *
+ * @return A function which parses the string representation of a
+ * {@code ByteString} as a UTF-8 encoded {@code String}.
+ */
+ public static Function<ByteString, String, Void> valueToString()
+ {
+ return BYTESTRING_TO_STRING;
+ }
+
+
+
+ // Prevent instantiation
+ private Functions()
+ {
+ // Do nothing.
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/util/Iterables.java b/sdk/src/org/opends/sdk/util/Iterables.java
new file mode 100644
index 0000000..0313cdc
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/Iterables.java
@@ -0,0 +1,397 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.util;
+
+
+
+import java.util.Iterator;
+
+
+
+/**
+ * Utility methods for manipulating {@link Iterable}s.
+ */
+public final class Iterables
+{
+ private static final class EmptyIterable<M> implements Iterable<M>
+ {
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterator<M> iterator()
+ {
+ return Iterators.empty();
+ }
+
+ }
+
+
+
+ private static final class FilteredIterable<M, P> implements
+ Iterable<M>
+ {
+
+ private final Iterable<M> iterable;
+ private final P parameter;
+ private final Predicate<? super M, P> predicate;
+
+
+
+ // Constructed via factory methods.
+ private FilteredIterable(Iterable<M> iterable,
+ Predicate<? super M, P> predicate, P p)
+ {
+ this.iterable = iterable;
+ this.predicate = predicate;
+ this.parameter = p;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterator<M> iterator()
+ {
+ return Iterators
+ .filter(iterable.iterator(), predicate, parameter);
+ }
+
+ }
+
+
+
+ private static final class SingletonIterable<M> implements
+ Iterable<M>
+ {
+
+ private final M value;
+
+
+
+ // Constructed via factory methods.
+ private SingletonIterable(M value)
+ {
+ this.value = value;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterator<M> iterator()
+ {
+ return Iterators.singleton(value);
+ }
+
+ }
+
+
+
+ private static final class ArrayIterable<M> implements Iterable<M>
+ {
+
+ private final M[] a;
+
+
+
+ // Constructed via factory methods.
+ private ArrayIterable(M[] a)
+ {
+ this.a = a;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterator<M> iterator()
+ {
+ return Iterators.arrayIterator(a);
+ }
+
+ }
+
+
+
+ private static final class TransformedIterable<M, N, P> implements
+ Iterable<N>
+ {
+
+ private final Function<? super M, ? extends N, P> function;
+ private final Iterable<M> iterable;
+ private final P parameter;
+
+
+
+ // Constructed via factory methods.
+ private TransformedIterable(Iterable<M> iterable,
+ Function<? super M, ? extends N, P> function, P p)
+ {
+ this.iterable = iterable;
+ this.function = function;
+ this.parameter = p;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterator<N> iterator()
+ {
+ return Iterators.transform(iterable.iterator(), function,
+ parameter);
+ }
+
+ }
+
+
+
+ private static final class UnmodifiableIterable<M> implements
+ Iterable<M>
+ {
+
+ private final Iterable<M> iterable;
+
+
+
+ // Constructed via factory methods.
+ private UnmodifiableIterable(Iterable<M> iterable)
+ {
+ this.iterable = iterable;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterator<M> iterator()
+ {
+ return Iterators.unmodifiable(iterable.iterator());
+ }
+
+ }
+
+ private static final Iterable<Object> EMPTY_ITERABLE =
+ new EmptyIterable<Object>();
+
+
+
+ /**
+ * Returns an immutable empty iterable.
+ *
+ * @param <M>
+ * The required type of the empty iterable.
+ * @return An immutable empty iterable.
+ */
+ @SuppressWarnings("unchecked")
+ public static <M> Iterable<M> empty()
+ {
+ return (Iterable<M>) EMPTY_ITERABLE;
+ }
+
+
+
+ /**
+ * Returns a filtered view of {@code iterable} containing only those
+ * elements which match {@code predicate}. The returned iterable's
+ * iterator supports element removal via the {@code remove()} method
+ * subject to any constraints imposed by {@code iterable}.
+ *
+ * @param <M>
+ * The type of elements contained in {@code iterable}.
+ * @param <P>
+ * The type of the additional parameter to the predicate's
+ * {@code matches} method. Use {@link java.lang.Void} for
+ * predicates that do not need an additional parameter.
+ * @param iterable
+ * The iterable to be filtered.
+ * @param predicate
+ * The predicate.
+ * @param p
+ * A predicate specified parameter.
+ * @return A filtered view of {@code iterable} containing only those
+ * elements which match {@code predicate}.
+ */
+ public static <M, P> Iterable<M> filter(Iterable<M> iterable,
+ Predicate<? super M, P> predicate, P p)
+ {
+ return new FilteredIterable<M, P>(iterable, predicate, p);
+ }
+
+
+
+ /**
+ * Returns a filtered view of {@code iterable} containing only those
+ * elements which match {@code predicate}. The returned iterable's
+ * iterator supports element removal via the {@code remove()} method
+ * subject to any constraints imposed by {@code iterable}.
+ *
+ * @param <M>
+ * The type of elements contained in {@code iterable}.
+ * @param iterable
+ * The iterable to be filtered.
+ * @param predicate
+ * The predicate.
+ * @return A filtered view of {@code iterable} containing only those
+ * elements which match {@code predicate}.
+ */
+ public static <M> Iterable<M> filter(Iterable<M> iterable,
+ Predicate<? super M, Void> predicate)
+ {
+ return new FilteredIterable<M, Void>(iterable, predicate, null);
+ }
+
+
+
+ /**
+ * Returns an iterable containing the single element {@code value}.
+ * The returned iterable's iterator does not support element removal
+ * via the {@code remove()} method.
+ *
+ * @param <M>
+ * The type of the single element {@code value}.
+ * @param value
+ * The single element.
+ * @return An iterable containing the single element {@code value}.
+ */
+ public static <M> Iterable<M> singleton(M value)
+ {
+ return new SingletonIterable<M>(value);
+ }
+
+
+
+ /**
+ * Returns an iterable containing the elements of {@code a}. The
+ * returned iterable's iterator does not support element removal via
+ * the {@code remove()} method.
+ *
+ * @param <M>
+ * The type of elements contained in {@code a}.
+ * @param a
+ * The array of elements.
+ * @return An iterable containing the elements of {@code a}.
+ */
+ public static <M> Iterable<M> arrayIterable(M[] a)
+ {
+ return new ArrayIterable<M>(a);
+ }
+
+
+
+ /**
+ * Returns a view of {@code iterable} whose values have been mapped to
+ * elements of type {@code N} using {@code function}. The returned
+ * iterable's iterator supports element removal via the {@code
+ * remove()} method subject to any constraints imposed by {@code
+ * iterable}.
+ *
+ * @param <M>
+ * The type of elements contained in {@code iterable}.
+ * @param <N>
+ * The type of elements contained in the returned iterable.
+ * @param <P>
+ * The type of the additional parameter to the function's
+ * {@code apply} method. Use {@link java.lang.Void} for
+ * functions that do not need an additional parameter.
+ * @param iterable
+ * The iterable to be transformed.
+ * @param function
+ * The function.
+ * @param p
+ * A predicate specified parameter.
+ * @return A view of {@code iterable} whose values have been mapped to
+ * elements of type {@code N} using {@code function}.
+ */
+ public static <M, N, P> Iterable<N> transform(Iterable<M> iterable,
+ Function<? super M, ? extends N, P> function, P p)
+ {
+ return new TransformedIterable<M, N, P>(iterable, function, p);
+ }
+
+
+
+ /**
+ * Returns a view of {@code iterable} whose values have been mapped to
+ * elements of type {@code N} using {@code function}. The returned
+ * iterable's iterator supports element removal via the {@code
+ * remove()} method subject to any constraints imposed by {@code
+ * iterable}.
+ *
+ * @param <M>
+ * The type of elements contained in {@code iterable}.
+ * @param <N>
+ * The type of elements contained in the returned iterable.
+ * @param iterable
+ * The iterable to be transformed.
+ * @param function
+ * The function.
+ * @return A view of {@code iterable} whose values have been mapped to
+ * elements of type {@code N} using {@code function}.
+ */
+ public static <M, N> Iterable<N> transform(Iterable<M> iterable,
+ Function<? super M, ? extends N, Void> function)
+ {
+ return new TransformedIterable<M, N, Void>(iterable, function, null);
+ }
+
+
+
+ /**
+ * Returns a read-only view of {@code iterable} whose iterator does
+ * not support element removal via the {@code remove()}. Attempts to
+ * use the {@code remove()} method will result in a {@code
+ * UnsupportedOperationException}.
+ *
+ * @param <M>
+ * The type of elements contained in {@code iterable}.
+ * @param iterable
+ * The iterable to be made read-only.
+ * @return A read-only view of {@code iterable} whose iterator does
+ * not support element removal via the {@code remove()}.
+ */
+ public static <M> Iterable<M> unmodifiable(Iterable<M> iterable)
+ {
+ return new UnmodifiableIterable<M>(iterable);
+ }
+
+
+
+ // Prevent instantiation
+ private Iterables()
+ {
+ // Do nothing.
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/util/Iterators.java b/sdk/src/org/opends/sdk/util/Iterators.java
new file mode 100644
index 0000000..785c278
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/Iterators.java
@@ -0,0 +1,549 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.util;
+
+
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+
+
+/**
+ * Utility methods for manipulating {@link Iterator}s.
+ */
+public final class Iterators
+{
+ private static final class EmptyIterator<M> implements Iterator<M>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public boolean hasNext()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public M next()
+ {
+ throw new NoSuchElementException();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+ };
+
+
+
+ private static final class FilteredIterator<M, P> implements
+ Iterator<M>
+ {
+
+ private boolean hasNextMustIterate = true;
+ private final Iterator<M> iterator;
+ private M next = null;
+
+ private final P parameter;
+ private final Predicate<? super M, P> predicate;
+
+
+
+ // Constructed via factory methods.
+ private FilteredIterator(Iterator<M> iterator,
+ Predicate<? super M, P> predicate, P p)
+ {
+ this.iterator = iterator;
+ this.predicate = predicate;
+ this.parameter = p;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean hasNext()
+ {
+ if (hasNextMustIterate)
+ {
+ hasNextMustIterate = false;
+ while (iterator.hasNext())
+ {
+ next = iterator.next();
+ if (predicate.matches(next, parameter))
+ {
+ return true;
+ }
+ }
+ next = null;
+ return false;
+ }
+ else
+ {
+ return next != null;
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public M next()
+ {
+ if (!hasNext())
+ {
+ throw new NoSuchElementException();
+ }
+ hasNextMustIterate = true;
+ return next;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void remove()
+ {
+ iterator.remove();
+ }
+
+ }
+
+
+
+ private static final class SingletonIterator<M> implements
+ Iterator<M>
+ {
+ private M value;
+
+
+
+ // Constructed via factory methods.
+ private SingletonIterator(M value)
+ {
+ this.value = value;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean hasNext()
+ {
+ return value != null;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public M next()
+ {
+ if (value != null)
+ {
+ M tmp = value;
+ value = null;
+ return tmp;
+ }
+ else
+ {
+ throw new NoSuchElementException();
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ }
+
+
+
+ private static final class ArrayIterator<M> implements Iterator<M>
+ {
+ private int i = 0;
+ private final M[] a;
+
+
+
+ // Constructed via factory methods.
+ private ArrayIterator(M[] a)
+ {
+ this.a = a;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean hasNext()
+ {
+ return i < a.length;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public M next()
+ {
+ if (hasNext())
+ {
+ return a[i++];
+ }
+ else
+ {
+ throw new NoSuchElementException();
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ }
+
+
+
+ private static final class TransformedIterator<M, N, P> implements
+ Iterator<N>
+ {
+
+ private final Function<? super M, ? extends N, P> function;
+ private final Iterator<M> iterator;
+ private final P parameter;
+
+
+
+ // Constructed via factory methods.
+ private TransformedIterator(Iterator<M> iterator,
+ Function<? super M, ? extends N, P> function, P p)
+ {
+ this.iterator = iterator;
+ this.function = function;
+ this.parameter = p;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean hasNext()
+ {
+ return iterator.hasNext();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public N next()
+ {
+ return function.apply(iterator.next(), parameter);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void remove()
+ {
+ iterator.remove();
+ }
+
+ }
+
+
+
+ private static final class UnmodifiableIterator<M> implements
+ Iterator<M>
+ {
+ private final Iterator<M> iterator;
+
+
+
+ private UnmodifiableIterator(Iterator<M> iterator)
+ {
+ this.iterator = iterator;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean hasNext()
+ {
+ return iterator.hasNext();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public M next()
+ {
+ return iterator.next();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private static final Iterator<Object> EMPTY_ITERATOR =
+ new EmptyIterator<Object>();
+
+
+
+ /**
+ * Returns an immutable empty iterator.
+ *
+ * @param <M>
+ * The required type of the empty iterator.
+ * @return An immutable empty iterator.
+ */
+ @SuppressWarnings("unchecked")
+ public static <M> Iterator<M> empty()
+ {
+ return (Iterator<M>) EMPTY_ITERATOR;
+ }
+
+
+
+ /**
+ * Returns a filtered view of {@code iterator} containing only those
+ * elements which match {@code predicate}. The returned iterator
+ * supports element removal via the {@code remove()} method subject to
+ * any constraints imposed by {@code iterator}.
+ *
+ * @param <M>
+ * The type of elements contained in {@code iterator}.
+ * @param <P>
+ * The type of the additional parameter to the predicate's
+ * {@code matches} method. Use {@link java.lang.Void} for
+ * predicates that do not need an additional parameter.
+ * @param iterator
+ * The iterator to be filtered.
+ * @param predicate
+ * The predicate.
+ * @param p
+ * A predicate specified parameter.
+ * @return A filtered view of {@code iterator} containing only those
+ * elements which match {@code predicate}.
+ */
+ public static <M, P> Iterator<M> filter(Iterator<M> iterator,
+ Predicate<? super M, P> predicate, P p)
+ {
+ return new FilteredIterator<M, P>(iterator, predicate, p);
+ }
+
+
+
+ /**
+ * Returns a filtered view of {@code iterator} containing only those
+ * elements which match {@code predicate}. The returned iterator
+ * supports element removal via the {@code remove()} method subject to
+ * any constraints imposed by {@code iterator}.
+ *
+ * @param <M>
+ * The type of elements contained in {@code iterator}.
+ * @param iterator
+ * The iterator to be filtered.
+ * @param predicate
+ * The predicate.
+ * @return A filtered view of {@code iterator} containing only those
+ * elements which match {@code predicate}.
+ */
+ public static <M> Iterator<M> filter(Iterator<M> iterator,
+ Predicate<? super M, Void> predicate)
+ {
+ return new FilteredIterator<M, Void>(iterator, predicate, null);
+ }
+
+
+
+ /**
+ * Returns an iterator containing the single element {@code value}.
+ * The returned iterator does not support element removal via the
+ * {@code remove()} method.
+ *
+ * @param <M>
+ * The type of the single element {@code value}.
+ * @param value
+ * The single element to be returned by the iterator.
+ * @return An iterator containing the single element {@code value}.
+ */
+ public static <M> Iterator<M> singleton(M value)
+ {
+ return new SingletonIterator<M>(value);
+ }
+
+
+
+ /**
+ * Returns an iterator over the elements contained in {@code a}. The
+ * returned iterator does not support element removal via the {@code
+ * remove()} method.
+ *
+ * @param <M>
+ * The type of elements contained in {@code a}.
+ * @param a
+ * The array of elements to be returned by the iterator.
+ * @return An iterator over the elements contained in {@code a}.
+ */
+ public static <M> Iterator<M> arrayIterator(M[] a)
+ {
+ return new ArrayIterator<M>(a);
+ }
+
+
+
+ /**
+ * Returns a view of {@code iterator} whose values have been mapped to
+ * elements of type {@code N} using {@code function}. The returned
+ * iterator supports element removal via the {@code remove()} method
+ * subject to any constraints imposed by {@code iterator}.
+ *
+ * @param <M>
+ * The type of elements contained in {@code iterator}.
+ * @param <N>
+ * The type of elements contained in the returned iterator.
+ * @param <P>
+ * The type of the additional parameter to the function's
+ * {@code apply} method. Use {@link java.lang.Void} for
+ * functions that do not need an additional parameter.
+ * @param iterator
+ * The iterator to be transformed.
+ * @param function
+ * The function.
+ * @param p
+ * A predicate specified parameter.
+ * @return A view of {@code iterator} whose values have been mapped to
+ * elements of type {@code N} using {@code function}.
+ */
+ public static <M, N, P> Iterator<N> transform(Iterator<M> iterator,
+ Function<? super M, ? extends N, P> function, P p)
+ {
+ return new TransformedIterator<M, N, P>(iterator, function, p);
+ }
+
+
+
+ /**
+ * Returns a view of {@code iterator} whose values have been mapped to
+ * elements of type {@code N} using {@code function}. The returned
+ * iterator supports element removal via the {@code remove()} method
+ * subject to any constraints imposed by {@code iterator}.
+ *
+ * @param <M>
+ * The type of elements contained in {@code iterator}.
+ * @param <N>
+ * The type of elements contained in the returned iterator.
+ * @param iterator
+ * The iterator to be transformed.
+ * @param function
+ * The function.
+ * @return A view of {@code iterator} whose values have been mapped to
+ * elements of type {@code N} using {@code function}.
+ */
+ public static <M, N> Iterator<N> transform(Iterator<M> iterator,
+ Function<? super M, ? extends N, Void> function)
+ {
+ return new TransformedIterator<M, N, Void>(iterator, function, null);
+ }
+
+
+
+ /**
+ * Returns a read-only view of {@code iterator} which does not support
+ * element removal via the {@code remove()}. Attempts to use the
+ * {@code remove()} method will result in a {@code
+ * UnsupportedOperationException}.
+ *
+ * @param <M>
+ * The type of elements contained in {@code iterator}.
+ * @param iterator
+ * The iterator to be made read-only.
+ * @return A read-only view of {@code iterator} which does not support
+ * element removal via the {@code remove()}.
+ */
+ public static <M> Iterator<M> unmodifiable(Iterator<M> iterator)
+ {
+ return new UnmodifiableIterator<M>(iterator);
+ }
+
+
+
+ // Prevent instantiation
+ private Iterators()
+ {
+ // Do nothing.
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/util/LocalizableException.java b/sdk/src/org/opends/sdk/util/LocalizableException.java
new file mode 100644
index 0000000..edbc792
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/LocalizableException.java
@@ -0,0 +1,48 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.util;
+
+
+
+import org.opends.messages.Message;
+
+
+
+/**
+ * This interface should be implemented by any exception interfaces that
+ * expose a localizable error message.
+ */
+public interface LocalizableException
+{
+ /**
+ * Returns the message that explains the problem that occurred.
+ *
+ * @return The message that explains the problem that occurred.
+ */
+ Message getMessageObject();
+
+}
diff --git a/sdk/src/org/opends/sdk/util/LocalizedIllegalArgumentException.java b/sdk/src/org/opends/sdk/util/LocalizedIllegalArgumentException.java
new file mode 100644
index 0000000..5687293
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/LocalizedIllegalArgumentException.java
@@ -0,0 +1,101 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.util;
+
+
+
+import org.opends.messages.Message;
+
+
+
+/**
+ * Thrown to indicate that a method has been passed an illegal or
+ * inappropriate argument.
+ * <p>
+ * A {@code LocalizedIllegalArgumentException} contains a localized
+ * error message which maybe used to provide the user with detailed
+ * diagnosis information. The localized message can be retrieved using
+ * the {@link #getMessageObject} method.
+ * <p>
+ * A {@code LocalizedIllegalArgumentException} is typically used to
+ * indicate problems parsing values such as distinguished names and
+ * filters.
+ */
+@SuppressWarnings("serial")
+public class LocalizedIllegalArgumentException extends
+ IllegalArgumentException implements LocalizableException
+{
+ // The I18N message associated with this exception.
+ private final Message message;
+
+
+
+ /**
+ * Creates a new localized illegal argument exception with the
+ * provided message.
+ *
+ * @param message
+ * The message that explains the problem that occurred.
+ */
+ public LocalizedIllegalArgumentException(Message message)
+ {
+ super(String.valueOf(message));
+ this.message = message;
+ }
+
+
+
+ /**
+ * Creates a new localized illegal argument exception with the
+ * provided message and cause.
+ *
+ * @param message
+ * The message that explains the problem that occurred.
+ * @param cause
+ * The cause which may be later retrieved by the
+ * {@link #getCause} method. A {@code null} value is
+ * permitted, and indicates that the cause is nonexistent or
+ * unknown.
+ */
+ public LocalizedIllegalArgumentException(Message message,
+ Throwable cause)
+ {
+ super(String.valueOf(message), cause);
+ this.message = message;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Message getMessageObject()
+ {
+ return this.message;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/util/Platform.java b/sdk/src/org/opends/sdk/util/Platform.java
new file mode 100644
index 0000000..8440dc1
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/Platform.java
@@ -0,0 +1,650 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.util;
+
+
+import static org.opends.messages.UtilityMessages.*;
+
+import java.io.*;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.security.*;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+
+import org.opends.messages.Message;
+
+/**
+ * Provides a wrapper class that collects all of the JVM vendor
+ * and JDK version specific code in a single place.
+ *
+ */
+public final class Platform {
+
+ //Prefix that determines which security package to use.
+ private static String pkgPrefix;
+
+ //IBM security package doesn't appear to support PCKS10, this flags turns
+ //off support for that.
+ private static boolean certReqAllowed;
+
+ //The two security package prefixes (IBM and SUN).
+ private static final String IBM_SEC = "com.ibm.security";
+ private static final String SUN_SEC = "sun.security";
+
+ private static final PlatformIMPL IMPL;
+
+ static {
+ String vendor = System.getProperty("java.vendor");
+ String ver = System.getProperty("java.version");
+
+ if(vendor.startsWith("IBM"))
+ {
+ pkgPrefix = IBM_SEC;
+ certReqAllowed = false;
+ if(ver.startsWith("1.5"))
+ {
+ IMPL = new IBM5PlatformIMPL();
+ }
+ else
+ {
+ IMPL = new DefaultPlatformIMPL();
+ }
+ }
+ else
+ {
+ pkgPrefix = SUN_SEC;
+ certReqAllowed = true;
+ if(ver.startsWith("1.5"))
+ {
+ IMPL = new Sun5PlatformIMPL();
+ }
+ else
+ {
+ IMPL = new DefaultPlatformIMPL();
+ }
+ }
+ }
+
+ /**
+ * Platform base class. Performs all of the certificate management functions.
+ */
+ private abstract static class PlatformIMPL {
+
+ //Key size, key algorithm and signature algorithms used.
+ private static final int KEY_SIZE = 1024;
+ private static final String KEY_ALGORITHM = "rsa";
+ private static final String SIG_ALGORITHM = "SHA1WithRSA";
+
+ //Time values used in validity calculations.
+ private static final int SEC_IN_DAY = 24 * 60 * 60;
+ private static final int DEFAULT_VALIDITY = 90 * SEC_IN_DAY;
+
+ //These two are used to build certificate request files.
+ private static final String TMPFILE_PREFIX = "CertificateManager-";
+ private static final String TMPFILE_EXT = ".csr";
+
+ //Methods pulled from the classes.
+ private static final String ENCODE_SIGN_METHOD = "encodeAndSign";
+ private static final String GENERATE_METHOD = "generate";
+ private static final String GET_PRIVATE_KEY_METHOD = "getPrivateKey";
+ private static final String GET_SELFSIGNED_CERT_METHOD =
+ "getSelfCertificate";
+ private static final String PRINT_METHOD = "print";
+
+ //Classes needed to manage certificates.
+ private static Class<?> certKeyGenClass, X500NameClass,
+ X500SignerClass, PKCS10Class;
+
+ //Constructors for each of the above classes.
+ private static Constructor<?> certKeyGenCons, X500NameCons,
+ X500SignerCons, pkcs10Cons;
+
+ static {
+ String x509pkg = pkgPrefix + ".x509";
+ String pkcs10Pkg = pkgPrefix + ".pkcs";
+ String certAndKeyGen= x509pkg + ".CertAndKeyGen";
+ String X500Name = x509pkg + ".X500Name";
+ String X500Signer = x509pkg + ".X500Signer";
+ try {
+ certKeyGenClass = Class.forName(certAndKeyGen);
+ X500NameClass = Class.forName(X500Name);
+ X500SignerClass = Class.forName(X500Signer);
+ if(certReqAllowed) {
+ String pkcs10 = pkcs10Pkg + ".PKCS10";
+ PKCS10Class = Class.forName(pkcs10);
+ pkcs10Cons = PKCS10Class.getConstructor(PublicKey.class);
+ }
+ certKeyGenCons =
+ certKeyGenClass.getConstructor(String.class, String.class);
+ X500NameCons = X500NameClass.getConstructor(String.class);
+ X500SignerCons =
+ X500SignerClass.getConstructor(Signature.class, X500NameClass);
+ } catch (ClassNotFoundException e) {
+ Message msg = ERR_CERTMGR_CLASS_NOT_FOUND.get(e.getMessage());
+ throw new ExceptionInInitializerError(msg.toString());
+ } catch (SecurityException e) {
+ Message msg = ERR_CERTMGR_SECURITY.get(e.getMessage());
+ throw new ExceptionInInitializerError(msg.toString());
+ } catch (NoSuchMethodException e) {
+ Message msg = ERR_CERTMGR_NO_METHOD.get(e.getMessage());
+ throw new ExceptionInInitializerError(msg.toString());
+ }
+ }
+
+ protected PlatformIMPL() {}
+
+ /**
+ * Generate a certificate request. Note that this methods checks if
+ * the certificate request generation is allowed and throws an
+ * exception if it isn't supported. Some vendors JDKs aren't compatible
+ * with Sun's certificate request generation classes so they aren't
+ * supported.
+ *
+ * @param ks The keystore to use in the request creation.
+ * @param ksType The keystore type.
+ * @param ksPath The path to the keystore.
+ * @param alias The alias to use in the request generation.
+ * @param pwd The keystore password to use.
+ * @param dn A dn string to use as the certificate subject.
+ *
+ * @return A file object pointing at the created certificate request.
+ * @throws KeyStoreException If the certificate request failed.
+ */
+ public final File
+ generateCertificateRequest(KeyStore ks, String ksType, String ksPath,
+ String alias, char[] pwd, String dn) throws KeyStoreException {
+ if(!certReqAllowed) {
+ String vendor = System.getProperty("java.vendor");
+ Message msg =
+ ERR_CERTMGR_CERT_SIGN_REQ_NOT_SUPPORTED.get(vendor);
+ throw new KeyStoreException(msg.toString());
+ }
+ KeyStore keyStore = generateSelfSignedCertificate(ks, ksType, ksPath,
+ alias, pwd, dn, DEFAULT_VALIDITY);
+ File csrFile;
+ try {
+ csrFile = File.createTempFile(TMPFILE_PREFIX, TMPFILE_EXT);
+ csrFile.deleteOnExit();
+ PrintStream printStream =
+ new PrintStream(new FileOutputStream(csrFile.getAbsolutePath()));
+ if(keyStore == null) {
+ Message msg = ERR_CERTMGR_KEYSTORE_NONEXISTANT.get();
+ throw new KeyStoreException(msg.toString());
+ }
+ PrivateKey privateKey = getPrivateKey(keyStore, alias, pwd);
+ if(privateKey == null) {
+ Message msg = ERR_CERTMGR_PRIVATE_KEY.get(alias);
+ throw new KeyStoreException(msg.toString());
+ }
+ Certificate cert = keyStore.getCertificate(alias);
+ if(cert == null) {
+ Message msg = ERR_CERTMGR_ALIAS_NO_CERTIFICATE.get(alias);
+ throw new KeyStoreException(msg.toString());
+ }
+ Signature signature = Signature.getInstance(SIG_ALGORITHM);
+ signature.initSign(privateKey);
+ Object request = pkcs10Cons.newInstance(cert.getPublicKey());
+ Object subject = X500NameCons.newInstance(dn);
+ Object signer =
+ X500SignerCons.newInstance(signature, subject);
+ Method encodeAndSign =
+ PKCS10Class.getMethod(ENCODE_SIGN_METHOD, X500SignerClass);
+ Method print =
+ PKCS10Class.getMethod(PRINT_METHOD, PrintStream.class);
+ encodeAndSign.invoke(request, signer);
+ print.invoke(request, printStream);
+ printStream.close();
+ } catch (Exception e) {
+ Message msg = ERR_CERTMGR_CERT_REQUEST.get(alias,e.getMessage());
+ throw new KeyStoreException(msg.toString());
+ }
+ return csrFile;
+ }
+
+ /**
+ * Delete the specified alias from the specified keystore.
+ *
+ * @param ks The keystore to delete the alias from.
+ * @param ksPath The path to the keystore.
+ * @param alias The alias to use in the request generation.
+ * @param pwd The keystore password to use.
+ *
+ * @throws KeyStoreException If an error occurred deleting the alias.
+ */
+ public final void deleteAlias(KeyStore ks, String ksPath,
+ String alias, char[] pwd) throws KeyStoreException {
+ try {
+ if(ks == null) {
+ Message msg = ERR_CERTMGR_KEYSTORE_NONEXISTANT.get();
+ throw new KeyStoreException(msg.toString());
+ }
+ ks.deleteEntry(alias);
+ FileOutputStream fs = new FileOutputStream(ksPath);
+ ks.store(fs, pwd);
+ fs.close();
+ } catch (Exception e) {
+ Message msg =
+ ERR_CERTMGR_DELETE_ALIAS.get(alias,e.getMessage());
+ throw new KeyStoreException(msg.toString());
+ }
+ }
+
+ /**
+ * Add the certificate in the specified path to the specified keystore,
+ * creating the keystore using the specified type and path if it the
+ * keystore doesn't exist.
+ *
+ * @param ks The keystore to add the certificate to, may be null if it
+ * doesn't exist.
+ * @param ksType The type to use if the keystore is created.
+ * @param ksPath The path to the keystore if it is created.
+ * @param alias The alias to store the certificate under.
+ * @param pwd The password to use in saving the certificate.
+ * @param certPath The path to the file containing the certificate.
+ * @throws KeyStoreException If an error occurred adding the
+ * certificate to the keystore.
+ */
+ public final void addCertificate(KeyStore ks, String ksType,
+ String ksPath, String alias, char[] pwd, String certPath)
+ throws KeyStoreException {
+ try {
+ CertificateFactory cf = CertificateFactory.getInstance("X509");
+ InputStream inStream = new FileInputStream(certPath);
+ if(ks == null) {
+ ks = KeyStore.getInstance(ksType);
+ ks.load(null, pwd);
+ }
+ //Do not support certificate replies.
+ if (ks.entryInstanceOf(alias ,KeyStore.PrivateKeyEntry.class)) {
+ Message msg = ERR_CERTMGR_CERT_REPLIES_INVALID.get(alias);
+ throw new KeyStoreException(msg.toString());
+ } else if(!ks.containsAlias(alias) ||
+ ks.entryInstanceOf(alias,
+ KeyStore.TrustedCertificateEntry.class))
+ trustedCert(alias, cf, ks, inStream);
+ else {
+ Message msg = ERR_CERTMGR_ALIAS_INVALID.get(alias);
+ throw new KeyStoreException(msg.toString());
+ }
+ FileOutputStream fileOutStream = new FileOutputStream(ksPath);
+ ks.store(fileOutStream, pwd);
+ fileOutStream.close();
+ inStream.close();
+ } catch (Exception e) {
+ Message msg =
+ ERR_CERTMGR_ADD_CERT.get(alias, e.getMessage());
+ throw new KeyStoreException(msg.toString());
+ }
+ }
+
+ /**
+ * Generate a self-signed certificate using the specified alias, dn
+ * string and validity period. If the keystore does not exist, create it
+ * using the specified type and path.
+ *
+ * @param ks The keystore to save the certificate in. May be null if it
+ * does not exist.
+ * @param ksType The keystore type to use if the keystore is created.
+ * @param ksPath The path to the keystore if the keystore is created.
+ * @param alias The alias to store the certificate under.
+ * @param pwd The password to us in saving the certificate.
+ * @param dn The dn string used as the certificate subject.
+ * @param validity The validity of the certificate in days.
+ * @return The keystore that the self-signed certificate was stored in.
+ *
+ * @throws KeyStoreException If the self-signed certificate cannot be
+ * generated.
+ */
+ public final
+ KeyStore generateSelfSignedCertificate(KeyStore ks, String ksType,
+ String ksPath, String alias, char[] pwd, String dn, int validity)
+ throws KeyStoreException {
+ try {
+ if(ks == null) {
+ ks = KeyStore.getInstance(ksType);
+ ks.load(null, pwd);
+ } else if(ks.containsAlias(alias)) {
+ Message msg = ERR_CERTMGR_ALIAS_ALREADY_EXISTS.get(alias);
+ throw new KeyStoreException(msg.toString());
+ }
+ Object keypair =
+ certKeyGenCons.newInstance(KEY_ALGORITHM, SIG_ALGORITHM);
+ Object subject = X500NameCons.newInstance(dn);
+ Method certAndKeyGenGenerate =
+ certKeyGenClass.getMethod(GENERATE_METHOD, int.class);
+ certAndKeyGenGenerate.invoke(keypair, KEY_SIZE);
+ Method certAndKeyGetPrivateKey =
+ certKeyGenClass.getMethod(GET_PRIVATE_KEY_METHOD);
+ PrivateKey privatevKey =
+ (PrivateKey) certAndKeyGetPrivateKey.invoke(keypair);
+ Certificate[] certificateChain = new Certificate[1];
+ Method getSelfCertificate =
+ certKeyGenClass.getMethod(GET_SELFSIGNED_CERT_METHOD,
+ X500NameClass,long.class);
+ int days = validity * SEC_IN_DAY;
+ certificateChain[0] =
+ (Certificate) getSelfCertificate.invoke(keypair, subject, days);
+ ks.setKeyEntry(alias, privatevKey, pwd, certificateChain);
+ FileOutputStream fileOutStream = new FileOutputStream(ksPath);
+ ks.store(fileOutStream, pwd);
+ fileOutStream.close();
+ } catch (Exception e) {
+ Message msg =
+ ERR_CERTMGR_GEN_SELF_SIGNED_CERT.get(alias, e.getMessage());
+ throw new KeyStoreException(msg.toString());
+ }
+ return ks;
+ }
+
+ /**
+ * Generate a x509 certificate from the input stream. Verification is
+ * done only if it is self-signed.
+ *
+ * @param alias The alias to save the certificate under.
+ * @param cf The x509 certificate factory.
+ * @param ks The keystore to add the certificate in.
+ * @param in The input stream to read the certificate from.
+ * @throws KeyStoreException If the alias exists already in the
+ * keystore, if the self-signed certificate didn't verify, or
+ * the certificate could not be stored.
+ */
+ private void trustedCert(String alias, CertificateFactory cf,
+ KeyStore ks, InputStream in) throws KeyStoreException {
+ try {
+ if (ks.containsAlias(alias) == true) {
+ Message msg = ERR_CERTMGR_ALIAS_ALREADY_EXISTS.get(alias);
+ throw new KeyStoreException(msg.toString());
+ }
+ X509Certificate cert = (X509Certificate) cf.generateCertificate(in);
+ if (isSelfSigned(cert))
+ cert.verify(cert.getPublicKey());
+ ks.setCertificateEntry(alias, cert);
+ } catch (Exception e) {
+ Message msg =
+ ERR_CERTMGR_TRUSTED_CERT.get(alias,e.getMessage());
+ throw new KeyStoreException(msg.toString());
+ }
+ }
+
+ /**
+ * Check that the issuer and subject DNs match.
+ *
+ * @param cert The certificate to examine.
+ * @return {@code true} if the certificate is self-signed.
+ */
+ private boolean isSelfSigned(X509Certificate cert) {
+ return cert.getSubjectDN().equals(cert.getIssuerDN());
+ }
+
+ /**
+ * Returns the private key associated with specified alias and keystore.
+ * The keystore was already checked for existance.
+ *
+ * @param ks The keystore to get the private key from, it must exist.
+ * @param alias The alias to get the private key of.
+ * @param pwd The password used to get the key from the keystore.
+ * @return The private key of related to the alias.
+ *
+ * @throws KeyStoreException If the alias is not in the keystore, the
+ * entry related to the alias is not of
+ */
+ private PrivateKey getPrivateKey(KeyStore ks, String alias, char[] pwd)
+ throws KeyStoreException {
+ PrivateKey key = null;
+ try {
+ if(!ks.containsAlias(alias)) {
+ Message msg = ERR_CERTMGR_ALIAS_DOES_NOT_EXIST.get(alias);
+ throw new KeyStoreException(msg.toString());
+ }
+ if(!ks.entryInstanceOf(alias, KeyStore.PrivateKeyEntry.class) &&
+ !ks.entryInstanceOf(alias, KeyStore.SecretKeyEntry.class)) {
+ Message msg =
+ ERR_CERTMGR_ALIAS_INVALID_ENTRY_TYPE.get(alias);
+ throw new KeyStoreException(msg.toString());
+ }
+ key = (PrivateKey)ks.getKey(alias, pwd);
+ } catch (Exception e) {
+ Message msg =
+ ERR_CERTMGR_GET_KEY.get(alias,e.getMessage());
+ throw new KeyStoreException(msg.toString());
+ }
+ return key;
+ }
+
+ /**
+ * Normalize the data in the specified buffer.
+ *
+ * @param buffer The buffer to normalize.
+ */
+ public abstract void normalize(StringBuilder buffer);
+ }
+
+ //Prevent instantiation.
+ private Platform() {}
+
+ /**
+ * Add the certificate in the specified path to the provided keystore;
+ * creating the keystore with the provided type and path if it doesn't
+ * exist.
+ *
+ * @param ks The keystore to add the certificate to, may be null if it
+ * doesn't exist.
+ * @param ksType The type to use if the keystore is created.
+ * @param ksPath The path to the keystore if it is created.
+ * @param alias The alias to store the certificate under.
+ * @param pwd The password to use in saving the certificate.
+ * @param certPath The path to the file containing the certificate.
+ *
+ * @throws KeyStoreException If an error occurred adding the
+ * certificate to the keystore.
+ */
+ public static void addCertificate(KeyStore ks, String ksType, String ksPath,
+ String alias, char[] pwd, String certPath) throws KeyStoreException {
+ IMPL.addCertificate(ks,ksType, ksPath, alias, pwd, certPath);
+ }
+
+
+ /**
+ * Delete the specified alias from the provided keystore.
+ *
+ * @param ks The keystore to delete the alias from.
+ * @param ksPath The path to the keystore.
+ * @param alias The alias to use in the request generation.
+ * @param pwd The keystore password to use.
+ *
+ * @throws KeyStoreException If an error occurred deleting the alias.
+ */
+ public static void deleteAlias(KeyStore ks, String ksPath, String alias,
+ char[] pwd) throws KeyStoreException {
+ IMPL.deleteAlias(ks, ksPath, alias, pwd);
+ }
+
+
+ /**
+ * Generate a certificate request using the specified parameters.
+ *
+ * @param ks The keystore to use in the request creation.
+ * @param ksType The keystore type.
+ * @param ksPath The path to the keystore.
+ * @param alias The alias to use in the request generation.
+ * @param pwd The keystore password to use.
+ * @param dn A dn string to use as the certificate subject.
+ * @return A file object pointing at the created certificate request.
+ *
+ * @throws KeyStoreException If the certificate request failed.
+ */
+ public static File generateCertificateRequest(KeyStore ks, String ksType,
+ String ksPath, String alias, char[] pwd, String dn)
+ throws KeyStoreException {
+ return IMPL.generateCertificateRequest(ks, ksType, ksPath, alias,
+ pwd, dn);
+ }
+
+
+ /**
+ * Generate a self-signed certificate using the specified alias, dn
+ * string and validity period. If the keystore does not exist, it will be
+ * created using the specified keystore type and path.
+ *
+ * @param ks The keystore to save the certificate in. May be null if it
+ * does not exist.
+ * @param ksType The keystore type to use if the keystore is created.
+ * @param ksPath The path to the keystore if the keystore is created.
+ * @param alias The alias to store the certificate under.
+ * @param pwd The password to us in saving the certificate.
+ * @param dn The dn string used as the certificate subject.
+ * @param validity The validity of the certificate in days.
+ *
+ * @throws KeyStoreException If the self-signed certificate cannot be
+ * generated.
+ */
+ public static void generateSelfSignedCertificate(KeyStore ks, String ksType,
+ String ksPath, String alias, char[] pwd, String dn, int validity)
+ throws KeyStoreException {
+ IMPL.generateSelfSignedCertificate(ks, ksType, ksPath, alias, pwd, dn,
+ validity);
+ }
+
+
+
+ /**
+ * Sun 5 JDK platform class.
+ */
+ private static class Sun5PlatformIMPL extends PlatformIMPL {
+ //normalize method.
+ private static final Method NORMALIZE;
+ //Normalized form method.
+ private static final Object FORM_NFKC;
+
+ static {
+ Method normalize = null;
+ Object formNFKC = null;
+ try {
+ Class<?> normalizer = Class.forName("sun.text.Normalizer");
+ formNFKC = normalizer.getField("DECOMP_COMPAT").get(null);
+ Class<?> normalizerForm = Class.forName("sun.text.Normalizer$Mode");
+ normalize = normalizer.getMethod("normalize", String.class,
+ normalizerForm, Integer.TYPE);
+ }
+ catch (Exception ex) {
+ // Do not use Normalizer. The values are already set to null.
+ }
+ NORMALIZE = normalize;
+ FORM_NFKC = formNFKC;
+ }
+
+
+ @Override
+ public void normalize(StringBuilder buffer) {
+ try {
+ String normal =
+ (String) NORMALIZE.invoke(null, buffer.toString(), FORM_NFKC,0);
+ buffer.replace(0,buffer.length(),normal);
+ }
+ catch(Exception ex) {
+ //Don't do anything. buffer should be used.
+ }
+ }
+ }
+
+ /**
+ * Default platform class.
+ */
+ private static class DefaultPlatformIMPL extends PlatformIMPL {
+ //normalize method.
+ private static final Method NORMALIZE;
+ //Normalized form method.
+ private static final Object FORM_NFKC;
+
+ static {
+
+ Method normalize = null;
+ Object formNFKC = null;
+ try {
+ Class<?> normalizer = Class.forName("java.text.Normalizer");
+ Class<?> normalizerForm = Class.forName("java.text.Normalizer$Form");
+ normalize = normalizer.getMethod("normalize", CharSequence.class,
+ normalizerForm);
+ formNFKC = normalizerForm.getField("NFKD").get(null);
+ }
+ catch (Exception ex) {
+ // Do not use Normalizer. The values are already set to null.
+ }
+ NORMALIZE = normalize;
+ FORM_NFKC = formNFKC;
+ }
+
+
+ @Override
+ public void normalize(StringBuilder buffer) {
+ try {
+ String normal = (String) NORMALIZE.invoke(null, buffer, FORM_NFKC);
+ buffer.replace(0,buffer.length(),normal);
+ }
+ catch(Exception ex) {
+ //Don't do anything. buffer should be used.
+ }
+ }
+ }
+
+ /**
+ * IBM JDK 5 platform class.
+ */
+ private static class IBM5PlatformIMPL extends PlatformIMPL {
+
+ @Override
+ public void normalize(StringBuilder buffer) {
+ //No implementation.
+ }
+ }
+
+ /**
+ * Normalize the specified buffer.
+ *
+ * @param buffer The buffer to normalize.
+ */
+ public static void normalize(StringBuilder buffer) {
+ IMPL.normalize(buffer);
+ }
+
+ /**
+ * Test if a platform java vendor property starts with the specified
+ * vendor string.
+ *
+ * @param vendor The vendor to check for.
+ * @return {@code true} if the java vendor starts with the specified vendor
+ * string.
+ */
+ public static boolean isVendor(String vendor) {
+ String javaVendor = System.getProperty("java.vendor");
+ return javaVendor.startsWith(vendor);
+ }
+}
+
diff --git a/sdk/src/org/opends/sdk/util/Predicate.java b/sdk/src/org/opends/sdk/util/Predicate.java
new file mode 100644
index 0000000..a7058f6
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/Predicate.java
@@ -0,0 +1,57 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.util;
+
+
+
+/**
+ * Predicates transform input values of type {@code M} to a boolean
+ * output value and are typically used for performing filtering.
+ *
+ * @param <M>
+ * The type of input values matched by this predicate.
+ * @param <P>
+ * The type of the additional parameter to this predicate's
+ * {@code matches} method. Use {@link java.lang.Void} for
+ * predicates that do not need an additional parameter.
+ */
+public interface Predicate<M, P>
+{
+ /**
+ * Indicates whether or not this predicate matches the provided input
+ * value of type {@code M}.
+ *
+ * @param value
+ * The input value for which to make the determination.
+ * @param p
+ * A predicate specified parameter.
+ * @return {@code true} if this predicate matches {@code value},
+ * otherwise {@code false}.
+ */
+ boolean matches(M value, P p);
+}
diff --git a/sdk/src/org/opends/sdk/util/SSLUtils.java b/sdk/src/org/opends/sdk/util/SSLUtils.java
new file mode 100644
index 0000000..a6cb1ba
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/SSLUtils.java
@@ -0,0 +1,47 @@
+package org.opends.sdk.util;
+
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: boli
+ * Date: Oct 22, 2009
+ * Time: 3:10:13 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class SSLUtils
+{
+ private static String PROVIDER =
+ System.getProperty("org.opends.security.provider");
+ private static String KEY_STORE =
+ System.getProperty("javax.net.ssl.keyStore");
+
+ {
+
+ }
+ public static SSLContext getSSLContext(TrustManager trustManager,
+ KeyManager keyManager)
+ throws KeyManagementException, NoSuchAlgorithmException {
+ TrustManager[] tm = null;
+ if (trustManager != null)
+ {
+ tm = new TrustManager[] {trustManager};
+ }
+
+ KeyManager[] km = null;
+ if (keyManager != null)
+ {
+ km = new KeyManager[] {keyManager};
+ }
+
+ SSLContext sslContext = SSLContext.getInstance("TLSv1");
+ sslContext.init(km, tm, null);
+
+ return sslContext;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/util/SizeLimitInputStream.java b/sdk/src/org/opends/sdk/util/SizeLimitInputStream.java
new file mode 100644
index 0000000..16eca77
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/SizeLimitInputStream.java
@@ -0,0 +1,182 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * An implementation of input stream that enforces an read size limit.
+ */
+public class SizeLimitInputStream extends InputStream
+{
+ private int bytesRead;
+ private int markBytesRead;
+ private int readLimit;
+ private InputStream parentStream;
+
+ /**
+ * Creates a new a new size limit input stream.
+ *
+ * @param parentStream
+ * The parent stream.
+ * @param readLimit
+ * The size limit.
+ */
+ public SizeLimitInputStream(InputStream parentStream, int readLimit)
+ {
+ this.parentStream = parentStream;
+ this.readLimit = readLimit;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int available() throws IOException
+ {
+ int streamAvail = parentStream.available();
+ int limitedAvail = readLimit - bytesRead;
+ return limitedAvail < streamAvail ? limitedAvail : streamAvail;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public synchronized void mark(int readlimit)
+ {
+ parentStream.mark(readlimit);
+ markBytesRead = bytesRead;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int read() throws IOException
+ {
+ if(bytesRead >= readLimit)
+ {
+ return -1;
+ }
+
+ int b = parentStream.read();
+ if (b != -1)
+ {
+ ++bytesRead;
+ }
+ return b;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int read(byte b[], int off, int len) throws IOException
+ {
+ if(off < 0 || len < 0 || off+len > b.length)
+ {
+ throw new IndexOutOfBoundsException();
+ }
+
+ if(len == 0)
+ {
+ return 0;
+ }
+
+ if(bytesRead >= readLimit)
+ {
+ return -1;
+ }
+
+ if(bytesRead + len > readLimit)
+ {
+ len = readLimit - bytesRead;
+ }
+
+ int readLen = parentStream.read(b, off, len);
+ if(readLen > 0)
+ {
+ bytesRead += readLen;
+ }
+ return readLen;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public synchronized void reset() throws IOException
+ {
+ parentStream.reset();
+ bytesRead = markBytesRead;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public long skip(long n) throws IOException
+ {
+ if(bytesRead + n > readLimit)
+ {
+ n = readLimit - bytesRead;
+ }
+
+ bytesRead += n;
+ return parentStream.skip(n);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean markSupported() {
+ return parentStream.markSupported();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void close() throws IOException {
+ parentStream.close();
+ }
+
+ /**
+ * Retrieves the number of bytes read from this stream.
+ *
+ * @return The number of bytes read from this stream.
+ */
+ public int getBytesRead()
+ {
+ return bytesRead;
+ }
+
+ /**
+ * Retrieves the size limit of this stream.
+ *
+ * @return The size limit of this stream.
+ */
+ public int getSizeLimit()
+ {
+ return readLimit;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/util/StaticUtils.java b/sdk/src/org/opends/sdk/util/StaticUtils.java
new file mode 100644
index 0000000..b7fb4ad
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/StaticUtils.java
@@ -0,0 +1,1998 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.util;
+
+
+
+import static org.opends.messages.UtilityMessages.ERR_HEX_DECODE_INVALID_CHARACTER;
+import static org.opends.messages.UtilityMessages.ERR_HEX_DECODE_INVALID_LENGTH;
+import static org.opends.messages.UtilityMessages.ERR_INVALID_ESCAPE_CHAR;
+
+import java.lang.reflect.InvocationTargetException;
+import java.text.ParseException;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.zip.DataFormatException;
+import java.util.zip.Deflater;
+import java.util.zip.Inflater;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.messages.MessageDescriptor;
+import org.opends.sdk.DecodeException;
+
+
+
+/**
+ * Common utility methods.
+ */
+public final class StaticUtils
+{
+ public static final Logger DEBUG_LOG = Logger
+ .getLogger("org.opends.sdk");
+
+ /**
+ * The end-of-line character for this platform.
+ */
+ public static final String EOL = System.getProperty("line.separator");
+
+ // The name of the time zone for universal coordinated time (UTC).
+ private static final String TIME_ZONE_UTC = "UTC";
+
+ // UTC TimeZone is assumed to never change over JVM lifetime
+ private static final TimeZone TIME_ZONE_UTC_OBJ = TimeZone
+ .getTimeZone(TIME_ZONE_UTC);
+
+
+
+ /**
+ * Retrieves a string representation of the provided byte in
+ * hexadecimal.
+ *
+ * @param b
+ * The byte for which to retrieve the hexadecimal string
+ * representation.
+ * @return The string representation of the provided byte in
+ * hexadecimal.
+ */
+ public static String byteToHex(byte b)
+ {
+ switch (b & 0xFF)
+ {
+ case 0x00:
+ return "00";
+ case 0x01:
+ return "01";
+ case 0x02:
+ return "02";
+ case 0x03:
+ return "03";
+ case 0x04:
+ return "04";
+ case 0x05:
+ return "05";
+ case 0x06:
+ return "06";
+ case 0x07:
+ return "07";
+ case 0x08:
+ return "08";
+ case 0x09:
+ return "09";
+ case 0x0A:
+ return "0A";
+ case 0x0B:
+ return "0B";
+ case 0x0C:
+ return "0C";
+ case 0x0D:
+ return "0D";
+ case 0x0E:
+ return "0E";
+ case 0x0F:
+ return "0F";
+ case 0x10:
+ return "10";
+ case 0x11:
+ return "11";
+ case 0x12:
+ return "12";
+ case 0x13:
+ return "13";
+ case 0x14:
+ return "14";
+ case 0x15:
+ return "15";
+ case 0x16:
+ return "16";
+ case 0x17:
+ return "17";
+ case 0x18:
+ return "18";
+ case 0x19:
+ return "19";
+ case 0x1A:
+ return "1A";
+ case 0x1B:
+ return "1B";
+ case 0x1C:
+ return "1C";
+ case 0x1D:
+ return "1D";
+ case 0x1E:
+ return "1E";
+ case 0x1F:
+ return "1F";
+ case 0x20:
+ return "20";
+ case 0x21:
+ return "21";
+ case 0x22:
+ return "22";
+ case 0x23:
+ return "23";
+ case 0x24:
+ return "24";
+ case 0x25:
+ return "25";
+ case 0x26:
+ return "26";
+ case 0x27:
+ return "27";
+ case 0x28:
+ return "28";
+ case 0x29:
+ return "29";
+ case 0x2A:
+ return "2A";
+ case 0x2B:
+ return "2B";
+ case 0x2C:
+ return "2C";
+ case 0x2D:
+ return "2D";
+ case 0x2E:
+ return "2E";
+ case 0x2F:
+ return "2F";
+ case 0x30:
+ return "30";
+ case 0x31:
+ return "31";
+ case 0x32:
+ return "32";
+ case 0x33:
+ return "33";
+ case 0x34:
+ return "34";
+ case 0x35:
+ return "35";
+ case 0x36:
+ return "36";
+ case 0x37:
+ return "37";
+ case 0x38:
+ return "38";
+ case 0x39:
+ return "39";
+ case 0x3A:
+ return "3A";
+ case 0x3B:
+ return "3B";
+ case 0x3C:
+ return "3C";
+ case 0x3D:
+ return "3D";
+ case 0x3E:
+ return "3E";
+ case 0x3F:
+ return "3F";
+ case 0x40:
+ return "40";
+ case 0x41:
+ return "41";
+ case 0x42:
+ return "42";
+ case 0x43:
+ return "43";
+ case 0x44:
+ return "44";
+ case 0x45:
+ return "45";
+ case 0x46:
+ return "46";
+ case 0x47:
+ return "47";
+ case 0x48:
+ return "48";
+ case 0x49:
+ return "49";
+ case 0x4A:
+ return "4A";
+ case 0x4B:
+ return "4B";
+ case 0x4C:
+ return "4C";
+ case 0x4D:
+ return "4D";
+ case 0x4E:
+ return "4E";
+ case 0x4F:
+ return "4F";
+ case 0x50:
+ return "50";
+ case 0x51:
+ return "51";
+ case 0x52:
+ return "52";
+ case 0x53:
+ return "53";
+ case 0x54:
+ return "54";
+ case 0x55:
+ return "55";
+ case 0x56:
+ return "56";
+ case 0x57:
+ return "57";
+ case 0x58:
+ return "58";
+ case 0x59:
+ return "59";
+ case 0x5A:
+ return "5A";
+ case 0x5B:
+ return "5B";
+ case 0x5C:
+ return "5C";
+ case 0x5D:
+ return "5D";
+ case 0x5E:
+ return "5E";
+ case 0x5F:
+ return "5F";
+ case 0x60:
+ return "60";
+ case 0x61:
+ return "61";
+ case 0x62:
+ return "62";
+ case 0x63:
+ return "63";
+ case 0x64:
+ return "64";
+ case 0x65:
+ return "65";
+ case 0x66:
+ return "66";
+ case 0x67:
+ return "67";
+ case 0x68:
+ return "68";
+ case 0x69:
+ return "69";
+ case 0x6A:
+ return "6A";
+ case 0x6B:
+ return "6B";
+ case 0x6C:
+ return "6C";
+ case 0x6D:
+ return "6D";
+ case 0x6E:
+ return "6E";
+ case 0x6F:
+ return "6F";
+ case 0x70:
+ return "70";
+ case 0x71:
+ return "71";
+ case 0x72:
+ return "72";
+ case 0x73:
+ return "73";
+ case 0x74:
+ return "74";
+ case 0x75:
+ return "75";
+ case 0x76:
+ return "76";
+ case 0x77:
+ return "77";
+ case 0x78:
+ return "78";
+ case 0x79:
+ return "79";
+ case 0x7A:
+ return "7A";
+ case 0x7B:
+ return "7B";
+ case 0x7C:
+ return "7C";
+ case 0x7D:
+ return "7D";
+ case 0x7E:
+ return "7E";
+ case 0x7F:
+ return "7F";
+ case 0x80:
+ return "80";
+ case 0x81:
+ return "81";
+ case 0x82:
+ return "82";
+ case 0x83:
+ return "83";
+ case 0x84:
+ return "84";
+ case 0x85:
+ return "85";
+ case 0x86:
+ return "86";
+ case 0x87:
+ return "87";
+ case 0x88:
+ return "88";
+ case 0x89:
+ return "89";
+ case 0x8A:
+ return "8A";
+ case 0x8B:
+ return "8B";
+ case 0x8C:
+ return "8C";
+ case 0x8D:
+ return "8D";
+ case 0x8E:
+ return "8E";
+ case 0x8F:
+ return "8F";
+ case 0x90:
+ return "90";
+ case 0x91:
+ return "91";
+ case 0x92:
+ return "92";
+ case 0x93:
+ return "93";
+ case 0x94:
+ return "94";
+ case 0x95:
+ return "95";
+ case 0x96:
+ return "96";
+ case 0x97:
+ return "97";
+ case 0x98:
+ return "98";
+ case 0x99:
+ return "99";
+ case 0x9A:
+ return "9A";
+ case 0x9B:
+ return "9B";
+ case 0x9C:
+ return "9C";
+ case 0x9D:
+ return "9D";
+ case 0x9E:
+ return "9E";
+ case 0x9F:
+ return "9F";
+ case 0xA0:
+ return "A0";
+ case 0xA1:
+ return "A1";
+ case 0xA2:
+ return "A2";
+ case 0xA3:
+ return "A3";
+ case 0xA4:
+ return "A4";
+ case 0xA5:
+ return "A5";
+ case 0xA6:
+ return "A6";
+ case 0xA7:
+ return "A7";
+ case 0xA8:
+ return "A8";
+ case 0xA9:
+ return "A9";
+ case 0xAA:
+ return "AA";
+ case 0xAB:
+ return "AB";
+ case 0xAC:
+ return "AC";
+ case 0xAD:
+ return "AD";
+ case 0xAE:
+ return "AE";
+ case 0xAF:
+ return "AF";
+ case 0xB0:
+ return "B0";
+ case 0xB1:
+ return "B1";
+ case 0xB2:
+ return "B2";
+ case 0xB3:
+ return "B3";
+ case 0xB4:
+ return "B4";
+ case 0xB5:
+ return "B5";
+ case 0xB6:
+ return "B6";
+ case 0xB7:
+ return "B7";
+ case 0xB8:
+ return "B8";
+ case 0xB9:
+ return "B9";
+ case 0xBA:
+ return "BA";
+ case 0xBB:
+ return "BB";
+ case 0xBC:
+ return "BC";
+ case 0xBD:
+ return "BD";
+ case 0xBE:
+ return "BE";
+ case 0xBF:
+ return "BF";
+ case 0xC0:
+ return "C0";
+ case 0xC1:
+ return "C1";
+ case 0xC2:
+ return "C2";
+ case 0xC3:
+ return "C3";
+ case 0xC4:
+ return "C4";
+ case 0xC5:
+ return "C5";
+ case 0xC6:
+ return "C6";
+ case 0xC7:
+ return "C7";
+ case 0xC8:
+ return "C8";
+ case 0xC9:
+ return "C9";
+ case 0xCA:
+ return "CA";
+ case 0xCB:
+ return "CB";
+ case 0xCC:
+ return "CC";
+ case 0xCD:
+ return "CD";
+ case 0xCE:
+ return "CE";
+ case 0xCF:
+ return "CF";
+ case 0xD0:
+ return "D0";
+ case 0xD1:
+ return "D1";
+ case 0xD2:
+ return "D2";
+ case 0xD3:
+ return "D3";
+ case 0xD4:
+ return "D4";
+ case 0xD5:
+ return "D5";
+ case 0xD6:
+ return "D6";
+ case 0xD7:
+ return "D7";
+ case 0xD8:
+ return "D8";
+ case 0xD9:
+ return "D9";
+ case 0xDA:
+ return "DA";
+ case 0xDB:
+ return "DB";
+ case 0xDC:
+ return "DC";
+ case 0xDD:
+ return "DD";
+ case 0xDE:
+ return "DE";
+ case 0xDF:
+ return "DF";
+ case 0xE0:
+ return "E0";
+ case 0xE1:
+ return "E1";
+ case 0xE2:
+ return "E2";
+ case 0xE3:
+ return "E3";
+ case 0xE4:
+ return "E4";
+ case 0xE5:
+ return "E5";
+ case 0xE6:
+ return "E6";
+ case 0xE7:
+ return "E7";
+ case 0xE8:
+ return "E8";
+ case 0xE9:
+ return "E9";
+ case 0xEA:
+ return "EA";
+ case 0xEB:
+ return "EB";
+ case 0xEC:
+ return "EC";
+ case 0xED:
+ return "ED";
+ case 0xEE:
+ return "EE";
+ case 0xEF:
+ return "EF";
+ case 0xF0:
+ return "F0";
+ case 0xF1:
+ return "F1";
+ case 0xF2:
+ return "F2";
+ case 0xF3:
+ return "F3";
+ case 0xF4:
+ return "F4";
+ case 0xF5:
+ return "F5";
+ case 0xF6:
+ return "F6";
+ case 0xF7:
+ return "F7";
+ case 0xF8:
+ return "F8";
+ case 0xF9:
+ return "F9";
+ case 0xFA:
+ return "FA";
+ case 0xFB:
+ return "FB";
+ case 0xFC:
+ return "FC";
+ case 0xFD:
+ return "FD";
+ case 0xFE:
+ return "FE";
+ case 0xFF:
+ return "FF";
+ default:
+ return "??";
+ }
+ }
+
+
+
+ /**
+ * Attempts to compress the data in the provided source array into the
+ * given destination array. If the compressed data will fit into the
+ * destination array, then this method will return the number of bytes
+ * of compressed data in the array. Otherwise, it will return -1 to
+ * indicate that the compression was not successful. Note that if -1
+ * is returned, then the data in the destination array should be
+ * considered invalid.
+ *
+ * @param src
+ * The array containing the raw data to compress.
+ * @param srcOff
+ * The start offset of the source data.
+ * @param srcLen
+ * The maximum number of source data bytes to compress.
+ * @param dst
+ * The array into which the compressed data should be
+ * written.
+ * @param dstOff
+ * The start offset of the compressed data.
+ * @param dstLen
+ * The maximum number of bytes of compressed data.
+ * @return The number of bytes of compressed data, or -1 if it was not
+ * possible to actually compress the data.
+ */
+ public static int compress(byte[] src, int srcOff, int srcLen,
+ byte[] dst, int dstOff, int dstLen)
+ {
+ final Deflater deflater = new Deflater();
+ try
+ {
+ deflater.setInput(src, srcOff, srcLen);
+ deflater.finish();
+
+ final int compressedLength = deflater
+ .deflate(dst, dstOff, dstLen);
+ if (deflater.finished())
+ {
+ return compressedLength;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+ finally
+ {
+ deflater.end();
+ }
+ }
+
+
+
+ /**
+ * Attempts to compress the data in the provided byte sequence into
+ * the provided byte string builder. Note that if compression was not
+ * successful, then the byte string builder will be left unchanged.
+ *
+ * @param input
+ * The source data to be compressed.
+ * @param output
+ * The destination buffer to which the compressed data will
+ * be appended.
+ * @return <code>true</code> if compression was successful or
+ * <code>false</code> otherwise.
+ */
+ public static boolean compress(ByteSequence input,
+ ByteStringBuilder output)
+ {
+ // Avoid extra copies if possible.
+ byte[] inputBuffer;
+ int inputOffset;
+ final int inputLength = input.length();
+
+ if (input instanceof ByteString)
+ {
+ final ByteString byteString = (ByteString) input;
+ inputBuffer = byteString.buffer;
+ inputOffset = byteString.offset;
+ }
+ else if (input instanceof ByteStringBuilder)
+ {
+ final ByteStringBuilder builder = (ByteStringBuilder) input;
+ inputBuffer = builder.buffer;
+ inputOffset = 0;
+ }
+ else
+ {
+ inputBuffer = new byte[inputLength];
+ inputOffset = 0;
+ input.copyTo(inputBuffer);
+ }
+
+ // Make sure the free space in the destination buffer is at least
+ // as big as this.
+ output.ensureAdditionalCapacity(inputLength);
+
+ final int compressedSize = compress(inputBuffer, inputOffset,
+ inputLength, output.buffer, output.length, output.buffer.length
+ - output.length);
+
+ if (compressedSize != -1)
+ {
+ if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINE))
+ {
+ StaticUtils.DEBUG_LOG.fine(String.format("Compression %d/%d%n",
+ compressedSize, inputLength));
+ }
+
+ output.length += compressedSize;
+ return true;
+ }
+
+ return false;
+ }
+
+
+
+ public static ByteString evaluateEscapes(SubstringReader reader,
+ char[] escapeChars, boolean trim) throws DecodeException
+ {
+ return evaluateEscapes(reader, escapeChars, escapeChars, trim);
+ }
+
+
+
+ public static ByteString evaluateEscapes(SubstringReader reader,
+ char[] escapeChars, char[] delimiterChars, boolean trim)
+ throws DecodeException
+ {
+ int length = 0;
+ int lengthWithoutSpace = 0;
+ char c;
+ ByteStringBuilder valueBuffer = null;
+
+ if (trim)
+ {
+ reader.skipWhitespaces();
+ }
+
+ reader.mark();
+ while (reader.remaining() > 0)
+ {
+ c = reader.read();
+ if (c == 0x5C) // The backslash character
+ {
+ if (valueBuffer == null)
+ {
+ valueBuffer = new ByteStringBuilder();
+ }
+ valueBuffer.append(reader.read(length));
+ valueBuffer.append(evaluateEscapedChar(reader, escapeChars));
+ reader.mark();
+ length = lengthWithoutSpace = 0;
+ }
+ if (delimiterChars != null)
+ {
+ for (final char delimiterChar : delimiterChars)
+ {
+ if (c == delimiterChar)
+ {
+ reader.reset();
+ if (valueBuffer != null)
+ {
+ if (trim)
+ {
+ valueBuffer.append(reader.read(lengthWithoutSpace));
+ }
+ else
+ {
+ valueBuffer.append(reader.read(length));
+ }
+ return valueBuffer.toByteString();
+ }
+ else
+ {
+ if (trim)
+ {
+ if (lengthWithoutSpace > 0)
+ {
+ return ByteString.valueOf(reader
+ .read(lengthWithoutSpace));
+ }
+ return ByteString.empty();
+ }
+ if (length > 0)
+ {
+ return ByteString.valueOf(reader.read(length));
+ }
+ return ByteString.empty();
+ }
+ }
+ }
+ }
+ length++;
+ if (c != ' ')
+ {
+ lengthWithoutSpace = length;
+ }
+ else
+ {
+ lengthWithoutSpace++;
+ }
+ }
+
+ reader.reset();
+ if (valueBuffer != null)
+ {
+ if (trim)
+ {
+ valueBuffer.append(reader.read(lengthWithoutSpace));
+ }
+ else
+ {
+ valueBuffer.append(reader.read(length));
+ }
+ return valueBuffer.toByteString();
+ }
+ else
+ {
+ if (trim)
+ {
+ if (lengthWithoutSpace > 0)
+ {
+ return ByteString.valueOf(reader.read(lengthWithoutSpace));
+ }
+ return ByteString.empty();
+ }
+ if (length > 0)
+ {
+ return ByteString.valueOf(reader.read(length));
+ }
+ return ByteString.empty();
+ }
+ }
+
+
+
+ /**
+ * Returns a string containing provided date formatted using the
+ * generalized time syntax.
+ *
+ * @param date
+ * The date to be formated.
+ * @return The string containing provided date formatted using the
+ * generalized time syntax.
+ * @throws NullPointerException
+ * If {@code date} was {@code null}.
+ */
+ public static String formatAsGeneralizedTime(Date date)
+ {
+ return formatAsGeneralizedTime(date.getTime());
+ }
+
+
+
+ /**
+ * Returns a string containing provided date formatted using the
+ * generalized time syntax.
+ *
+ * @param date
+ * The date to be formated.
+ * @return The string containing provided date formatted using the
+ * generalized time syntax.
+ * @throws IllegalArgumentException
+ * If {@code date} was invalid.
+ */
+ public static String formatAsGeneralizedTime(long date)
+ {
+ // Generalized time has the format yyyyMMddHHmmss.SSS'Z'
+
+ // Do this in a thread-safe non-synchronized fashion.
+ // (Simple)DateFormat is neither fast nor thread-safe.
+
+ final StringBuilder sb = new StringBuilder(19);
+
+ final GregorianCalendar calendar = new GregorianCalendar(
+ TIME_ZONE_UTC_OBJ);
+ calendar.setLenient(false);
+ calendar.setTimeInMillis(date);
+
+ // Format the year yyyy.
+ int n = calendar.get(Calendar.YEAR);
+ if (n < 0)
+ {
+ final IllegalArgumentException e = new IllegalArgumentException(
+ "Year cannot be < 0:" + n);
+ StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "format",
+ e);
+ throw e;
+ }
+ else if (n < 10)
+ {
+ sb.append("000");
+ }
+ else if (n < 100)
+ {
+ sb.append("00");
+ }
+ else if (n < 1000)
+ {
+ sb.append("0");
+ }
+ sb.append(n);
+
+ // Format the month MM.
+ n = calendar.get(Calendar.MONTH) + 1;
+ if (n < 10)
+ {
+ sb.append("0");
+ }
+ sb.append(n);
+
+ // Format the day dd.
+ n = calendar.get(Calendar.DAY_OF_MONTH);
+ if (n < 10)
+ {
+ sb.append("0");
+ }
+ sb.append(n);
+
+ // Format the hour HH.
+ n = calendar.get(Calendar.HOUR_OF_DAY);
+ if (n < 10)
+ {
+ sb.append("0");
+ }
+ sb.append(n);
+
+ // Format the minute mm.
+ n = calendar.get(Calendar.MINUTE);
+ if (n < 10)
+ {
+ sb.append("0");
+ }
+ sb.append(n);
+
+ // Format the seconds ss.
+ n = calendar.get(Calendar.SECOND);
+ if (n < 10)
+ {
+ sb.append("0");
+ }
+ sb.append(n);
+
+ // Format the milli-seconds.
+ sb.append('.');
+ n = calendar.get(Calendar.MILLISECOND);
+ if (n < 10)
+ {
+ sb.append("00");
+ }
+ else if (n < 100)
+ {
+ sb.append("0");
+ }
+ sb.append(n);
+
+ // Format the timezone (always Z).
+ sb.append('Z');
+
+ return sb.toString();
+ }
+
+
+
+ /**
+ * Construct a byte array containing the UTF-8 encoding of the
+ * provided string. This is significantly faster than calling
+ * {@link String#getBytes(String)} for ASCII strings.
+ *
+ * @param s
+ * The string to convert to a UTF-8 byte array.
+ * @return Returns a byte array containing the UTF-8 encoding of the
+ * provided string.
+ */
+ public static byte[] getBytes(String s)
+ {
+ if (s == null)
+ {
+ return null;
+ }
+
+ try
+ {
+ char c;
+ final int length = s.length();
+ final byte[] returnArray = new byte[length];
+ for (int i = 0; i < length; i++)
+ {
+ c = s.charAt(i);
+ returnArray[i] = (byte) (c & 0x0000007F);
+ if (c != returnArray[i])
+ {
+ return s.getBytes("UTF-8");
+ }
+ }
+
+ return returnArray;
+ }
+ catch (final Exception e)
+ {
+ DEBUG_LOG.warning("Unable to encode UTF-8 string " + s);
+
+ return s.getBytes();
+ }
+ }
+
+
+
+ /**
+ * Retrieves the best human-readable message for the provided
+ * exception. For exceptions defined in the OpenDS project, it will
+ * attempt to use the message (combining it with the message ID if
+ * available). For some exceptions that use encapsulation (e.g.,
+ * InvocationTargetException), it will be unwrapped and the cause will
+ * be treated. For all others, the
+ *
+ * @param t
+ * The {@code Throwable} object for which to retrieve the
+ * message.
+ * @return The human-readable message generated for the provided
+ * exception.
+ */
+ public static Message getExceptionMessage(Throwable t)
+ {
+ if (t instanceof LocalizableException)
+ {
+ final LocalizableException ie = (LocalizableException) t;
+
+ final StringBuilder message = new StringBuilder();
+ message.append(ie.getMessageObject());
+ message.append(" (id=");
+ final Message ieMsg = ie.getMessageObject();
+ if (ieMsg != null)
+ {
+ message.append(ieMsg.getDescriptor().getId());
+ }
+ else
+ {
+ message.append(MessageDescriptor.NULL_ID);
+ }
+ message.append(")");
+ return Message.raw(message.toString());
+ }
+ else if (t instanceof NullPointerException)
+ {
+ final StackTraceElement[] stackElements = t.getStackTrace();
+
+ final MessageBuilder message = new MessageBuilder();
+ message.append("NullPointerException(");
+ message.append(stackElements[0].getFileName());
+ message.append(":");
+ message.append(stackElements[0].getLineNumber());
+ message.append(")");
+ return message.toMessage();
+ }
+ else if (t instanceof InvocationTargetException
+ && t.getCause() != null)
+ {
+ return getExceptionMessage(t.getCause());
+ }
+ else
+ {
+ final StringBuilder message = new StringBuilder();
+
+ final String className = t.getClass().getName();
+ final int periodPos = className.lastIndexOf('.');
+ if (periodPos > 0)
+ {
+ message.append(className.substring(periodPos + 1));
+ }
+ else
+ {
+ message.append(className);
+ }
+
+ message.append("(");
+ if (t.getMessage() == null)
+ {
+ final StackTraceElement[] stackElements = t.getStackTrace();
+ message.append(stackElements[0].getFileName());
+ message.append(":");
+ message.append(stackElements[0].getLineNumber());
+
+ // FIXME Temporary to debug issue 2256.
+ if (t instanceof IllegalStateException)
+ {
+ for (int i = 1; i < stackElements.length; i++)
+ {
+ message.append(' ');
+ message.append(stackElements[i].getFileName());
+ message.append(":");
+ message.append(stackElements[i].getLineNumber());
+ }
+ }
+ }
+ else
+ {
+ message.append(t.getMessage());
+ }
+
+ message.append(")");
+
+ return Message.raw(message.toString());
+ }
+ }
+
+
+
+ /**
+ * Converts the provided hexadecimal string to a byte array.
+ *
+ * @param hexString
+ * The hexadecimal string to convert to a byte array.
+ * @return The byte array containing the binary representation of the
+ * provided hex string.
+ * @throws java.text.ParseException
+ * If the provided string contains invalid hexadecimal
+ * digits or does not contain an even number of digits.
+ */
+ public static byte[] hexStringToByteArray(String hexString)
+ throws ParseException
+ {
+ int length;
+ if (hexString == null || (length = hexString.length()) == 0)
+ {
+ return new byte[0];
+ }
+
+ if (length % 2 == 1)
+ {
+ final Message message = ERR_HEX_DECODE_INVALID_LENGTH
+ .get(hexString);
+ throw new ParseException(message.toString(), 0);
+ }
+
+ final int arrayLength = length / 2;
+ final byte[] returnArray = new byte[arrayLength];
+ for (int i = 0; i < arrayLength; i++)
+ {
+ returnArray[i] = hexToByte(hexString.charAt(i * 2), hexString
+ .charAt(i * 2 + 1));
+ }
+
+ return returnArray;
+ }
+
+
+
+ /**
+ * Converts the provided pair of characters to a byte.
+ *
+ * @param c1
+ * The first hexadecimal character.
+ * @param c2
+ * The second hexadecimal character.
+ * @return The byte containing the binary representation of the
+ * provided hex characters.
+ * @throws ParseException
+ * If the provided string contains invalid hexadecimal
+ * digits or does not contain an even number of digits.
+ */
+ public static byte hexToByte(char c1, char c2) throws ParseException
+ {
+ byte b;
+ switch (c1)
+ {
+ case '0':
+ b = 0x00;
+ break;
+ case '1':
+ b = 0x10;
+ break;
+ case '2':
+ b = 0x20;
+ break;
+ case '3':
+ b = 0x30;
+ break;
+ case '4':
+ b = 0x40;
+ break;
+ case '5':
+ b = 0x50;
+ break;
+ case '6':
+ b = 0x60;
+ break;
+ case '7':
+ b = 0x70;
+ break;
+ case '8':
+ b = (byte) 0x80;
+ break;
+ case '9':
+ b = (byte) 0x90;
+ break;
+ case 'A':
+ case 'a':
+ b = (byte) 0xA0;
+ break;
+ case 'B':
+ case 'b':
+ b = (byte) 0xB0;
+ break;
+ case 'C':
+ case 'c':
+ b = (byte) 0xC0;
+ break;
+ case 'D':
+ case 'd':
+ b = (byte) 0xD0;
+ break;
+ case 'E':
+ case 'e':
+ b = (byte) 0xE0;
+ break;
+ case 'F':
+ case 'f':
+ b = (byte) 0xF0;
+ break;
+ default:
+ final Message message = ERR_HEX_DECODE_INVALID_CHARACTER.get(
+ new String(new char[] { c1, c2 }), c1);
+ throw new ParseException(message.toString(), 0);
+ }
+
+ switch (c2)
+ {
+ case '0':
+ // No action required.
+ break;
+ case '1':
+ b |= 0x01;
+ break;
+ case '2':
+ b |= 0x02;
+ break;
+ case '3':
+ b |= 0x03;
+ break;
+ case '4':
+ b |= 0x04;
+ break;
+ case '5':
+ b |= 0x05;
+ break;
+ case '6':
+ b |= 0x06;
+ break;
+ case '7':
+ b |= 0x07;
+ break;
+ case '8':
+ b |= 0x08;
+ break;
+ case '9':
+ b |= 0x09;
+ break;
+ case 'A':
+ case 'a':
+ b |= 0x0A;
+ break;
+ case 'B':
+ case 'b':
+ b |= 0x0B;
+ break;
+ case 'C':
+ case 'c':
+ b |= 0x0C;
+ break;
+ case 'D':
+ case 'd':
+ b |= 0x0D;
+ break;
+ case 'E':
+ case 'e':
+ b |= 0x0E;
+ break;
+ case 'F':
+ case 'f':
+ b |= 0x0F;
+ break;
+ default:
+ final Message message = ERR_HEX_DECODE_INVALID_CHARACTER.get(
+ new String(new char[] { c1, c2 }), c1);
+ throw new ParseException(message.toString(), 0);
+ }
+
+ return b;
+ }
+
+
+
+ /**
+ * Indicates whether the provided character is an ASCII alphabetic
+ * character.
+ *
+ * @param c
+ * The character for which to make the determination.
+ * @return <CODE>true</CODE> if the provided value is an uppercase or
+ * lowercase ASCII alphabetic character, or <CODE>false</CODE>
+ * if it is not.
+ */
+ public static boolean isAlpha(char c)
+ {
+ final ASCIICharProp cp = ASCIICharProp.valueOf(c);
+ return cp != null ? cp.isLetter() : false;
+ }
+
+
+
+ /**
+ * Indicates whether the provided character is a numeric digit.
+ *
+ * @param c
+ * The character for which to make the determination.
+ * @return <CODE>true</CODE> if the provided character represents a
+ * numeric digit, or <CODE>false</CODE> if not.
+ */
+ public static boolean isDigit(char c)
+ {
+ final ASCIICharProp cp = ASCIICharProp.valueOf(c);
+ return cp != null ? cp.isDigit() : false;
+ }
+
+
+
+ /**
+ * Indicates whether the provided character is a hexadecimal digit.
+ *
+ * @param c
+ * The character for which to make the determination.
+ * @return <CODE>true</CODE> if the provided character represents a
+ * hexadecimal digit, or <CODE>false</CODE> if not.
+ */
+ public static boolean isHexDigit(char c)
+ {
+ final ASCIICharProp cp = ASCIICharProp.valueOf(c);
+ return cp != null ? cp.isHexDigit() : false;
+ }
+
+
+
+ /**
+ * Returns a string representation of the contents of the provided
+ * byte sequence using hexadecimal characters and a space between each
+ * byte.
+ *
+ * @param bytes
+ * The byte sequence.
+ * @return A string representation of the contents of the provided
+ * byte sequence using hexadecimal characters.
+ */
+ public static String toHex(ByteSequence bytes)
+ {
+ return toHex(bytes, new StringBuilder((bytes.length() - 1) * 3 + 2))
+ .toString();
+ }
+
+
+
+ /**
+ * Appends the string representation of the contents of the provided
+ * byte sequence to a string builder using hexadecimal characters and
+ * a space between each byte.
+ *
+ * @param bytes
+ * The byte sequence.
+ * @param builder
+ * The string builder to which the hexadecimal representation
+ * of {@code bytes} should be appended.
+ * @return The string builder.
+ */
+ public static StringBuilder toHex(ByteSequence bytes,
+ StringBuilder builder)
+ {
+ final int length = bytes.length();
+ builder.ensureCapacity(builder.length() + (length - 1) * 3 + 2);
+ builder.append(StaticUtils.byteToHex(bytes.byteAt(0)));
+ for (int i = 1; i < length; i++)
+ {
+ builder.append(" ");
+ builder.append(StaticUtils.byteToHex(bytes.byteAt(i)));
+ }
+ return builder;
+ }
+
+
+
+ /**
+ * Appends a string representation of the data in the provided byte
+ * sequence to the given string builder using the specified indent.
+ * <p>
+ * The data will be formatted with sixteen hex bytes in a row followed
+ * by the ASCII representation, then wrapping to a new line as
+ * necessary. The state of the byte buffer is not changed.
+ *
+ * @param bytes
+ * The byte sequence.
+ * @param builder
+ * The string builder to which the information is to be
+ * appended.
+ * @param indent
+ * The number of spaces to indent the output.
+ * @return The string builder.
+ */
+ public static StringBuilder toHexPlusAscii(ByteSequence bytes,
+ StringBuilder builder, int indent)
+ {
+ final StringBuilder indentBuf = new StringBuilder(indent);
+ for (int i = 0; i < indent; i++)
+ {
+ indentBuf.append(' ');
+ }
+
+ final int length = bytes.length();
+ int pos = 0;
+ while (length - pos >= 16)
+ {
+ final StringBuilder asciiBuf = new StringBuilder(17);
+
+ byte currentByte = bytes.byteAt(pos);
+ builder.append(indentBuf);
+ builder.append(StaticUtils.byteToHex(currentByte));
+ asciiBuf.append(byteToASCII(currentByte));
+ pos++;
+
+ for (int i = 1; i < 16; i++, pos++)
+ {
+ currentByte = bytes.byteAt(pos);
+ builder.append(' ');
+ builder.append(StaticUtils.byteToHex(currentByte));
+ asciiBuf.append(byteToASCII(currentByte));
+
+ if (i == 7)
+ {
+ builder.append(" ");
+ asciiBuf.append(' ');
+ }
+ }
+
+ builder.append(" ");
+ builder.append(asciiBuf);
+ builder.append(EOL);
+ }
+
+ final int remaining = length - pos;
+ if (remaining > 0)
+ {
+ final StringBuilder asciiBuf = new StringBuilder(remaining + 1);
+
+ byte currentByte = bytes.byteAt(pos);
+ builder.append(indentBuf);
+ builder.append(StaticUtils.byteToHex(currentByte));
+ asciiBuf.append(byteToASCII(currentByte));
+ pos++;
+
+ for (int i = 1; i < 16; i++, pos++)
+ {
+ builder.append(' ');
+
+ if (i < remaining)
+ {
+ currentByte = bytes.byteAt(pos);
+ builder.append(StaticUtils.byteToHex(currentByte));
+ asciiBuf.append(byteToASCII(currentByte));
+ }
+ else
+ {
+ builder.append(" ");
+ }
+
+ if (i == 7)
+ {
+ builder.append(" ");
+
+ if (i < remaining)
+ {
+ asciiBuf.append(' ');
+ }
+ }
+ }
+
+ builder.append(" ");
+ builder.append(asciiBuf);
+ builder.append(EOL);
+ }
+
+ return builder;
+ }
+
+
+
+ /**
+ * Appends a lowercase string representation of the contents of the
+ * given byte array to the provided buffer. This implementation
+ * presumes that the provided string will contain only ASCII
+ * characters and is optimized for that case. However, if a non-ASCII
+ * character is encountered it will fall back on a more expensive
+ * algorithm that will work properly for non-ASCII characters.
+ *
+ * @param b
+ * The byte array for which to obtain the lowercase string
+ * representation.
+ * @param builder
+ * The buffer to which the lowercase form of the string
+ * should be appended.
+ * @return The updated {@code StringBuilder}.
+ */
+ public static StringBuilder toLowerCase(ByteSequence b,
+ StringBuilder builder)
+ {
+ Validator.ensureNotNull(b, builder);
+
+ // FIXME: What locale should we use for non-ASCII characters? I
+ // think we should use default to the Unicode StringPrep.
+
+ final int origBufferLen = builder.length();
+ final int length = b.length();
+
+ for (int i = 0; i < length; i++)
+ {
+ final int c = b.byteAt(i);
+
+ if (c < 0)
+ {
+ builder.replace(origBufferLen, builder.length(), b.toString()
+ .toLowerCase(Locale.ENGLISH));
+ return builder;
+ }
+
+ // At this point 0 <= 'c' <= 128.
+ final ASCIICharProp cp = ASCIICharProp.valueOf(c);
+ builder.append(cp.toLowerCase());
+ }
+
+ return builder;
+ }
+
+
+
+ /**
+ * Retrieves a lower-case representation of the given string. This
+ * implementation presumes that the provided string will contain only
+ * ASCII characters and is optimized for that case. However, if a
+ * non-ASCII character is encountered it will fall back on a more
+ * expensive algorithm that will work properly for non-ASCII
+ * characters.
+ *
+ * @param s
+ * The string for which to obtain the lower-case
+ * representation.
+ * @return The lower-case representation of the given string.
+ */
+ public static String toLowerCase(String s)
+ {
+ Validator.ensureNotNull(s);
+
+ // FIXME: What locale should we use for non-ASCII characters? I
+ // think we should use default to the Unicode StringPrep.
+
+ // This code is optimized for the case where the input string 's'
+ // has already been converted to lowercase.
+ final int length = s.length();
+ int i = 0;
+ ASCIICharProp cp = null;
+
+ // Scan for non lowercase ASCII.
+ while (i < length)
+ {
+ cp = ASCIICharProp.valueOf(s.charAt(i));
+ if (cp == null || cp.isUpperCase()) break;
+ i++;
+ }
+
+ if (i == length)
+ {
+ // String was already lowercase ASCII.
+ return s;
+ }
+
+ // Found non lowercase ASCII.
+ final StringBuilder builder = new StringBuilder(length);
+ builder.append(s, 0, i);
+
+ if (cp != null)
+ {
+ // Upper-case ASCII.
+ builder.append(cp.toLowerCase());
+ i++;
+ while (i < length)
+ {
+ cp = ASCIICharProp.valueOf(s.charAt(i));
+ if (cp == null) break;
+ builder.append(cp.toLowerCase());
+ i++;
+ }
+ }
+
+ if (i < length)
+ {
+ builder.append(s.substring(i).toLowerCase(Locale.ENGLISH));
+ }
+
+ return builder.toString();
+ }
+
+
+
+ /**
+ * Appends a lower-case representation of the given string to the
+ * provided buffer. This implementation presumes that the provided
+ * string will contain only ASCII characters and is optimized for that
+ * case. However, if a non-ASCII character is encountered it will fall
+ * back on a more expensive algorithm that will work properly for
+ * non-ASCII characters.
+ *
+ * @param s
+ * The string for which to obtain the lower-case
+ * representation.
+ * @param builder
+ * The {@code StringBuilder} to which the lower-case form of
+ * the string should be appended.
+ * @return The updated {@code StringBuilder}.
+ */
+ public static StringBuilder toLowerCase(String s,
+ StringBuilder builder)
+ {
+ Validator.ensureNotNull(s, builder);
+
+ // FIXME: What locale should we use for non-ASCII characters? I
+ // think we should use default to the Unicode StringPrep.
+
+ final int length = s.length();
+ builder.ensureCapacity(builder.length() + length);
+
+ for (int i = 0; i < length; i++)
+ {
+ final ASCIICharProp cp = ASCIICharProp.valueOf(s.charAt(i));
+ if (cp != null)
+ {
+ builder.append(cp.toLowerCase());
+ }
+ else
+ {
+ // Non-ASCII.
+ builder.append(s.substring(i).toLowerCase(Locale.ENGLISH));
+ return builder;
+ }
+ }
+
+ return builder;
+ }
+
+
+
+ /**
+ * Attempts to uncompress the data in the provided source array into
+ * the given destination array. If the uncompressed data will fit into
+ * the given destination array, then this method will return the
+ * number of bytes of uncompressed data written into the destination
+ * buffer. Otherwise, it will return a negative value to indicate that
+ * the destination buffer was not large enough. The absolute value of
+ * that negative return value will indicate the buffer size required
+ * to fully decompress the data. Note that if a negative value is
+ * returned, then the data in the destination array should be
+ * considered invalid.
+ *
+ * @param src
+ * The array containing the raw data to compress.
+ * @param srcOff
+ * The start offset of the source data.
+ * @param srcLen
+ * The maximum number of source data bytes to compress.
+ * @param dst
+ * The array into which the compressed data should be
+ * written.
+ * @param dstOff
+ * The start offset of the compressed data.
+ * @param dstLen
+ * The maximum number of bytes of compressed data.
+ * @return A positive value containing the number of bytes of
+ * uncompressed data written into the destination buffer, or a
+ * negative value whose absolute value is the size of the
+ * destination buffer required to fully decompress the
+ * provided data.
+ * @throws java.util.zip.DataFormatException
+ * If a problem occurs while attempting to uncompress the
+ * data.
+ */
+ public static int uncompress(byte[] src, int srcOff, int srcLen,
+ byte[] dst, int dstOff, int dstLen) throws DataFormatException
+ {
+ final Inflater inflater = new Inflater();
+ try
+ {
+ inflater.setInput(src, srcOff, srcLen);
+
+ final int decompressedLength = inflater.inflate(dst, dstOff,
+ dstLen);
+ if (inflater.finished())
+ {
+ return decompressedLength;
+ }
+ else
+ {
+ int totalLength = decompressedLength;
+
+ while (!inflater.finished())
+ {
+ totalLength += inflater.inflate(dst, dstOff, dstLen);
+ }
+
+ return -totalLength;
+ }
+ }
+ finally
+ {
+ inflater.end();
+ }
+ }
+
+
+
+ /**
+ * Attempts to uncompress the data in the provided byte sequence into
+ * the provided byte string builder. Note that if uncompression was
+ * not successful, then the data in the destination buffer should be
+ * considered invalid.
+ *
+ * @param input
+ * The source data to be uncompressed.
+ * @param output
+ * The destination buffer to which the uncompressed data will
+ * be appended.
+ * @param uncompressedSize
+ * The uncompressed size of the data if known or 0 otherwise.
+ * @return <code>true</code> if decompression was successful or
+ * <code>false</code> otherwise.
+ * @throws java.util.zip.DataFormatException
+ * If a problem occurs while attempting to uncompress the
+ * data.
+ */
+ public static boolean uncompress(ByteSequence input,
+ ByteStringBuilder output, int uncompressedSize)
+ throws DataFormatException
+ {
+ // Avoid extra copies if possible.
+ byte[] inputBuffer;
+ int inputOffset;
+ final int inputLength = input.length();
+
+ if (input instanceof ByteString)
+ {
+ final ByteString byteString = (ByteString) input;
+ inputBuffer = byteString.buffer;
+ inputOffset = byteString.offset;
+ }
+ else if (input instanceof ByteStringBuilder)
+ {
+ final ByteStringBuilder builder = (ByteStringBuilder) input;
+ inputBuffer = builder.buffer;
+ inputOffset = 0;
+ }
+ else
+ {
+ inputBuffer = new byte[inputLength];
+ inputOffset = 0;
+ input.copyTo(inputBuffer);
+ }
+
+ // Resize destination buffer if a uncompressed size was provided.
+ if (uncompressedSize > 0)
+ {
+ output.ensureAdditionalCapacity(uncompressedSize);
+ }
+
+ int decompressResult = uncompress(inputBuffer, inputOffset,
+ inputLength, output.buffer, output.length, output.buffer.length
+ - output.length);
+
+ if (decompressResult < 0)
+ {
+ // The destination buffer wasn't big enough. Resize and retry.
+ output.ensureAdditionalCapacity(-decompressResult);
+ decompressResult = uncompress(inputBuffer, inputOffset,
+ inputLength, output.buffer, output.length,
+ output.buffer.length - output.length);
+ }
+
+ if (decompressResult >= 0)
+ {
+ // It was successful.
+ output.length += decompressResult;
+ return true;
+ }
+
+ // Still unsuccessful. Give up.
+ return false;
+ }
+
+
+
+ /**
+ * Retrieves the printable ASCII representation of the provided byte.
+ *
+ * @param b
+ * The byte for which to retrieve the printable ASCII
+ * representation.
+ * @return The printable ASCII representation of the provided byte, or
+ * a space if the provided byte does not have printable ASCII
+ * representation.
+ */
+ private static char byteToASCII(byte b)
+ {
+ if (b >= 32 && b <= 126)
+ {
+ return (char) b;
+ }
+
+ return ' ';
+ }
+
+
+
+ private static char evaluateEscapedChar(SubstringReader reader,
+ char[] escapeChars) throws DecodeException
+ {
+ final char c1 = reader.read();
+ byte b;
+ switch (c1)
+ {
+ case '0':
+ b = 0x00;
+ break;
+ case '1':
+ b = 0x10;
+ break;
+ case '2':
+ b = 0x20;
+ break;
+ case '3':
+ b = 0x30;
+ break;
+ case '4':
+ b = 0x40;
+ break;
+ case '5':
+ b = 0x50;
+ break;
+ case '6':
+ b = 0x60;
+ break;
+ case '7':
+ b = 0x70;
+ break;
+ case '8':
+ b = (byte) 0x80;
+ break;
+ case '9':
+ b = (byte) 0x90;
+ break;
+ case 'A':
+ case 'a':
+ b = (byte) 0xA0;
+ break;
+ case 'B':
+ case 'b':
+ b = (byte) 0xB0;
+ break;
+ case 'C':
+ case 'c':
+ b = (byte) 0xC0;
+ break;
+ case 'D':
+ case 'd':
+ b = (byte) 0xD0;
+ break;
+ case 'E':
+ case 'e':
+ b = (byte) 0xE0;
+ break;
+ case 'F':
+ case 'f':
+ b = (byte) 0xF0;
+ break;
+ default:
+ if (c1 == 0x5C)
+ {
+ return c1;
+ }
+ if (escapeChars != null)
+ {
+ for (final char escapeChar : escapeChars)
+ {
+ if (c1 == escapeChar)
+ {
+ return c1;
+ }
+ }
+ }
+ final Message message = ERR_INVALID_ESCAPE_CHAR.get(reader
+ .getString(), c1);
+ throw DecodeException.error(message);
+ }
+
+ // The two positions must be the hex characters that
+ // comprise the escaped value.
+ if (reader.remaining() == 0)
+ {
+ final Message message = ERR_HEX_DECODE_INVALID_LENGTH.get(reader
+ .getString());
+
+ throw DecodeException.error(message);
+ }
+
+ final char c2 = reader.read();
+ switch (c2)
+ {
+ case '0':
+ // No action required.
+ break;
+ case '1':
+ b |= 0x01;
+ break;
+ case '2':
+ b |= 0x02;
+ break;
+ case '3':
+ b |= 0x03;
+ break;
+ case '4':
+ b |= 0x04;
+ break;
+ case '5':
+ b |= 0x05;
+ break;
+ case '6':
+ b |= 0x06;
+ break;
+ case '7':
+ b |= 0x07;
+ break;
+ case '8':
+ b |= 0x08;
+ break;
+ case '9':
+ b |= 0x09;
+ break;
+ case 'A':
+ case 'a':
+ b |= 0x0A;
+ break;
+ case 'B':
+ case 'b':
+ b |= 0x0B;
+ break;
+ case 'C':
+ case 'c':
+ b |= 0x0C;
+ break;
+ case 'D':
+ case 'd':
+ b |= 0x0D;
+ break;
+ case 'E':
+ case 'e':
+ b |= 0x0E;
+ break;
+ case 'F':
+ case 'f':
+ b |= 0x0F;
+ break;
+ default:
+ final Message message = ERR_HEX_DECODE_INVALID_CHARACTER.get(
+ new String(new char[] { c1, c2 }), c1);
+ throw DecodeException.error(message);
+ }
+ return (char) b;
+ }
+
+
+
+ // Prevent instantiation.
+ private StaticUtils()
+ {
+ // No implementation required.
+ }
+
+}
diff --git a/sdk/src/org/opends/sdk/util/StringPrepProfile.java b/sdk/src/org/opends/sdk/util/StringPrepProfile.java
new file mode 100644
index 0000000..e8b9abe
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/StringPrepProfile.java
@@ -0,0 +1,696 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.util;
+
+
+
+import static org.opends.sdk.util.Validator.ensureNotNull;
+
+import java.util.HashMap;
+import java.util.HashSet;
+
+
+
+/**
+ * This class defines the "stringprep" profile as defined in RFC 4518.
+ * It must be used by all the matching rules that support unicode
+ * characters. For a complete list of such rules, refer to Section 4.2,
+ * RFC 4517.
+ */
+public final class StringPrepProfile
+{
+ /**
+ * A Table defining the mapped code-points as per RFC 3454.
+ */
+ private static class MappingTable
+ {
+ // Set of chars which are deleted from the incoming value.
+ private final static HashSet<Character> map2null =
+ new HashSet<Character>();
+ // Set of chars which are replaced by a SPACE when found.
+ private final static HashSet<Character> map2space =
+ new HashSet<Character>();
+ // Table for case-folding. Map of Character and String containing
+ // uppercase
+ // and lowercase value as the key-value pair.
+ private final static HashMap<Character, String> caseMappingTable =
+ new HashMap<Character, String>();
+
+ static
+ {
+ // Appendix B.1 RFC 3454.
+ final char[][] mapped2null =
+ new char[][] { { '\u0000', '\u0008' },
+ { '\u000E', '\u001F' }, { '\u007F', '\u0084' },
+ { '\u0086', '\u009F' }, { '\u00AD' }, { '\u034F' },
+ { '\u06DD' }, { '\u070F' }, { '\u1806' },
+ { '\u180B', '\u180E' }, { '\u200C', '\u200F' },
+ { '\u202A', '\u202E' }, { '\u2060', '\u2063' },
+ { '\u206A', '\u206F' }, { '\uFE00', '\uFE0F' },
+ { '\uFEFF' }, { '\uFFF9', '\uFFFC' } };
+
+ for (final char[] element : mapped2null)
+ {
+ if (element.length == 1)
+ {
+ map2null.add(element[0]);
+ }
+ else
+ {
+ // Contains a range of values.
+ for (char c = element[0]; c <= element[1]; c++)
+ {
+ map2null.add(c);
+ }
+ }
+ }
+
+ final char[] mapped2Space =
+ new char[] { '\u0009', 0xA, '\u000B', '\u000C', 0xD,
+ '\u0085', '\u00A0', '\u1680', '\u2000', '\u2001',
+ '\u2002', '\u2003', '\u2004', '\u2005', '\u2006',
+ '\u2007', '\u2008', '\u2009', '\u200A', '\u2028',
+ '\u2029', '\u202F', '\u205F', '\u3000' };
+
+ for (final char c : mapped2Space)
+ {
+ map2space.add(c);
+ }
+
+ // Appendix B.2 RFC 3454.
+ // Build an uppercase array and a lowercase array and create a map
+ // of both
+ // values.
+ final char[] upperCaseArr =
+ new char[] { '\u0041', '\u0042', '\u0043', '\u0044',
+ '\u0045', '\u0046', '\u0047', '\u0048', '\u0049',
+ '\u004A', '\u004B', '\u004C', '\u004D', '\u004E',
+ '\u004F', '\u0050', '\u0051', '\u0052', '\u0053',
+ '\u0054', '\u0055', '\u0056', '\u0057', '\u0058',
+ '\u0059', '\u005A', '\u00B5', '\u00C0', '\u00C1',
+ '\u00C2', '\u00C3', '\u00C4', '\u00C5', '\u00C6',
+ '\u00C7', '\u00C8', '\u00C9', '\u00CA', '\u00CB',
+ '\u00CC', '\u00CD', '\u00CE', '\u00CF', '\u00D0',
+ '\u00D1', '\u00D2', '\u00D3', '\u00D4', '\u00D5',
+ '\u00D6', '\u00D8', '\u00D9', '\u00DA', '\u00DB',
+ '\u00DC', '\u00DD', '\u00DE', '\u00DF', '\u0100',
+ '\u0102', '\u0104', '\u0106', '\u0108', '\u010A',
+ '\u010C', '\u010E', '\u0110', '\u0112', '\u0114',
+ '\u0116', '\u0118', '\u011A', '\u011C', '\u011E',
+ '\u0120', '\u0122', '\u0124', '\u0126', '\u0128',
+ '\u012A', '\u012C', '\u012E', '\u0130', '\u0132',
+ '\u0134', '\u0136', '\u0139', '\u013B', '\u013D',
+ '\u013F', '\u0141', '\u0143', '\u0145', '\u0147',
+ '\u0149', '\u014A', '\u014C', '\u014E', '\u0150',
+ '\u0152', '\u0154', '\u0156', '\u0158', '\u015A',
+ '\u015C', '\u015E', '\u0160', '\u0162', '\u0164',
+ '\u0166', '\u0168', '\u016A', '\u016C', '\u016E',
+ '\u0170', '\u0172', '\u0174', '\u0176', '\u0178',
+ '\u0179', '\u017B', '\u017D', '\u017F', '\u0181',
+ '\u0182', '\u0184', '\u0186', '\u0187', '\u0189',
+ '\u018A', '\u018B', '\u018E', '\u018F', '\u0190',
+ '\u0191', '\u0193', '\u0194', '\u0196', '\u0197',
+ '\u0198', '\u019C', '\u019D', '\u019F', '\u01A0',
+ '\u01A2', '\u01A4', '\u01A6', '\u01A7', '\u01A9',
+ '\u01AC', '\u01AE', '\u01AF', '\u01B1', '\u01B2',
+ '\u01B3', '\u01B5', '\u01B7', '\u01B8', '\u01BC',
+ '\u01C4', '\u01C5', '\u01C7', '\u01C8', '\u01CA',
+ '\u01CB', '\u01CD', '\u01CF', '\u01D1', '\u01D3',
+ '\u01D5', '\u01D7', '\u01D9', '\u01DB', '\u01DE',
+ '\u01E0', '\u01E2', '\u01E4', '\u01E6', '\u01E8',
+ '\u01EA', '\u01EC', '\u01EE', '\u01F0', '\u01F1',
+ '\u01F2', '\u01F4', '\u01F6', '\u01F7', '\u01F8',
+ '\u01FA', '\u01FC', '\u01FE', '\u0200', '\u0202',
+ '\u0204', '\u0206', '\u0208', '\u020A', '\u020C',
+ '\u020E', '\u0210', '\u0212', '\u0214', '\u0216',
+ '\u0218', '\u021A', '\u021C', '\u021E', '\u0220',
+ '\u0222', '\u0224', '\u0226', '\u0228', '\u022A',
+ '\u022C', '\u022E', '\u0230', '\u0232', '\u0345',
+ '\u037A', '\u0386', '\u0388', '\u0389', '\u038A',
+ '\u038C', '\u038E', '\u038F', '\u0390', '\u0391',
+ '\u0392', '\u0393', '\u0394', '\u0395', '\u0396',
+ '\u0397', '\u0398', '\u0399', '\u039A', '\u039B',
+ '\u039C', '\u039D', '\u039E', '\u039F', '\u03A0',
+ '\u03A1', '\u03A3', '\u03A4', '\u03A5', '\u03A6',
+ '\u03A7', '\u03A8', '\u03A9', '\u03AA', '\u03AB',
+ '\u03B0', '\u03C2', '\u03D0', '\u03D1', '\u03D2',
+ '\u03D3', '\u03D4', '\u03D5', '\u03D6', '\u03D8',
+ '\u03DA', '\u03DC', '\u03DE', '\u03E0', '\u03E2',
+ '\u03E4', '\u03E6', '\u03E8', '\u03EA', '\u03EC',
+ '\u03EE', '\u03F0', '\u03F1', '\u03F2', '\u03F4',
+ '\u03F5', '\u0400', '\u0401', '\u0402', '\u0403',
+ '\u0404', '\u0405', '\u0406', '\u0407', '\u0408',
+ '\u0409', '\u040A', '\u040B', '\u040C', '\u040D',
+ '\u040E', '\u040F', '\u0410', '\u0411', '\u0412',
+ '\u0413', '\u0414', '\u0415', '\u0416', '\u0417',
+ '\u0418', '\u0419', '\u041A', '\u041B', '\u041C',
+ '\u041D', '\u041E', '\u041F', '\u0420', '\u0421',
+ '\u0422', '\u0423', '\u0424', '\u0425', '\u0426',
+ '\u0427', '\u0428', '\u0429', '\u042A', '\u042B',
+ '\u042C', '\u042D', '\u042E', '\u042F', '\u0460',
+ '\u0462', '\u0464', '\u0466', '\u0468', '\u046A',
+ '\u046C', '\u046E', '\u0470', '\u0472', '\u0474',
+ '\u0476', '\u0478', '\u047A', '\u047C', '\u047E',
+ '\u0480', '\u048A', '\u048C', '\u048E', '\u0490',
+ '\u0492', '\u0494', '\u0496', '\u0498', '\u049A',
+ '\u049C', '\u049E', '\u04A0', '\u04A2', '\u04A4',
+ '\u04A6', '\u04A8', '\u04AA', '\u04AC', '\u04AE',
+ '\u04B0', '\u04B2', '\u04B4', '\u04B6', '\u04B8',
+ '\u04BA', '\u04BC', '\u04BE', '\u04C1', '\u04C3',
+ '\u04C5', '\u04C7', '\u04C9', '\u04CB', '\u04CD',
+ '\u04D0', '\u04D2', '\u04D4', '\u04D6', '\u04D8',
+ '\u04DA', '\u04DC', '\u04DE', '\u04E0', '\u04E2',
+ '\u04E4', '\u04E6', '\u04E8', '\u04EA', '\u04EC',
+ '\u04EE', '\u04F0', '\u04F2', '\u04F4', '\u04F8',
+ '\u0500', '\u0502', '\u0504', '\u0506', '\u0508',
+ '\u050A', '\u050C', '\u050E', '\u0531', '\u0532',
+ '\u0533', '\u0534', '\u0535', '\u0536', '\u0537',
+ '\u0538', '\u0539', '\u053A', '\u053B', '\u053C',
+ '\u053D', '\u053E', '\u053F', '\u0540', '\u0541',
+ '\u0542', '\u0543', '\u0544', '\u0545', '\u0546',
+ '\u0547', '\u0548', '\u0549', '\u054A', '\u054B',
+ '\u054C', '\u054D', '\u054E', '\u054F', '\u0550',
+ '\u0551', '\u0552', '\u0553', '\u0554', '\u0555',
+ '\u0556', '\u0587', '\u1E00', '\u1E02', '\u1E04',
+ '\u1E06', '\u1E08', '\u1E0A', '\u1E0C', '\u1E0E',
+ '\u1E10', '\u1E12', '\u1E14', '\u1E16', '\u1E18',
+ '\u1E1A', '\u1E1C', '\u1E1E', '\u1E20', '\u1E22',
+ '\u1E24', '\u1E26', '\u1E28', '\u1E2A', '\u1E2C',
+ '\u1E2E', '\u1E30', '\u1E32', '\u1E34', '\u1E36',
+ '\u1E38', '\u1E3A', '\u1E3C', '\u1E3E', '\u1E40',
+ '\u1E42', '\u1E44', '\u1E46', '\u1E48', '\u1E4A',
+ '\u1E4C', '\u1E4E', '\u1E50', '\u1E52', '\u1E54',
+ '\u1E56', '\u1E58', '\u1E5A', '\u1E5C', '\u1E5E',
+ '\u1E60', '\u1E62', '\u1E64', '\u1E66', '\u1E68',
+ '\u1E6A', '\u1E6C', '\u1E6E', '\u1E70', '\u1E72',
+ '\u1E74', '\u1E76', '\u1E78', '\u1E7A', '\u1E7C',
+ '\u1E7E', '\u1E80', '\u1E82', '\u1E84', '\u1E86',
+ '\u1E88', '\u1E8A', '\u1E8C', '\u1E8E', '\u1E90',
+ '\u1E92', '\u1E94', '\u1E96', '\u1E97', '\u1E98',
+ '\u1E99', '\u1E9A', '\u1E9B', '\u1EA0', '\u1EA2',
+ '\u1EA4', '\u1EA6', '\u1EA8', '\u1EAA', '\u1EAC',
+ '\u1EAE', '\u1EB0', '\u1EB2', '\u1EB4', '\u1EB6',
+ '\u1EB8', '\u1EBA', '\u1EBC', '\u1EBE', '\u1EC0',
+ '\u1EC2', '\u1EC4', '\u1EC6', '\u1EC8', '\u1ECA',
+ '\u1ECC', '\u1ECE', '\u1ED0', '\u1ED2', '\u1ED4',
+ '\u1ED6', '\u1ED8', '\u1EDA', '\u1EDC', '\u1EDE',
+ '\u1EE0', '\u1EE2', '\u1EE4', '\u1EE6', '\u1EE8',
+ '\u1EEA', '\u1EEC', '\u1EEE', '\u1EF0', '\u1EF2',
+ '\u1EF4', '\u1EF6', '\u1EF8', '\u1F08', '\u1F09',
+ '\u1F0A', '\u1F0B', '\u1F0C', '\u1F0D', '\u1F0E',
+ '\u1F0F', '\u1F18', '\u1F19', '\u1F1A', '\u1F1B',
+ '\u1F1C', '\u1F1D', '\u1F28', '\u1F29', '\u1F2A',
+ '\u1F2B', '\u1F2C', '\u1F2D', '\u1F2E', '\u1F2F',
+ '\u1F38', '\u1F39', '\u1F3A', '\u1F3B', '\u1F3C',
+ '\u1F3D', '\u1F3E', '\u1F3F', '\u1F48', '\u1F49',
+ '\u1F4A', '\u1F4B', '\u1F4C', '\u1F4D', '\u1F50',
+ '\u1F52', '\u1F54', '\u1F56', '\u1F59', '\u1F5B',
+ '\u1F5D', '\u1F5F', '\u1F68', '\u1F69', '\u1F6A',
+ '\u1F6B', '\u1F6C', '\u1F6D', '\u1F6E', '\u1F6F',
+ '\u1F80', '\u1F81', '\u1F82', '\u1F83', '\u1F84',
+ '\u1F85', '\u1F86', '\u1F87', '\u1F88', '\u1F89',
+ '\u1F8A', '\u1F8B', '\u1F8C', '\u1F8D', '\u1F8E',
+ '\u1F8F', '\u1F90', '\u1F91', '\u1F92', '\u1F93',
+ '\u1F94', '\u1F95', '\u1F96', '\u1F97', '\u1F98',
+ '\u1F99', '\u1F9A', '\u1F9B', '\u1F9C', '\u1F9D',
+ '\u1F9E', '\u1F9F', '\u1FA0', '\u1FA1', '\u1FA2',
+ '\u1FA3', '\u1FA4', '\u1FA5', '\u1FA6', '\u1FA7',
+ '\u1FA8', '\u1FA9', '\u1FAA', '\u1FAB', '\u1FAC',
+ '\u1FAD', '\u1FAE', '\u1FAF', '\u1FB2', '\u1FB3',
+ '\u1FB4', '\u1FB6', '\u1FB7', '\u1FB8', '\u1FB9',
+ '\u1FBA', '\u1FBB', '\u1FBC', '\u1FBE', '\u1FC2',
+ '\u1FC3', '\u1FC4', '\u1FC6', '\u1FC7', '\u1FC8',
+ '\u1FC9', '\u1FCA', '\u1FCB', '\u1FCC', '\u1FD2',
+ '\u1FD3', '\u1FD6', '\u1FD7', '\u1FD8', '\u1FD9',
+ '\u1FDA', '\u1FDB', '\u1FE2', '\u1FE3', '\u1FE4',
+ '\u1FE6', '\u1FE7', '\u1FE8', '\u1FE9', '\u1FEA',
+ '\u1FEB', '\u1FEC', '\u1FF2', '\u1FF3', '\u1FF4',
+ '\u1FF6', '\u1FF7', '\u1FF8', '\u1FF9', '\u1FFA',
+ '\u1FFB', '\u1FFC', '\u20A8', '\u2102', '\u2103',
+ '\u2107', '\u2109', '\u210B', '\u210C', '\u210D',
+ '\u2110', '\u2111', '\u2112', '\u2115', '\u2116',
+ '\u2119', '\u211A', '\u211B', '\u211C', '\u211D',
+ '\u2120', '\u2121', '\u2122', '\u2124', '\u2126',
+ '\u2128', '\u212A', '\u212B', '\u212C', '\u212D',
+ '\u2130', '\u2131', '\u2133', '\u213E', '\u213F',
+ '\u2145', '\u2160', '\u2161', '\u2162', '\u2163',
+ '\u2164', '\u2165', '\u2166', '\u2167', '\u2168',
+ '\u2169', '\u216A', '\u216B', '\u216C', '\u216D',
+ '\u216E', '\u216F', '\u24B6', '\u24B7', '\u24B8',
+ '\u24B9', '\u24BA', '\u24BB', '\u24BC', '\u24BD',
+ '\u24BE', '\u24BF', '\u24C0', '\u24C1', '\u24C2',
+ '\u24C3', '\u24C4', '\u24C5', '\u24C6', '\u24C7',
+ '\u24C8', '\u24C9', '\u24CA', '\u24CB', '\u24CC',
+ '\u24CD', '\u24CE', '\u24CF', '\u3371', '\u3373',
+ '\u3375', '\u3380', '\u3381', '\u3382', '\u3383',
+ '\u3384', '\u3385', '\u3386', '\u3387', '\u338A',
+ '\u338B', '\u338C', '\u3390', '\u3391', '\u3392',
+ '\u3393', '\u3394', '\u33A9', '\u33AA', '\u33AB',
+ '\u33AC', '\u33B4', '\u33B5', '\u33B6', '\u33B7',
+ '\u33B8', '\u33B9', '\u33BA', '\u33BB', '\u33BC',
+ '\u33BD', '\u33BE', '\u33BF', '\u33C0', '\u33C1',
+ '\u33C3', '\u33C6', '\u33C7', '\u33C8', '\u33C9',
+ '\u33CB', '\u33CD', '\u33CE', '\u33D7', '\u33D9',
+ '\u33DA', '\u33DC', '\u33DD', '\uFB00', '\uFB01',
+ '\uFB02', '\uFB03', '\uFB04', '\uFB05', '\uFB06',
+ '\uFB13', '\uFB14', '\uFB15', '\uFB16', '\uFB17',
+ '\uFF21', '\uFF22', '\uFF23', '\uFF24', '\uFF25',
+ '\uFF26', '\uFF27', '\uFF28', '\uFF29', '\uFF2A',
+ '\uFF2B', '\uFF2C', '\uFF2D', '\uFF2E', '\uFF2F',
+ '\uFF30', '\uFF31', '\uFF32', '\uFF33', '\uFF34',
+ '\uFF35', '\uFF36', '\uFF37', '\uFF38', '\uFF39',
+ '\uFF3A' };
+ final String[] lowerCaseFoldedArr =
+ new String[] { "\u0061", "\u0062", "\u0063", "\u0064",
+ "\u0065", "\u0066", "\u0067", "\u0068", "\u0069",
+ "\u006A", "\u006B", "\u006C", "\u006D", "\u006E",
+ "\u006F", "\u0070", "\u0071", "\u0072", "\u0073",
+ "\u0074", "\u0075", "\u0076", "\u0077", "\u0078",
+ "\u0079", "\u007A", "\u03BC", "\u00E0", "\u00E1",
+ "\u00E2", "\u00E3", "\u00E4", "\u00E5", "\u00E6",
+ "\u00E7", "\u00E8", "\u00E9", "\u00EA", "\u00EB",
+ "\u00EC", "\u00ED", "\u00EE", "\u00EF", "\u00F0",
+ "\u00F1", "\u00F2", "\u00F3", "\u00F4", "\u00F5",
+ "\u00F6", "\u00F8", "\u00F9", "\u00FA", "\u00FB",
+ "\u00FC", "\u00FD", "\u00FE", "\u0073\u0073", "\u0101",
+ "\u0103", "\u0105", "\u0107", "\u0109", "\u010B",
+ "\u010D", "\u010F", "\u0111", "\u0113", "\u0115",
+ "\u0117", "\u0119", "\u011B", "\u011D", "\u011F",
+ "\u0121", "\u0123", "\u0125", "\u0127", "\u0129",
+ "\u012B", "\u012D", "\u012F", "\u0069\u0307", "\u0133",
+ "\u0135", "\u0137", "\u013A", "\u013C", "\u013E",
+ "\u0140", "\u0142", "\u0144", "\u0146", "\u0148",
+ "\u02BC\u006E", "\u014B", "\u014D", "\u014F", "\u0151",
+ "\u0153", "\u0155", "\u0157", "\u0159", "\u015B",
+ "\u015D", "\u015F", "\u0161", "\u0163", "\u0165",
+ "\u0167", "\u0169", "\u016B", "\u016D", "\u016F",
+ "\u0171", "\u0173", "\u0175", "\u0177", "\u00FF",
+ "\u017A", "\u017C", "\u017E", "\u0073", "\u0253",
+ "\u0183", "\u0185", "\u0254", "\u0188", "\u0256",
+ "\u0257", "\u018C", "\u01DD", "\u0259", "\u025B",
+ "\u0192", "\u0260", "\u0263", "\u0269", "\u0268",
+ "\u0199", "\u026F", "\u0272", "\u0275", "\u01A1",
+ "\u01A3", "\u01A5", "\u0280", "\u01A8", "\u0283",
+ "\u01AD", "\u0288", "\u01B0", "\u028A", "\u028B",
+ "\u01B4", "\u01B6", "\u0292", "\u01B9", "\u01BD",
+ "\u01C6", "\u01C6", "\u01C9", "\u01C9", "\u01CC",
+ "\u01CC", "\u01CE", "\u01D0", "\u01D2", "\u01D4",
+ "\u01D6", "\u01D8", "\u01DA", "\u01DC", "\u01DF",
+ "\u01E1", "\u01E3", "\u01E5", "\u01E7", "\u01E9",
+ "\u01EB", "\u01ED", "\u01EF", "\u006A\u030C", "\u01F3",
+ "\u01F3", "\u01F5", "\u0195", "\u01BF", "\u01F9",
+ "\u01FB", "\u01FD", "\u01FF", "\u0201", "\u0203",
+ "\u0205", "\u0207", "\u0209", "\u020B", "\u020D",
+ "\u020F", "\u0211", "\u0213", "\u0215", "\u0217",
+ "\u0219", "\u021B", "\u021D", "\u021F", "\u019E",
+ "\u0223", "\u0225", "\u0227", "\u0229", "\u022B",
+ "\u022D", "\u022F", "\u0231", "\u0233", "\u03B9",
+ "\u0020\u03B9", "\u03AC", "\u03AD", "\u03AE", "\u03AF",
+ "\u03CC", "\u03CD", "\u03CE", "\u03B9\u0308\u0301",
+ "\u03B1", "\u03B2", "\u03B3", "\u03B4", "\u03B5",
+ "\u03B6", "\u03B7", "\u03B8", "\u03B9", "\u03BA",
+ "\u03BB", "\u03BC", "\u03BD", "\u03BE", "\u03BF",
+ "\u03C0", "\u03C1", "\u03C3", "\u03C4", "\u03C5",
+ "\u03C6", "\u03C7", "\u03C8", "\u03C9", "\u03CA",
+ "\u03CB", "\u03C5\u0308\u0301", "\u03C3", "\u03B2",
+ "\u03B8", "\u03C5", "\u03CD", "\u03CB", "\u03C6",
+ "\u03C0", "\u03D9", "\u03DB", "\u03DD", "\u03DF",
+ "\u03E1", "\u03E3", "\u03E5", "\u03E7", "\u03E9",
+ "\u03EB", "\u03ED", "\u03EF", "\u03BA", "\u03C1",
+ "\u03C3", "\u03B8", "\u03B5", "\u0450", "\u0451",
+ "\u0452", "\u0453", "\u0454", "\u0455", "\u0456",
+ "\u0457", "\u0458", "\u0459", "\u045A", "\u045B",
+ "\u045C", "\u045D", "\u045E", "\u045F", "\u0430",
+ "\u0431", "\u0432", "\u0433", "\u0434", "\u0435",
+ "\u0436", "\u0437", "\u0438", "\u0439", "\u043A",
+ "\u043B", "\u043C", "\u043D", "\u043E", "\u043F",
+ "\u0440", "\u0441", "\u0442", "\u0443", "\u0444",
+ "\u0445", "\u0446", "\u0447", "\u0448", "\u0449",
+ "\u044A", "\u044B", "\u044C", "\u044D", "\u044E",
+ "\u044F", "\u0461", "\u0463", "\u0465", "\u0467",
+ "\u0469", "\u046B", "\u046D", "\u046F", "\u0471",
+ "\u0473", "\u0475", "\u0477", "\u0479", "\u047B",
+ "\u047D", "\u047F", "\u0481", "\u048B", "\u048D",
+ "\u048F", "\u0491", "\u0493", "\u0495", "\u0497",
+ "\u0499", "\u049B", "\u049D", "\u049F", "\u04A1",
+ "\u04A3", "\u04A5", "\u04A7", "\u04A9", "\u04AB",
+ "\u04AD", "\u04AF", "\u04B1", "\u04B3", "\u04B5",
+ "\u04B7", "\u04B9", "\u04BB", "\u04BD", "\u04BF",
+ "\u04C2", "\u04C4", "\u04C6", "\u04C8", "\u04CA",
+ "\u04CC", "\u04CE", "\u04D1", "\u04D3", "\u04D5",
+ "\u04D7", "\u04D9", "\u04DB", "\u04DD", "\u04DF",
+ "\u04E1", "\u04E3", "\u04E5", "\u04E7", "\u04E9",
+ "\u04EB", "\u04ED", "\u04EF", "\u04F1", "\u04F3",
+ "\u04F5", "\u04F9", "\u0501", "\u0503", "\u0505",
+ "\u0507", "\u0509", "\u050B", "\u050D", "\u050F",
+ "\u0561", "\u0562", "\u0563", "\u0564", "\u0565",
+ "\u0566", "\u0567", "\u0568", "\u0569", "\u056A",
+ "\u056B", "\u056C", "\u056D", "\u056E", "\u056F",
+ "\u0570", "\u0571", "\u0572", "\u0573", "\u0574",
+ "\u0575", "\u0576", "\u0577", "\u0578", "\u0579",
+ "\u057A", "\u057B", "\u057C", "\u057D", "\u057E",
+ "\u057F", "\u0580", "\u0581", "\u0582", "\u0583",
+ "\u0584", "\u0585", "\u0586", "\u0565\u0582", "\u1E01",
+ "\u1E03", "\u1E05", "\u1E07", "\u1E09", "\u1E0B",
+ "\u1E0D", "\u1E0F", "\u1E11", "\u1E13", "\u1E15",
+ "\u1E17", "\u1E19", "\u1E1B", "\u1E1D", "\u1E1F",
+ "\u1E21", "\u1E23", "\u1E25", "\u1E27", "\u1E29",
+ "\u1E2B", "\u1E2D", "\u1E2F", "\u1E31", "\u1E33",
+ "\u1E35", "\u1E37", "\u1E39", "\u1E3B", "\u1E3D",
+ "\u1E3F", "\u1E41", "\u1E43", "\u1E45", "\u1E47",
+ "\u1E49", "\u1E4B", "\u1E4D", "\u1E4F", "\u1E51",
+ "\u1E53", "\u1E55", "\u1E57", "\u1E59", "\u1E5B",
+ "\u1E5D", "\u1E5F", "\u1E61", "\u1E63", "\u1E65",
+ "\u1E67", "\u1E69", "\u1E6B", "\u1E6D", "\u1E6F",
+ "\u1E71", "\u1E73", "\u1E75", "\u1E77", "\u1E79",
+ "\u1E7B", "\u1E7D", "\u1E7F", "\u1E81", "\u1E83",
+ "\u1E85", "\u1E87", "\u1E89", "\u1E8B", "\u1E8D",
+ "\u1E8F", "\u1E91", "\u1E93", "\u1E95", "\u0068\u0331",
+ "\u0074\u0308", "\u0077\u030A", "\u0079\u030A",
+ "\u0061\u02BE", "\u1E61", "\u1EA1", "\u1EA3", "\u1EA5",
+ "\u1EA7", "\u1EA9", "\u1EAB", "\u1EAD", "\u1EAF",
+ "\u1EB1", "\u1EB3", "\u1EB5", "\u1EB7", "\u1EB9",
+ "\u1EBB", "\u1EBD", "\u1EBF", "\u1EC1", "\u1EC3",
+ "\u1EC5", "\u1EC7", "\u1EC9", "\u1ECB", "\u1ECD",
+ "\u1ECF", "\u1ED1", "\u1ED3", "\u1ED5", "\u1ED7",
+ "\u1ED9", "\u1EDB", "\u1EDD", "\u1EDF", "\u1EE1",
+ "\u1EE3", "\u1EE5", "\u1EE7", "\u1EE9", "\u1EEB",
+ "\u1EED", "\u1EEF", "\u1EF1", "\u1EF3", "\u1EF5",
+ "\u1EF7", "\u1EF9", "\u1F00", "\u1F01", "\u1F02",
+ "\u1F03", "\u1F04", "\u1F05", "\u1F06", "\u1F07",
+ "\u1F10", "\u1F11", "\u1F12", "\u1F13", "\u1F14",
+ "\u1F15", "\u1F20", "\u1F21", "\u1F22", "\u1F23",
+ "\u1F24", "\u1F25", "\u1F26", "\u1F27", "\u1F30",
+ "\u1F31", "\u1F32", "\u1F33", "\u1F34", "\u1F35",
+ "\u1F36", "\u1F37", "\u1F40", "\u1F41", "\u1F42",
+ "\u1F43", "\u1F44", "\u1F45", "\u03C5\u0313",
+ "\u03C5\u0313\u0300", "\u03C5\u0313\u0301",
+ "\u03C5\u0313\u0342", "\u1F51", "\u1F53", "\u1F55",
+ "\u1F57", "\u1F60", "\u1F61", "\u1F62", "\u1F63",
+ "\u1F64", "\u1F65", "\u1F66", "\u1F67", "\u1F00\u03B9",
+ "\u1F01\u03B9", "\u1F02\u03B9", "\u1F03\u03B9",
+ "\u1F04\u03B9", "\u1F05\u03B9", "\u1F06\u03B9",
+ "\u1F07\u03B9", "\u1F00\u03B9", "\u1F01\u03B9",
+ "\u1F02\u03B9", "\u1F03\u03B9", "\u1F04\u03B9",
+ "\u1F05\u03B9", "\u1F06\u03B9", "\u1F07\u03B9",
+ "\u1F20\u03B9", "\u1F21\u03B9", "\u1F22\u03B9",
+ "\u1F23\u03B9", "\u1F24\u03B9", "\u1F25\u03B9",
+ "\u1F26\u03B9", "\u1F27\u03B9", "\u1F20\u03B9",
+ "\u1F21\u03B9", "\u1F22\u03B9", "\u1F23\u03B9",
+ "\u1F24\u03B9", "\u1F25\u03B9", "\u1F26\u03B9",
+ "\u1F27\u03B9", "\u1F60\u03B9", "\u1F61\u03B9",
+ "\u1F62\u03B9", "\u1F63\u03B9", "\u1F64\u03B9",
+ "\u1F65\u03B9", "\u1F66\u03B9", "\u1F67\u03B9",
+ "\u1F60\u03B9", "\u1F61\u03B9", "\u1F62\u03B9",
+ "\u1F63\u03B9", "\u1F64\u03B9", "\u1F65\u03B9",
+ "\u1F66\u03B9", "\u1F67\u03B9", "\u1F70\u03B9",
+ "\u03B1\u03B9", "\u03AC\u03B9", "\u03B1\u0342",
+ "\u03B1\u0342\u03B9", "\u1FB0", "\u1FB1", "\u1F70",
+ "\u1F71", "\u03B1\u03B9", "\u03B9", "\u1F74\u03B9",
+ "\u03B7\u03B9", "\u03AE\u03B9", "\u03B7\u0342",
+ "\u03B7\u0342\u03B9", "\u1F72", "\u1F73", "\u1F74",
+ "\u1F75", "\u03B7\u03B9", "\u03B9\u0308\u0300",
+ "\u03B9\u0308\u0301", "\u03B9\u0342",
+ "\u03B9\u0308\u0342", "\u1FD0", "\u1FD1", "\u1F76",
+ "\u1F77", "\u03C5\u0308\u0300", "\u03C5\u0308\u0301",
+ "\u03C1\u0313", "\u03C5\u0342", "\u03C5\u0308\u0342",
+ "\u1FE0", "\u1FE1", "\u1F7A", "\u1F7B", "\u1FE5",
+ "\u1F7C\u03B9", "\u03C9\u03B9", "\u03CE\u03B9",
+ "\u03C9\u0342", "\u03C9\u0342\u03B9", "\u1F78", "\u1F79",
+ "\u1F7C", "\u1F7D", "\u03C9\u03B9", "\u0072\u0073",
+ "\u0063", "\u00B0\u0063", "\u025B", "\u00B0\u0066",
+ "\u0068", "\u0068", "\u0068", "\u0069", "\u0069",
+ "\u006C", "\u006E", "\u006E\u006F", "\u0070", "\u0071",
+ "\u0072", "\u0072", "\u0072", "\u0073\u006D",
+ "\u0074\u0065\u006C", "\u0074\u006D", "\u007A", "\u03C9",
+ "\u007A", "\u006B", "\u00E5", "\u0062", "\u0063",
+ "\u0065", "\u0066", "\u006D", "\u03B3", "\u03C0",
+ "\u0064", "\u2170", "\u2171", "\u2172", "\u2173",
+ "\u2174", "\u2175", "\u2176", "\u2177", "\u2178",
+ "\u2179", "\u217A", "\u217B", "\u217C", "\u217D",
+ "\u217E", "\u217F", "\u24D0", "\u24D1", "\u24D2",
+ "\u24D3", "\u24D4", "\u24D5", "\u24D6", "\u24D7",
+ "\u24D8", "\u24D9", "\u24DA", "\u24DB", "\u24DC",
+ "\u24DD", "\u24DE", "\u24DF", "\u24E0", "\u24E1",
+ "\u24E2", "\u24E3", "\u24E4", "\u24E5", "\u24E6",
+ "\u24E7", "\u24E8", "\u24E9", "\u0068\u0070\u0061",
+ "\u0061\u0075", "\u006F\u0076", "\u0070\u0061",
+ "\u006E\u0061", "\u03BC\u0061", "\u006D\u0061",
+ "\u006B\u0061", "\u006B\u0062", "\u006D\u0062",
+ "\u0067\u0062", "\u0070\u0066", "\u006E\u0066",
+ "\u03BC\u0066", "\u0068\u007A", "\u006B\u0068\u007A",
+ "\u006D\u0068\u007A", "\u0067\u0068\u007A",
+ "\u0074\u0068\u007A", "\u0070\u0061",
+ "\u006B\u0070\u0061", "\u006D\u0070\u0061",
+ "\u0067\u0070\u0061", "\u0070\u0076", "\u006E\u0076",
+ "\u03BC\u0076", "\u006D\u0076", "\u006B\u0076",
+ "\u006D\u0076", "\u0070\u0077", "\u006E\u0077",
+ "\u03BC\u0077", "\u006D\u0077", "\u006B\u0077",
+ "\u006D\u0077", "\u006B\u03C9", "\u006D\u03C9",
+ "\u0062\u0071", "\u0063\u2215\u006B\u0067",
+ "\u0063\u006F\u002E", "\u0064\u0062", "\u0067\u0079",
+ "\u0068\u0070", "\u006B\u006B", "\u006B\u006D",
+ "\u0070\u0068", "\u0070\u0070\u006D", "\u0070\u0072",
+ "\u0073\u0076", "\u0077\u0062", "\u0066\u0066",
+ "\u0066\u0069", "\u0066\u006C", "\u0066\u0066\u0069",
+ "\u0066\u0066\u006C", "\u0073\u0074", "\u0073\u0074",
+ "\u0574\u0576", "\u0574\u0565", "\u0574\u056B",
+ "\u057E\u0576", "\u0574\u056D", "\uFF41", "\uFF42",
+ "\uFF43", "\uFF44", "\uFF45", "\uFF46", "\uFF47",
+ "\uFF48", "\uFF49", "\uFF4A", "\uFF4B", "\uFF4C",
+ "\uFF4D", "\uFF4E", "\uFF4F", "\uFF50", "\uFF51",
+ "\uFF52", "\uFF53", "\uFF54", "\uFF55", "\uFF56",
+ "\uFF57", "\uFF58", "\uFF59", "\uFF5A" };
+ for (int count = 0; count < upperCaseArr.length; count++)
+ {
+ caseMappingTable.put(upperCaseArr[count],
+ lowerCaseFoldedArr[count]);
+ }
+ }
+
+
+
+ // Gets the mapped String.
+ private static void map(StringBuilder buffer,
+ ByteSequence sequence, boolean trim, boolean foldCase)
+ {
+ final String value = sequence.toString();
+ for (int i = 0; i < value.length(); i++)
+ {
+ final char c = value.charAt(i);
+ if (map2null.contains(c))
+ {
+ continue;
+ }
+
+ if (map2space.contains(c))
+ {
+ final int buffLen = buffer.length();
+ if (trim && buffLen == 0 || buffLen > 0
+ && buffer.charAt(buffLen - 1) == SPACE_CHAR)
+ {
+ /**
+ * Do not map this character into a space if: a . trimming
+ * is wanted and this was the first char. b. The last
+ * character was a space.
+ **/
+ continue;
+ }
+ buffer.append(SPACE_CHAR);
+ continue;
+ }
+
+ if (foldCase)
+ {
+ final String mapping = caseMappingTable.get(c);
+ if (mapping != null)
+ {
+ buffer.append(mapping);
+ continue;
+ }
+ }
+ // It came here so no match was found.
+ buffer.append(c);
+ }
+ }
+ }
+
+ /**
+ * Defines SPACE character.
+ */
+ private static final char SPACE_CHAR = '\u0020';
+
+ /**
+ * Indicates whether case should be folded during string preparation.
+ */
+ public static final boolean CASE_FOLD = true;
+
+ /**
+ * Indicates whether case should not be folded during string
+ * preparation.
+ */
+ public static final boolean NO_CASE_FOLD = false;
+
+ /**
+ * Indicates whether leading and trailing spaces should be trimmed
+ * during string preparation.
+ */
+ public static final boolean TRIM = true;
+
+
+
+ /**
+ * Prepares an attribute or assertion value as per stringprep
+ * algorithm defined in RFC 4518.
+ *
+ * @param buffer
+ * The buffer to which the prepared form of the string should
+ * be appended.
+ * @param sequence
+ * The {@link org.opends.sdk.util.ByteSequence} that
+ * needs preparation.
+ * @param trim
+ * Indicates whether leading and trailing spaces should be
+ * omitted from the string representation.
+ * @param foldCase
+ * Indicates whether the case will be folded during mapping.
+ * @see <a href="http://www.rfc-editor.org/rfc/rfc4518.txt">
+ * Internationalized String Preparation</a>
+ */
+ public static void prepareUnicode(StringBuilder buffer,
+ ByteSequence sequence, boolean trim, boolean foldCase)
+ {
+ ensureNotNull(buffer);
+ ensureNotNull(sequence);
+ // Optimize in the case of purely ascii characters which is the most
+ // common
+ // case.
+ final int length = sequence.length();
+ for (int i = 0; i < length; i++)
+ {
+ if ((sequence.byteAt(i) & 0x7F) != sequence.byteAt(i))
+ {
+ // Map the attribute value.
+ map(buffer, sequence.subSequence(i, length), trim, foldCase);
+ // Normalize the attribute value.
+ normalize(buffer);
+ break;
+ }
+ int buffLen = buffer.length();
+ switch (sequence.byteAt(i))
+ {
+ case ' ':
+ if (trim && buffLen == 0 || buffLen > 0
+ && buffer.charAt(buffLen - 1) == SPACE_CHAR)
+ {
+ break;
+ }
+ buffer.append(' ');
+ break;
+ default:
+ final byte b = sequence.byteAt(i);
+ // Perform mapping.
+ if (b >= '\u0009' && b < '\u000E')
+ {
+ // These characters are mapped to a SPACE.
+ buffLen = buffer.length();
+ if (trim && buffLen == 0 || buffLen > 0
+ && buffer.charAt(buffLen - 1) == ' ')
+ {
+ /**
+ * Do not map this character into a space if: a . trimming
+ * is desired and this was the leading char. b. The last
+ * character was a space.
+ **/
+ break;
+ }
+ else
+ {
+ buffer.append(SPACE_CHAR);
+ }
+ }
+ else if (b >= '\u0000' && b <= '\u0008' || b >= '\u000E'
+ && b <= '\u001F' || b == '\u007F')
+ {
+ // These characters are mapped to nothing and hence not copied
+ // over..
+ break;
+ }
+ else if (foldCase && b >= 65 && b <= 90)
+ {
+ // If case-folding is allowed then map to the lower case.
+ buffer.append((char) (b + 32));
+ }
+ else
+ {
+ buffer.append((char) b);
+ }
+ break;
+ }
+ }
+ if (trim)
+ {
+ // Strip off any trailing spaces.
+ for (int i = buffer.length() - 1; i > 0; i--)
+ {
+ if (buffer.charAt(i) == SPACE_CHAR)
+ {
+ buffer.delete(i, i + 1);
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ }
+
+
+
+ // Checks each character and replaces it with its mapping.
+ private static void map(StringBuilder buffer, ByteSequence value,
+ boolean trim, boolean foldCase)
+ {
+ MappingTable.map(buffer, value, trim, foldCase);
+ }
+
+
+
+ // Normalizes the input string with NFKC Form.
+ private static void normalize(StringBuilder buffer)
+ {
+ Platform.normalize(buffer);
+ }
+
+
+
+ // Prevent instantiation.
+ private StringPrepProfile()
+ {
+ // Nothing to do.
+ }
+}
diff --git a/sdk/src/org/opends/sdk/util/SubstringReader.java b/sdk/src/org/opends/sdk/util/SubstringReader.java
new file mode 100644
index 0000000..1efcd07
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/SubstringReader.java
@@ -0,0 +1,84 @@
+package org.opends.sdk.util;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: boli
+ * Date: Jul 13, 2009
+ * Time: 3:11:50 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class SubstringReader
+{
+ private String source;
+ private int pos;
+ private int mark;
+
+ public SubstringReader(String s) {
+ Validator.ensureNotNull(s);
+ source = s;
+ pos = 0;
+ mark = 0;
+ }
+
+ /**
+ * Attemps to read a substring of the specified length.
+ *
+ * @param length The number of characters to read.
+ * @return The substring.
+ */
+ public String read(int length)
+ {
+ String substring = source.substring(pos, pos + length);
+ pos += length;
+ return substring;
+ }
+
+ public char read()
+ {
+ return source.charAt(pos++);
+ }
+
+ public String getString()
+ {
+ return source;
+ }
+
+ public int pos()
+ {
+ return pos;
+ }
+
+ public int remaining()
+ {
+ return source.length() - pos;
+ }
+
+ /**
+ * Marks the present position in the stream. Subsequent calls
+ * to reset() will reposition the stream to this point.
+ */
+ public void mark()
+ {
+ mark = pos;
+ }
+
+ /**
+ * Resets the stream to the most recent mark, or to the beginning
+ * of the string if it has never been marked.
+ */
+ public void reset()
+ {
+ pos = mark;
+ }
+
+ public int skipWhitespaces()
+ {
+ int skipped = 0;
+ while(pos < source.length() && source.charAt(pos) == ' ')
+ {
+ skipped++;
+ pos++;
+ }
+ return skipped;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/util/Validator.java b/sdk/src/org/opends/sdk/util/Validator.java
new file mode 100644
index 0000000..47fbaaa
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/Validator.java
@@ -0,0 +1,215 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.util;
+
+
+
+/**
+ * Common methods for validating method arguments.
+ */
+public final class Validator
+{
+
+ /**
+ * Throws a {@code NullPointerException} if the provided argument is
+ * {@code null}. This method returns a reference to its single
+ * parameter so that it can be easily used in constructors.
+ *
+ * @param <T>
+ * The type of {@code o1}.
+ * @param o1
+ * The object to test.
+ * @return A reference to {@code o1}.
+ * @throws NullPointerException
+ * If the provided argument is {@code null}.
+ */
+ public static <T> T ensureNotNull(T o1) throws NullPointerException
+ {
+ if (o1 == null)
+ {
+ throw new NullPointerException();
+ }
+ return o1;
+ }
+
+
+
+ /**
+ * Throws a {@code NullPointerException} if any of the provided
+ * arguments are {@code null}.
+ *
+ * @param o1
+ * The first object to test.
+ * @param o2
+ * The second object to test.
+ * @throws NullPointerException
+ * If any of the provided arguments are {@code null}.
+ */
+ public static void ensureNotNull(Object o1, Object o2)
+ throws NullPointerException
+ {
+ if (o1 == null || o2 == null)
+ {
+ throw new NullPointerException();
+ }
+ }
+
+
+
+ /**
+ * Throws a {@code NullPointerException} if any of the provided
+ * arguments are {@code null}.
+ *
+ * @param o1
+ * The first object to test.
+ * @param o2
+ * The second object to test.
+ * @param o3
+ * The third object to test.
+ * @throws NullPointerException
+ * If any of the provided arguments are {@code null}.
+ */
+ public static void ensureNotNull(Object o1, Object o2, Object o3)
+ throws NullPointerException
+ {
+ if (o1 == null || o2 == null || o3 == null)
+ {
+ throw new NullPointerException();
+ }
+ }
+
+
+
+ /**
+ * Throws a {@code NullPointerException} if any of the provided
+ * arguments are {@code null}.
+ *
+ * @param o1
+ * The first object to test.
+ * @param o2
+ * The second object to test.
+ * @param o3
+ * The third object to test.
+ * @param o4
+ * The fourth object to test.
+ * @throws NullPointerException
+ * If any of the provided arguments are {@code null}.
+ */
+ public static void ensureNotNull(Object o1, Object o2, Object o3,
+ Object o4) throws NullPointerException
+ {
+ if (o1 == null || o2 == null || o3 == null || o4 == null)
+ {
+ throw new NullPointerException();
+ }
+ }
+
+
+
+ /**
+ * Throws a {@code NullPointerException} if any of the provided
+ * arguments are {@code null}.
+ *
+ * @param o1
+ * The first object to test.
+ * @param o2
+ * The second object to test.
+ * @param o3
+ * The third object to test.
+ * @param o4
+ * The fourth object to test.
+ * @param o5
+ * The fifth object to test.
+ * @throws NullPointerException
+ * If any of the provided arguments are {@code null}.
+ */
+ public static void ensureNotNull(Object o1, Object o2, Object o3,
+ Object o4, Object o5) throws NullPointerException
+ {
+ if (o1 == null || o2 == null || o3 == null || o4 == null
+ || o5 == null)
+ {
+ throw new NullPointerException();
+ }
+ }
+
+
+
+ /**
+ * Throws a {@code NullPointerException} if any of the provided
+ * arguments are {@code null}.
+ *
+ * @param objects
+ * The objects to test.
+ * @throws NullPointerException
+ * If any of the provided arguments are {@code null}.
+ */
+ public static void ensureNotNull(Object... objects)
+ throws NullPointerException
+ {
+ for (Object o : objects)
+ {
+ if (o == null)
+ {
+ throw new NullPointerException();
+ }
+ }
+ }
+
+
+
+ /**
+ * Throws an {@code IllegalArgumentException} if the provided
+ * condition is {@code false}.
+ *
+ * @param condition
+ * The condition, which must be {@code true} to avoid an
+ * exception.
+ * @param message
+ * The error message to include in the exception if it is
+ * thrown.
+ * @throws IllegalArgumentException
+ * If {@code condition} was {@code false}.
+ */
+ public static void ensureTrue(boolean condition, String message)
+ throws IllegalArgumentException
+ {
+ if (!condition)
+ {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+
+
+ // Prevent instantiation.
+ private Validator()
+ {
+ // No implementation required.
+ }
+}
diff --git a/sdk/src/org/opends/sdk/util/ssl/DistrustAllTrustManager.java b/sdk/src/org/opends/sdk/util/ssl/DistrustAllTrustManager.java
new file mode 100644
index 0000000..3b1ac39
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/ssl/DistrustAllTrustManager.java
@@ -0,0 +1,66 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2008 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.util.ssl;
+
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * An X509TrustManager which trusts everything.
+ */
+public class DistrustAllTrustManager implements X509TrustManager {
+
+ /**
+ * {@inheritDoc}
+ */
+ public void checkClientTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException
+ {
+ throw new CertificateException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void checkServerTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException
+ {
+ throw new CertificateException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public X509Certificate[] getAcceptedIssuers()
+ {
+ return new X509Certificate[0];
+ }
+}
+
diff --git a/sdk/src/org/opends/sdk/util/ssl/HostnameMismatchCertificateException.java b/sdk/src/org/opends/sdk/util/ssl/HostnameMismatchCertificateException.java
new file mode 100644
index 0000000..f6153af
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/ssl/HostnameMismatchCertificateException.java
@@ -0,0 +1,67 @@
+package org.opends.sdk.util.ssl;
+
+
+
+import java.security.cert.CertificateException;
+
+
+
+/**
+ * The certificate's subject DN's value and the host name we tried to
+ * connect to do not match.
+ */
+@SuppressWarnings("serial")
+public class HostnameMismatchCertificateException extends
+ CertificateException
+{
+ private String expectedHostname;
+
+ private String certificateHostname;
+
+
+
+ public HostnameMismatchCertificateException(String expectedHostname,
+ String certificateHostname)
+ {
+ this.expectedHostname = expectedHostname;
+ this.certificateHostname = certificateHostname;
+ }
+
+
+
+ public HostnameMismatchCertificateException(String msg,
+ String expectedHostname, String certificateHostname)
+ {
+ super(msg);
+ this.expectedHostname = expectedHostname;
+ this.certificateHostname = certificateHostname;
+ }
+
+
+
+ public String getExpectedHostname()
+ {
+ return expectedHostname;
+ }
+
+
+
+ public void setExpectedHostname(String expectedHostname)
+ {
+ this.expectedHostname = expectedHostname;
+ }
+
+
+
+ public String getCertificateHostname()
+ {
+ return certificateHostname;
+ }
+
+
+
+ public void setCertificateHostname(String certificateHostname)
+ {
+ this.certificateHostname = certificateHostname;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/util/ssl/PromptingTrustManager.java b/sdk/src/org/opends/sdk/util/ssl/PromptingTrustManager.java
new file mode 100644
index 0000000..77ee67f
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/ssl/PromptingTrustManager.java
@@ -0,0 +1,412 @@
+package org.opends.sdk.util.ssl;
+
+import static org.opends.messages.UtilityMessages.*;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.Validator;
+import org.opends.server.util.cli.*;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: boli
+ * Date: Oct 16, 2009
+ * Time: 10:38:50 AM
+ * To change this template use File | Settings | File Templates.
+ */
+public class PromptingTrustManager implements X509TrustManager
+{
+ static private final Logger LOG =
+ Logger.getLogger(PromptingTrustManager.class.getName());
+ static private final String DEFAULT_PATH =
+ System.getProperty("user.home") + File.separator + ".opends" +
+ File.separator + "keystore";
+ static private final char[] DEFAULT_PASSWORD = "OpenDS".toCharArray();
+
+ /**
+ * Enumeration description server certificate trust option.
+ */
+ private enum TrustOption
+ {
+ UNTRUSTED(1, INFO_LDAP_CONN_PROMPT_SECURITY_TRUST_OPTION_NO.get()),
+ SESSION(2,INFO_LDAP_CONN_PROMPT_SECURITY_TRUST_OPTION_SESSION.get()),
+ PERMAMENT(3,INFO_LDAP_CONN_PROMPT_SECURITY_TRUST_OPTION_ALWAYS.get()),
+ CERTIFICATE_DETAILS(4,
+ INFO_LDAP_CONN_PROMPT_SECURITY_CERTIFICATE_DETAILS.get());
+
+ private Integer choice;
+
+ private Message msg;
+
+ /**
+ * Private constructor.
+ *
+ * @param i
+ * the menu return value.
+ * @param msg
+ * the message message.
+ */
+ private TrustOption(int i, Message msg)
+ {
+ choice = i;
+ this.msg = msg;
+ }
+
+ /**
+ * Returns the choice number.
+ *
+ * @return the attribute name.
+ */
+ public Integer getChoice()
+ {
+ return choice;
+ }
+
+ /**
+ * Return the menu message.
+ *
+ * @return the menu message.
+ */
+ public Message getMenuMessage()
+ {
+ return msg;
+ }
+ }
+
+ private final KeyStore inMemoryTrustStore;
+ private final KeyStore onDiskTrustStore;
+
+ private final X509TrustManager inMemoryTrustManager;
+ private final X509TrustManager onDiskTrustManager;
+
+ private final X509TrustManager nestedTrustManager;
+ private final ConsoleApplication app;
+
+ public PromptingTrustManager(ConsoleApplication app,
+ X509TrustManager sourceTrustManager)
+ throws KeyStoreException, IOException, NoSuchAlgorithmException,
+ CertificateException
+ {
+ this(app, DEFAULT_PATH, sourceTrustManager);
+ }
+ public PromptingTrustManager(ConsoleApplication app, String acceptedStorePath,
+ X509TrustManager sourceTrustManager)
+ throws KeyStoreException, IOException, NoSuchAlgorithmException,
+ CertificateException
+ {
+ Validator.ensureNotNull(app, acceptedStorePath);
+ this.app = app;
+ this.nestedTrustManager = sourceTrustManager;
+ inMemoryTrustStore =
+ KeyStore.getInstance(KeyStore.getDefaultType());
+ onDiskTrustStore =
+ KeyStore.getInstance(KeyStore.getDefaultType());
+
+ File onDiskTrustStorePath = new File(acceptedStorePath);
+ inMemoryTrustStore.load(null, null);
+ if(!onDiskTrustStorePath.exists())
+ {
+ onDiskTrustStore.load(null, null);
+ }
+ else
+ {
+ FileInputStream fos = new FileInputStream(onDiskTrustStorePath);
+ onDiskTrustStore.load(fos, DEFAULT_PASSWORD);
+ }
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(
+ TrustManagerFactory.getDefaultAlgorithm());
+
+ tmf.init(inMemoryTrustStore);
+ X509TrustManager x509tm = null;
+ for(TrustManager tm : tmf.getTrustManagers())
+ {
+ if(tm instanceof X509TrustManager)
+ {
+ x509tm = (X509TrustManager)tm;
+ break;
+ }
+ }
+ if(x509tm == null)
+ {
+ throw new NoSuchAlgorithmException();
+ }
+ this.inMemoryTrustManager = x509tm;
+
+ tmf.init(onDiskTrustStore);
+ x509tm = null;
+ for(TrustManager tm : tmf.getTrustManagers())
+ {
+ if(tm instanceof X509TrustManager)
+ {
+ x509tm = (X509TrustManager)tm;
+ break;
+ }
+ }
+ if(x509tm == null)
+ {
+ throw new NoSuchAlgorithmException();
+ }
+ this.onDiskTrustManager = x509tm;
+ }
+
+ public void checkClientTrusted(X509Certificate[] x509Certificates, String s)
+ throws CertificateException
+ {
+ try
+ {
+ inMemoryTrustManager.checkClientTrusted(x509Certificates, s);
+ }
+ catch(Exception ce1)
+ {
+ try
+ {
+ onDiskTrustManager.checkClientTrusted(x509Certificates, s);
+ }
+ catch(Exception ce2)
+ {
+ if(nestedTrustManager != null)
+ {
+ try
+ {
+ nestedTrustManager.checkClientTrusted(x509Certificates, s);
+ }
+ catch(Exception ce3)
+ {
+ checkManuallyTrusted(x509Certificates, ce3);
+ }
+ }
+ else
+ {
+ checkManuallyTrusted(x509Certificates, ce1);
+ }
+ }
+ }
+ }
+
+ public void checkServerTrusted(X509Certificate[] x509Certificates, String s)
+ throws CertificateException
+ {
+ try
+ {
+ inMemoryTrustManager.checkServerTrusted(x509Certificates, s);
+ }
+ catch(Exception ce1)
+ {
+ try
+ {
+ onDiskTrustManager.checkServerTrusted(x509Certificates, s);
+ }
+ catch(Exception ce2)
+ {
+ if(nestedTrustManager != null)
+ {
+ try
+ {
+ nestedTrustManager.checkServerTrusted(x509Certificates, s);
+ }
+ catch(Exception ce3)
+ {
+ checkManuallyTrusted(x509Certificates, ce3);
+ }
+ }
+ else
+ {
+ checkManuallyTrusted(x509Certificates, ce1);
+ }
+ }
+ }
+ }
+
+ public X509Certificate[] getAcceptedIssuers() {
+ if(nestedTrustManager != null)
+ {
+ return nestedTrustManager.getAcceptedIssuers();
+ }
+ return new X509Certificate[0];
+ }
+
+ /**
+ * Indicate if the certificate chain can be trusted.
+ *
+ * @param chain The certificate chain to validate
+ * certificate.
+ */
+ private void checkManuallyTrusted(X509Certificate[] chain,
+ Exception exception)
+ throws CertificateException
+ {
+ app.println();
+ app.println(INFO_LDAP_CONN_PROMPT_SECURITY_SERVER_CERTIFICATE.get());
+ app.println();
+ for (int i = 0; i < chain.length; i++)
+ {
+ // Certificate DN
+ app.println(INFO_LDAP_CONN_SECURITY_SERVER_CERTIFICATE_USER_DN.get(
+ chain[i].getSubjectDN().toString()));
+
+ // certificate validity
+ app.println(
+ INFO_LDAP_CONN_SECURITY_SERVER_CERTIFICATE_VALIDITY.get(
+ chain[i].getNotBefore().toString(),
+ chain[i].getNotAfter().toString()));
+
+ // certificate Issuer
+ app.println(
+ INFO_LDAP_CONN_SECURITY_SERVER_CERTIFICATE_ISSUER.get(
+ chain[i].getIssuerDN().toString()));
+
+ if (i+1 <chain.length)
+ {
+ app.println();
+ app.println();
+ }
+ }
+ MenuBuilder<Integer> builder = new MenuBuilder<Integer>(app);
+ builder.setPrompt(INFO_LDAP_CONN_PROMPT_SECURITY_TRUST_OPTION.get());
+
+ TrustOption defaultTrustMethod = TrustOption.SESSION ;
+ for (TrustOption t : TrustOption.values())
+ {
+ int i = builder.addNumberedOption(t.getMenuMessage(), MenuResult
+ .success(t.getChoice()));
+ if (t.equals(defaultTrustMethod))
+ {
+ builder.setDefault(
+ INFO_LDAP_CONN_PROMPT_SECURITY_PROTOCOL_DEFAULT_CHOICE
+ .get(new Integer(i)), MenuResult.success(t.getChoice()));
+ }
+ }
+
+ app.println();
+ app.println();
+
+ Menu<Integer> menu = builder.toMenu();
+ while (true)
+ {
+ try
+ {
+ MenuResult<Integer> result = menu.run();
+ if (result.isSuccess())
+ {
+ if (result.getValue().equals(TrustOption.UNTRUSTED.getChoice()))
+ {
+ if(exception instanceof CertificateException)
+ {
+ throw (CertificateException)exception;
+ }
+ else
+ {
+ throw new CertificateException(exception);
+ }
+ }
+
+ if ((result.getValue().equals(TrustOption.CERTIFICATE_DETAILS
+ .getChoice())))
+ {
+ for (X509Certificate aChain : chain) {
+ app.println();
+ app.println(INFO_LDAP_CONN_SECURITY_SERVER_CERTIFICATE
+ .get(aChain.toString()));
+ }
+ continue;
+ }
+
+ // Update the trust manager with the new certificate
+ acceptCertificate(chain,
+ result.getValue().equals(TrustOption.PERMAMENT.getChoice()));
+ break;
+ }
+ else
+ {
+ // Should never happen.
+ throw new RuntimeException();
+ }
+ }
+ catch (CLIException cliE)
+ {
+ throw new RuntimeException(cliE);
+ }
+ }
+ }
+
+ /**
+ * This method is called when the user accepted a certificate.
+ * @param chain the certificate chain accepted by the user.
+ * certificate.
+ */
+ public void acceptCertificate(X509Certificate[] chain, boolean permanent)
+ {
+ if(permanent)
+ {
+ LOG.log(Level.INFO, "Permanently accepting certificate chain to " +
+ "truststore");
+ }
+ else
+ {
+ LOG.log(Level.INFO, "Accepting certificate chain for this session");
+ }
+
+ for (X509Certificate aChain : chain) {
+ try
+ {
+ String alias = aChain.getSubjectDN().getName();
+ inMemoryTrustStore.setCertificateEntry(alias, aChain);
+ if(permanent)
+ {
+ onDiskTrustStore.setCertificateEntry(alias, aChain);
+ }
+ }
+ catch(Exception e)
+ {
+ LOG.log(Level.WARNING, "Error setting certificate to store: " + e +
+ "\nCert: " + aChain.toString());
+ }
+ }
+
+ if(permanent)
+ {
+ try
+ {
+ File truststoreFile = new File(DEFAULT_PATH);
+ if (!truststoreFile.exists())
+ {
+ createFile(truststoreFile);
+ }
+ FileOutputStream fos = new FileOutputStream(truststoreFile);
+ onDiskTrustStore.store(fos, DEFAULT_PASSWORD);
+ fos.close();
+ }
+ catch(Exception e)
+ {
+ LOG.log(Level.WARNING, "Error saving store to disk: " + e);
+ }
+ }
+ }
+
+ private boolean createFile(File f) throws IOException {
+ boolean success = false;
+ if (f != null) {
+ File parent = f.getParentFile();
+ if (!parent.exists()) {
+ parent.mkdirs();
+ }
+ success = f.createNewFile();
+ }
+ return success;
+ }
+}
diff --git a/sdk/src/org/opends/sdk/util/ssl/TrustAllTrustManager.java b/sdk/src/org/opends/sdk/util/ssl/TrustAllTrustManager.java
new file mode 100644
index 0000000..17c0db5
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/ssl/TrustAllTrustManager.java
@@ -0,0 +1,64 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2008 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.util.ssl;
+
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * An X509TrustManager which trusts everything.
+ */
+public class TrustAllTrustManager implements X509TrustManager {
+
+ /**
+ * {@inheritDoc}
+ */
+ public void checkClientTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException
+ {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void checkServerTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException
+ {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public X509Certificate[] getAcceptedIssuers()
+ {
+ return new X509Certificate[0];
+ }
+}
+
diff --git a/sdk/src/org/opends/sdk/util/ssl/TrustStoreTrustManager.java b/sdk/src/org/opends/sdk/util/ssl/TrustStoreTrustManager.java
new file mode 100644
index 0000000..9b3ad5b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/ssl/TrustStoreTrustManager.java
@@ -0,0 +1,285 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2008-2009 Sun Microsystems, Inc.
+ * Portions Copyright 2009 Parametric Technology Corporation (PTC)
+ */
+
+package org.opends.sdk.util.ssl;
+
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+import org.opends.sdk.DN;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class is in charge of checking whether the certificates that are
+ * presented are trusted or not. This implementation tries to check also
+ * that the subject DN of the certificate corresponds to the host passed
+ * using the setHostName method. This implementation also checks to make
+ * sure the certificate is in the validity period. The constructor tries
+ * to use a default TrustManager from the system and if it cannot be
+ * retrieved this class will only accept the certificates explicitly
+ * accepted by the user (and specified by calling acceptCertificate).
+ */
+public class TrustStoreTrustManager implements X509TrustManager
+{
+ static private final Logger LOG = Logger
+ .getLogger(TrustStoreTrustManager.class.getName());
+
+ private final X509TrustManager trustManager;
+
+ private final KeyStore truststore;
+
+ private final File truststoreFile;
+
+ private final char[] truststorePassword;
+
+ private final String hostname;
+
+ private final boolean checkValidityDates;
+
+
+
+ /**
+ * The default constructor.
+ */
+ public TrustStoreTrustManager(String truststorePath,
+ String truststorePassword, String hostname,
+ boolean checkValidityDates) throws KeyStoreException,
+ IOException, NoSuchAlgorithmException, CertificateException
+ {
+ Validator.ensureNotNull(truststorePath);
+ this.truststoreFile = new File(truststorePath);
+ if (truststorePassword != null)
+ {
+ this.truststorePassword = truststorePassword.toCharArray();
+ }
+ else
+ {
+ this.truststorePassword = null;
+ }
+ truststore = KeyStore.getInstance(KeyStore.getDefaultType());
+
+ FileInputStream fos = new FileInputStream(truststoreFile);
+ truststore.load(fos, this.truststorePassword);
+ TrustManagerFactory tmf = TrustManagerFactory
+ .getInstance(TrustManagerFactory.getDefaultAlgorithm());
+
+ tmf.init(truststore);
+ X509TrustManager x509tm = null;
+ for (TrustManager tm : tmf.getTrustManagers())
+ {
+ if (tm instanceof X509TrustManager)
+ {
+ x509tm = (X509TrustManager) tm;
+ break;
+ }
+ }
+ if (x509tm == null)
+ {
+ throw new NoSuchAlgorithmException();
+ }
+ this.trustManager = x509tm;
+ this.hostname = hostname;
+ this.checkValidityDates = checkValidityDates;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void checkClientTrusted(X509Certificate[] chain,
+ String authType) throws CertificateException
+ {
+ verifyExpiration(chain);
+ verifyHostName(chain);
+ trustManager.checkClientTrusted(chain, authType);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void checkServerTrusted(X509Certificate[] chain,
+ String authType) throws CertificateException
+ {
+ verifyExpiration(chain);
+ verifyHostName(chain);
+ trustManager.checkClientTrusted(chain, authType);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public X509Certificate[] getAcceptedIssuers()
+ {
+ if (trustManager != null)
+ {
+ return trustManager.getAcceptedIssuers();
+ }
+ else
+ {
+ return new X509Certificate[0];
+ }
+ }
+
+
+
+ private void verifyExpiration(X509Certificate[] chain)
+ throws CertificateException
+ {
+ Date currentDate = new Date();
+ for (X509Certificate c : chain)
+ {
+ try
+ {
+ c.checkValidity(currentDate);
+ }
+ catch (CertificateExpiredException cee)
+ {
+ LOG.log(Level.WARNING, "Refusing to trust security"
+ + " certificate \"" + c.getSubjectDN().getName()
+ + "\" because it" + " expired on "
+ + String.valueOf(c.getNotAfter()));
+
+ throw cee;
+ }
+ catch (CertificateNotYetValidException cnyve)
+ {
+ LOG.log(Level.WARNING, "Refusing to trust security"
+ + " certificate \"" + c.getSubjectDN().getName()
+ + "\" because it" + " is not valid until "
+ + String.valueOf(c.getNotBefore()));
+
+ throw cnyve;
+ }
+ }
+ }
+
+
+
+ /**
+ * Verifies that the provided certificate chains subject DN
+ * corresponds to the host name specified with the setHost method.
+ *
+ * @param chain
+ * the certificate chain to analyze.
+ * @throws HostnameMismatchCertificateException
+ * if the subject DN of the certificate does not match with
+ * the host name specified with the method setHost.
+ */
+ private void verifyHostName(X509Certificate[] chain)
+ throws HostnameMismatchCertificateException
+ {
+ if (hostname != null)
+ {
+ try
+ {
+ DN dn = DN.valueOf(
+ chain[0].getSubjectX500Principal().getName(), Schema
+ .getCoreSchema());
+ String value = dn.iterator().next().iterator().next()
+ .getAttributeValue().toString();
+ if (!hostMatch(value, hostname))
+ {
+ throw new HostnameMismatchCertificateException(
+ "Hostname mismatch between host name " + hostname
+ + " and subject DN: "
+ + chain[0].getSubjectX500Principal(), hostname,
+ chain[0].getSubjectX500Principal().getName());
+ }
+ }
+ catch (Throwable t)
+ {
+ LOG.log(Level.WARNING, "Error parsing subject dn: "
+ + chain[0].getSubjectX500Principal(), t);
+ }
+ }
+ }
+
+
+
+ /**
+ * Checks whether two host names match. It accepts the use of wildcard
+ * in the host name.
+ *
+ * @param host1
+ * the first host name.
+ * @param host2
+ * the second host name.
+ * @return <CODE>true</CODE> if the host match and <CODE>false</CODE>
+ * otherwise.
+ */
+ private boolean hostMatch(String host1, String host2)
+ {
+ if (host1 == null)
+ {
+ throw new IllegalArgumentException(
+ "The host1 parameter cannot be null");
+ }
+ if (host2 == null)
+ {
+ throw new IllegalArgumentException(
+ "The host2 parameter cannot be null");
+ }
+ String[] h1 = host1.split("\\.");
+ String[] h2 = host2.split("\\.");
+
+ boolean hostMatch = h1.length == h2.length;
+ for (int i = 0; i < h1.length && hostMatch; i++)
+ {
+ if (!h1[i].equals("*") && !h2[i].equals("*"))
+ {
+ hostMatch = h1[i].equalsIgnoreCase(h2[i]);
+ }
+ }
+ return hostMatch;
+ }
+}
--
Gitblit v1.10.0