initial commit
This commit is contained in:
commit
bdb39ae448
529 changed files with 69284 additions and 0 deletions
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
/.settings
|
||||
/.classpath
|
||||
/.project
|
||||
/.gradle
|
||||
**/data
|
||||
**/work
|
||||
**/logs
|
||||
**/.idea
|
||||
**/target
|
||||
**/out
|
||||
**/build
|
||||
.DS_Store
|
||||
*.iml
|
||||
*~
|
||||
*.key
|
||||
*.crt
|
202
LICENSE.txt
Normal file
202
LICENSE.txt
Normal file
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
3
asn1/src/main/java/module-info.java
Normal file
3
asn1/src/main/java/module-info.java
Normal file
|
@ -0,0 +1,3 @@
|
|||
module org.xbib.net.ldap.asnone {
|
||||
exports org.xbib.asn1;
|
||||
}
|
55
asn1/src/main/java/org/xbib/asn1/AbstractDERTag.java
Normal file
55
asn1/src/main/java/org/xbib/asn1/AbstractDERTag.java
Normal file
|
@ -0,0 +1,55 @@
|
|||
|
||||
package org.xbib.asn1;
|
||||
|
||||
/**
|
||||
* Abstract base class for custom DER tag types.
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractDERTag implements DERTag {
|
||||
|
||||
/**
|
||||
* Tag number.
|
||||
*/
|
||||
private final int tagNo;
|
||||
|
||||
/**
|
||||
* Flag indicating whether value is primitive or constructed.
|
||||
*/
|
||||
private final boolean constructed;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new tag with given tag number.
|
||||
*
|
||||
* @param number Tag number.
|
||||
* @param isConstructed True for constructed tag, false otherwise.
|
||||
*/
|
||||
public AbstractDERTag(final int number, final boolean isConstructed) {
|
||||
tagNo = number;
|
||||
constructed = isConstructed;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getTagNo() {
|
||||
return tagNo;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isConstructed() {
|
||||
return constructed;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getTagByte() {
|
||||
return constructed ? tagNo | ASN_CONSTRUCTED : tagNo;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name() + "(" + tagNo + ")";
|
||||
}
|
||||
}
|
81
asn1/src/main/java/org/xbib/asn1/AbstractDERType.java
Normal file
81
asn1/src/main/java/org/xbib/asn1/AbstractDERType.java
Normal file
|
@ -0,0 +1,81 @@
|
|||
|
||||
package org.xbib.asn1;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Provides functionality common to DER types implementations.
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractDERType {
|
||||
|
||||
/**
|
||||
* Length of short form integers.
|
||||
*/
|
||||
private static final int SHORT_FORM_INT_LENGTH = 127;
|
||||
|
||||
/**
|
||||
* Constructed tag.
|
||||
*/
|
||||
private final int derTag;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new abstract der type.
|
||||
*
|
||||
* @param tag to encode for this type
|
||||
*/
|
||||
public AbstractDERType(final DERTag tag) {
|
||||
derTag = tag.getTagByte();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* DER encodes the supplied items with the tag associated with this type. If the length is greater than 127 bytes the
|
||||
* long form is always expressed using 4 bytes.
|
||||
*
|
||||
* @param items to encode
|
||||
* @return DER encoded items
|
||||
*/
|
||||
protected byte[] encode(final byte[]... items) {
|
||||
int itemLength = 0;
|
||||
if (items != null) {
|
||||
for (byte[] b : items) {
|
||||
if (b != null) {
|
||||
itemLength += b.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final byte[] lengthBytes;
|
||||
if (itemLength <= SHORT_FORM_INT_LENGTH) {
|
||||
lengthBytes = new byte[]{(byte) itemLength};
|
||||
} else {
|
||||
// use 4 bytes for all long form integers
|
||||
// CheckStyle:MagicNumber OFF
|
||||
lengthBytes = new byte[]{
|
||||
(byte) 0x84,
|
||||
(byte) (itemLength >>> 24),
|
||||
(byte) (itemLength >>> 16),
|
||||
(byte) (itemLength >>> 8),
|
||||
(byte) itemLength,
|
||||
};
|
||||
// CheckStyle:MagicNumber ON
|
||||
}
|
||||
|
||||
// add 1 for the type tag, 1 or 5 for the length
|
||||
final ByteBuffer encodedItem = ByteBuffer.allocate(itemLength + 1 + lengthBytes.length);
|
||||
encodedItem.put((byte) derTag);
|
||||
for (byte b : lengthBytes) {
|
||||
encodedItem.put(b);
|
||||
}
|
||||
if (items != null) {
|
||||
for (byte[] b : items) {
|
||||
if (b != null) {
|
||||
encodedItem.put(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
return encodedItem.array();
|
||||
}
|
||||
}
|
35
asn1/src/main/java/org/xbib/asn1/AbstractParseHandler.java
Normal file
35
asn1/src/main/java/org/xbib/asn1/AbstractParseHandler.java
Normal file
|
@ -0,0 +1,35 @@
|
|||
|
||||
package org.xbib.asn1;
|
||||
|
||||
/**
|
||||
* Parse handler for managing and initializing an object.
|
||||
*
|
||||
* @param <T> type of object initialized by this handler
|
||||
*/
|
||||
public abstract class AbstractParseHandler<T> implements ParseHandler {
|
||||
|
||||
/**
|
||||
* Object to initialize.
|
||||
*/
|
||||
private final T object;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new abstract parse handler.
|
||||
*
|
||||
* @param t object to initialize
|
||||
*/
|
||||
public AbstractParseHandler(final T t) {
|
||||
object = t;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the object.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public T getObject() {
|
||||
return object;
|
||||
}
|
||||
}
|
48
asn1/src/main/java/org/xbib/asn1/ApplicationDERTag.java
Normal file
48
asn1/src/main/java/org/xbib/asn1/ApplicationDERTag.java
Normal file
|
@ -0,0 +1,48 @@
|
|||
|
||||
package org.xbib.asn1;
|
||||
|
||||
/**
|
||||
* Generic application-specific tag.
|
||||
*
|
||||
*/
|
||||
public class ApplicationDERTag extends AbstractDERTag {
|
||||
|
||||
/**
|
||||
* Generic tag name "APP" for an application-specific type.
|
||||
*/
|
||||
public static final String TAG_NAME = "APP";
|
||||
|
||||
/**
|
||||
* Application class is 01b in first two high-order bits.
|
||||
*/
|
||||
public static final int TAG_CLASS = 0x40;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new application-specific tag with given tag number.
|
||||
*
|
||||
* @param number Tag number.
|
||||
* @param isConstructed True for constructed tag, false otherwise.
|
||||
*/
|
||||
public ApplicationDERTag(final int number, final boolean isConstructed) {
|
||||
super(number, isConstructed);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getTagByte() {
|
||||
return super.getTagByte() | TAG_CLASS;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return TAG_NAME + "(" + getTagNo() + ")";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name();
|
||||
}
|
||||
}
|
87
asn1/src/main/java/org/xbib/asn1/BooleanType.java
Normal file
87
asn1/src/main/java/org/xbib/asn1/BooleanType.java
Normal file
|
@ -0,0 +1,87 @@
|
|||
|
||||
package org.xbib.asn1;
|
||||
|
||||
/**
|
||||
* Converts booleans to and from their DER encoded format.
|
||||
*
|
||||
*/
|
||||
public class BooleanType extends AbstractDERType implements DEREncoder {
|
||||
|
||||
/**
|
||||
* Boolean true byte representation.
|
||||
*/
|
||||
private static final byte TRUE_BYTE = (byte) 0xff;
|
||||
|
||||
/**
|
||||
* Boolean false byte representation.
|
||||
*/
|
||||
private static final byte FALSE_BYTE = (byte) 0x00;
|
||||
|
||||
/**
|
||||
* Boolean to encode.
|
||||
*/
|
||||
private final byte[] derItem;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new boolean type.
|
||||
*
|
||||
* @param item to DER encode
|
||||
*/
|
||||
public BooleanType(final boolean item) {
|
||||
super(UniversalDERTag.BOOL);
|
||||
derItem = toBytes(item);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new boolean type.
|
||||
*
|
||||
* @param tag der tag associated with this type
|
||||
* @param item to DER encode
|
||||
* @throws IllegalArgumentException if the der tag is constructed
|
||||
*/
|
||||
public BooleanType(final DERTag tag, final boolean item) {
|
||||
super(tag);
|
||||
if (tag.isConstructed()) {
|
||||
throw new IllegalArgumentException("DER tag must not be constructed");
|
||||
}
|
||||
derItem = toBytes(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts bytes in the buffer to a boolean by reading from the current position to the limit.
|
||||
*
|
||||
* @param encoded buffer containing DER-encoded data where the buffer is positioned at the start of boolean bytes
|
||||
* and the limit is set beyond the last byte of integer data.
|
||||
* @return decoded bytes as a boolean.
|
||||
*/
|
||||
public static boolean decode(final DERBuffer encoded) {
|
||||
final byte[] bytes = encoded.getRemainingBytes();
|
||||
if (bytes.length > 1) {
|
||||
throw new IllegalArgumentException("Boolean cannot be longer than 1 byte");
|
||||
}
|
||||
if (bytes[0] == TRUE_BYTE) {
|
||||
return true;
|
||||
} else if (bytes[0] == FALSE_BYTE) {
|
||||
return false;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid boolean value: " + (int) bytes[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the supplied boolean to a byte array.
|
||||
*
|
||||
* @param b to convert
|
||||
* @return byte array
|
||||
*/
|
||||
public static byte[] toBytes(final boolean b) {
|
||||
return new byte[]{b ? TRUE_BYTE : FALSE_BYTE};
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encode() {
|
||||
return encode(derItem);
|
||||
}
|
||||
}
|
51
asn1/src/main/java/org/xbib/asn1/ConstructedDEREncoder.java
Normal file
51
asn1/src/main/java/org/xbib/asn1/ConstructedDEREncoder.java
Normal file
|
@ -0,0 +1,51 @@
|
|||
|
||||
package org.xbib.asn1;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Encodes constructed types to their DER format.
|
||||
*
|
||||
*/
|
||||
public class ConstructedDEREncoder extends AbstractDERType implements DEREncoder {
|
||||
|
||||
/**
|
||||
* Encoders in this sequence.
|
||||
*/
|
||||
private final DEREncoder[] derEncoders;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new sequence encoder.
|
||||
*
|
||||
* @param tag der tag associated with this type
|
||||
* @param encoders to encode in this sequence
|
||||
*/
|
||||
public ConstructedDEREncoder(final DERTag tag, final DEREncoder... encoders) {
|
||||
super(tag);
|
||||
if (!tag.isConstructed()) {
|
||||
throw new IllegalArgumentException("DER tag must be constructed");
|
||||
}
|
||||
if (encoders == null || encoders.length == 0) {
|
||||
throw new IllegalArgumentException("Encoders cannot be null or empty");
|
||||
}
|
||||
derEncoders = encoders;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte[] encode() {
|
||||
final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
try {
|
||||
try (bytes) {
|
||||
for (DEREncoder encoder : derEncoders) {
|
||||
bytes.write(encoder.encode());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Encode failed", e);
|
||||
}
|
||||
return encode(bytes.toByteArray());
|
||||
}
|
||||
}
|
42
asn1/src/main/java/org/xbib/asn1/ContextDERTag.java
Normal file
42
asn1/src/main/java/org/xbib/asn1/ContextDERTag.java
Normal file
|
@ -0,0 +1,42 @@
|
|||
|
||||
package org.xbib.asn1;
|
||||
|
||||
/**
|
||||
* Generic context-specific tag.
|
||||
*
|
||||
*/
|
||||
public class ContextDERTag extends AbstractDERTag {
|
||||
|
||||
/**
|
||||
* Generic tag name "CTX" for a context-specific type.
|
||||
*/
|
||||
public static final String TAG_NAME = "CTX";
|
||||
|
||||
/**
|
||||
* Context-specific class is 10b in first two high-order bits.
|
||||
*/
|
||||
public static final int TAG_CLASS = 0x80;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new context-specific tag with given tag number.
|
||||
*
|
||||
* @param number Tag number.
|
||||
* @param isConstructed True for constructed tag, false otherwise.
|
||||
*/
|
||||
public ContextDERTag(final int number, final boolean isConstructed) {
|
||||
super(number, isConstructed);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getTagByte() {
|
||||
return super.getTagByte() | TAG_CLASS;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return TAG_NAME + "(" + getTagNo() + ")";
|
||||
}
|
||||
}
|
67
asn1/src/main/java/org/xbib/asn1/ContextType.java
Normal file
67
asn1/src/main/java/org/xbib/asn1/ContextType.java
Normal file
|
@ -0,0 +1,67 @@
|
|||
|
||||
package org.xbib.asn1;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* Converts context types to their DER encoded format.
|
||||
*
|
||||
*/
|
||||
public class ContextType extends AbstractDERType implements DEREncoder {
|
||||
|
||||
/**
|
||||
* Data to encode.
|
||||
*/
|
||||
private final byte[] derItem;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new context type.
|
||||
*
|
||||
* @param index of this item in the context
|
||||
* @param item to encode
|
||||
*/
|
||||
public ContextType(final int index, final byte[] item) {
|
||||
super(new ContextDERTag(index, false));
|
||||
derItem = item;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new context type.
|
||||
*
|
||||
* @param index of this item in the context
|
||||
* @param item to encode
|
||||
*/
|
||||
public ContextType(final int index, final String item) {
|
||||
this(index, OctetStringType.toBytes(item));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new context type.
|
||||
*
|
||||
* @param index of this item in the context
|
||||
* @param item to encode
|
||||
*/
|
||||
public ContextType(final int index, final boolean item) {
|
||||
this(index, BooleanType.toBytes(item));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new context type.
|
||||
*
|
||||
* @param index of this item in the context
|
||||
* @param item to encode
|
||||
*/
|
||||
public ContextType(final int index, final BigInteger item) {
|
||||
this(index, IntegerType.toBytes(item));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte[] encode() {
|
||||
return encode(derItem);
|
||||
}
|
||||
}
|
40
asn1/src/main/java/org/xbib/asn1/CustomDERTag.java
Normal file
40
asn1/src/main/java/org/xbib/asn1/CustomDERTag.java
Normal file
|
@ -0,0 +1,40 @@
|
|||
|
||||
package org.xbib.asn1;
|
||||
|
||||
/**
|
||||
* Describes the tag of an application-specific, context-specific, or private DER type where the tag name may be
|
||||
* specified for clarity in application code.
|
||||
*
|
||||
*/
|
||||
public class CustomDERTag extends AbstractDERTag {
|
||||
|
||||
/**
|
||||
* Tag name.
|
||||
*/
|
||||
private final String tagName;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new custom DER tag.
|
||||
*
|
||||
* @param number of the tag
|
||||
* @param name of the tag
|
||||
* @param isConstructed whether this tag is primitive or constructed
|
||||
*/
|
||||
public CustomDERTag(final int number, final String name, final boolean isConstructed) {
|
||||
super(number, isConstructed);
|
||||
tagName = name;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return tagName;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name() + "(" + getTagNo() + ")";
|
||||
}
|
||||
}
|
128
asn1/src/main/java/org/xbib/asn1/DERBuffer.java
Normal file
128
asn1/src/main/java/org/xbib/asn1/DERBuffer.java
Normal file
|
@ -0,0 +1,128 @@
|
|||
|
||||
package org.xbib.asn1;
|
||||
|
||||
/**
|
||||
* Byte buffer used for DER parsing.
|
||||
*
|
||||
*/
|
||||
public interface DERBuffer {
|
||||
|
||||
|
||||
/**
|
||||
* Returns this buffer's position.
|
||||
*
|
||||
* @return position of this buffer
|
||||
*/
|
||||
int position();
|
||||
|
||||
|
||||
/**
|
||||
* Sets this buffer's position.
|
||||
*
|
||||
* @param newPosition The new position value; must be non-negative
|
||||
* and no larger than the current limit
|
||||
* @return This buffer
|
||||
* @throws IllegalArgumentException if the preconditions on newPosition do not hold
|
||||
*/
|
||||
DERBuffer position(int newPosition);
|
||||
|
||||
|
||||
/**
|
||||
* Returns this buffer's limit.
|
||||
*
|
||||
* @return limit of this buffer
|
||||
*/
|
||||
int limit();
|
||||
|
||||
|
||||
/**
|
||||
* Sets this buffer's limit.
|
||||
*
|
||||
* @param newLimit The new limit value; must be non-negative
|
||||
* and no larger than this buffer's capacity
|
||||
* @return This buffer
|
||||
* @throws IllegalArgumentException if the preconditions on newLimit do not hold
|
||||
*/
|
||||
DERBuffer limit(int newLimit);
|
||||
|
||||
|
||||
/**
|
||||
* Sets the position to zero and the limit to the capacity.
|
||||
*
|
||||
* <p>This method does not actually erase the data in the buffer.</p>
|
||||
*
|
||||
* @return This buffer
|
||||
*/
|
||||
DERBuffer clear();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of elements between the current position and the limit.
|
||||
*
|
||||
* @return number of elements remaining in this buffer
|
||||
*/
|
||||
default int remaining() {
|
||||
return limit() - position();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether there are any elements between the current position and the limit.
|
||||
*
|
||||
* @return true iff there is at least one element remaining in this buffer
|
||||
*/
|
||||
default boolean hasRemaining() {
|
||||
return position() < limit();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns this buffer's capacity.
|
||||
*
|
||||
* @return capacity of this buffer
|
||||
*/
|
||||
int capacity();
|
||||
|
||||
|
||||
/**
|
||||
* Relative <i>get</i> method. Reads the byte at this buffer's current position and then increments the position.
|
||||
*
|
||||
* @return byte at the buffer's current position
|
||||
*/
|
||||
byte get();
|
||||
|
||||
|
||||
/**
|
||||
* Relative bulk <i>get</i> method.
|
||||
*
|
||||
* @param dst destination array
|
||||
* @return This buffer
|
||||
*/
|
||||
DERBuffer get(byte[] dst);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the bytes remaining in the buffer. Those bytes between {@link #position()} and {@link #limit()}.
|
||||
*
|
||||
* @return remaining bytes
|
||||
*/
|
||||
default byte[] getRemainingBytes() {
|
||||
final byte[] b = new byte[remaining()];
|
||||
get(b);
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new DER buffer whose content is a shared sub-sequence of this buffer's content.
|
||||
*
|
||||
* <p>The content of the new buffer will start at this buffer's current position. Changes to this buffer's content
|
||||
* will be visible in the new buffer, and vice versa; the two buffers' position and limit will be independent.</p>
|
||||
*
|
||||
* <p>The new buffer's position will be zero, its capacity and its limit will be the number of bytes remaining in this
|
||||
* buffer.</p>
|
||||
*
|
||||
* @return The new byte buffer
|
||||
*/
|
||||
DERBuffer slice();
|
||||
}
|
17
asn1/src/main/java/org/xbib/asn1/DEREncoder.java
Normal file
17
asn1/src/main/java/org/xbib/asn1/DEREncoder.java
Normal file
|
@ -0,0 +1,17 @@
|
|||
|
||||
package org.xbib.asn1;
|
||||
|
||||
/**
|
||||
* Interface for encoding DER objects.
|
||||
*
|
||||
*/
|
||||
public interface DEREncoder {
|
||||
|
||||
|
||||
/**
|
||||
* Encode this object into its DER type.
|
||||
*
|
||||
* @return DER encoded object
|
||||
*/
|
||||
byte[] encode();
|
||||
}
|
213
asn1/src/main/java/org/xbib/asn1/DERParser.java
Normal file
213
asn1/src/main/java/org/xbib/asn1/DERParser.java
Normal file
|
@ -0,0 +1,213 @@
|
|||
|
||||
package org.xbib.asn1;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
|
||||
/**
|
||||
* This class provides a SAX-like parsing facility for DER-encoded data where elements of interest in the parse tree may
|
||||
* be registered to handlers via the {@link #registerHandler} methods. {@link DERPath} strings are used to map handlers
|
||||
* to elements of interest.
|
||||
*/
|
||||
public class DERParser {
|
||||
|
||||
/**
|
||||
* Handlers for DER paths.
|
||||
*/
|
||||
private final Map<DERPath, ParseHandler> handlerMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Permutations of the current path.
|
||||
*/
|
||||
private final Queue<DERPath> permutations = new ArrayDeque<>();
|
||||
|
||||
public DERParser() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the supplied handler to fire when the supplied path is encountered.
|
||||
*
|
||||
* @param path to register
|
||||
* @param handler to associate with the path
|
||||
*/
|
||||
public void registerHandler(final DERPath path, final ParseHandler handler) {
|
||||
handlerMap.put(path, handler);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse a DER-encoded data structure by calling registered handlers when points of interest are encountered in the
|
||||
* parse tree.
|
||||
*
|
||||
* @param encoded DER-encoded bytes.
|
||||
*/
|
||||
public void parse(final DERBuffer encoded) {
|
||||
parseTags(encoded);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads a DER tag from a single byte at the current position of the given buffer. The buffer position is naturally
|
||||
* advanced one byte in this operation.
|
||||
*
|
||||
* @param encoded Buffer containing DER-encoded bytes positioned at tag.
|
||||
* @return Tag or null if no universal tag or application-specific tag is known that matches the byte read in.
|
||||
*/
|
||||
public DERTag readTag(final DERBuffer encoded) {
|
||||
if (encoded.position() >= encoded.limit()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final DERTag tag;
|
||||
final byte b = encoded.get();
|
||||
// CheckStyle:MagicNumber OFF
|
||||
final int tagNo = b & 0x1F;
|
||||
final boolean constructed = (b & 0x20) == 0x20;
|
||||
// Read class from first two high-order bits
|
||||
switch (b & 0xC0) {
|
||||
|
||||
case UniversalDERTag.TAG_CLASS:
|
||||
tag = UniversalDERTag.fromTagNo(tagNo);
|
||||
break;
|
||||
|
||||
case ApplicationDERTag.TAG_CLASS:
|
||||
tag = new ApplicationDERTag(tagNo, constructed);
|
||||
break;
|
||||
|
||||
case ContextDERTag.TAG_CLASS:
|
||||
tag = new ContextDERTag(tagNo, constructed);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Private class (class 11b)
|
||||
throw new IllegalArgumentException("Private classes not supported.");
|
||||
}
|
||||
// CheckStyle:MagicNumber ON
|
||||
return tag;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads the length of a DER-encoded value from the given byte buffer. The buffer is expected to be positioned at the
|
||||
* byte immediately following the tag byte, which is where the length byte(s) begin(s). Invocation of this method has
|
||||
* two generally beneficial side effects:
|
||||
*
|
||||
* <ol>
|
||||
* <li>Buffer is positioned at <em>start</em> of value bytes.</li>
|
||||
* <li>Buffer limit is set to the <em>end</em> of value bytes.</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param encoded buffer containing DER-encoded bytes positioned at start of length byte(s).
|
||||
* @return number of bytes occupied by tag value.
|
||||
*/
|
||||
public int readLength(final DERBuffer encoded) {
|
||||
int length = 0;
|
||||
final byte b = encoded.get();
|
||||
// CheckStyle:MagicNumber OFF
|
||||
if ((b & 0x80) == 0x80) {
|
||||
final int len = b & 0x7F;
|
||||
if (len > 0) {
|
||||
final int limit = encoded.limit();
|
||||
encoded.limit(encoded.position() + len);
|
||||
length = IntegerType.decodeUnsignedPrimitive(encoded);
|
||||
encoded.limit(limit);
|
||||
}
|
||||
} else {
|
||||
length = b;
|
||||
}
|
||||
// CheckStyle:MagicNumber ON
|
||||
return length;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads the supplied DER encoded bytes and invokes handlers as configured paths are encountered.
|
||||
*
|
||||
* @param encoded to parse
|
||||
*/
|
||||
private void parseTags(final DERBuffer encoded) {
|
||||
int index = 0;
|
||||
while (encoded.position() < encoded.limit() && !handlerMap.isEmpty()) {
|
||||
final DERTag tag = readTag(encoded);
|
||||
if (tag != null) {
|
||||
addTag(tag, index++);
|
||||
parseTag(tag, encoded);
|
||||
removeTag();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invokes the parse handler for the current path and advances to the next position in the encoded bytes.
|
||||
*
|
||||
* @param tag to inspect for internal tags
|
||||
* @param encoded to parse
|
||||
*/
|
||||
private void parseTag(final DERTag tag, final DERBuffer encoded) {
|
||||
final int limit = encoded.limit();
|
||||
final int end = readLength(encoded) + encoded.position();
|
||||
final int start = encoded.position();
|
||||
|
||||
// Invoke handlers for all permutations of current path
|
||||
ParseHandler handler;
|
||||
for (DERPath p : permutations) {
|
||||
handler = handlerMap.get(p);
|
||||
if (handler != null) {
|
||||
encoded.limit(end).position(start);
|
||||
handler.handle(this, encoded);
|
||||
}
|
||||
}
|
||||
|
||||
if (tag.isConstructed()) {
|
||||
parseTags(encoded);
|
||||
}
|
||||
encoded.limit(limit).position(end);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add the given tag at the specified index to all permutations of the current parser path and increases the number of
|
||||
* permutations as necessary to satisfy the following relation:
|
||||
*
|
||||
* <pre>size = 2^n</pre>
|
||||
*
|
||||
* <p>where n is the path length.</p>
|
||||
*
|
||||
* @param tag to add to path.
|
||||
* @param index of tag relative to parent.
|
||||
*/
|
||||
private void addTag(final DERTag tag, final int index) {
|
||||
if (permutations.isEmpty()) {
|
||||
permutations.add(new DERPath().pushNode(tag.name()));
|
||||
permutations.add(new DERPath().pushNode(tag.name(), index));
|
||||
} else {
|
||||
final Collection<DERPath> generation = new ArrayDeque<>(permutations.size());
|
||||
for (DERPath p : permutations) {
|
||||
generation.add(new DERPath(p).pushNode(tag.name()));
|
||||
p.pushNode(tag.name(), index);
|
||||
}
|
||||
permutations.addAll(generation);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes the tag at the leaf position of all permutations of the current parser path, and reduces the number of
|
||||
* permutations as necessary to satisfy the following relation:
|
||||
*
|
||||
* <pre>size = 2^n</pre>
|
||||
*
|
||||
* <p>where n is the path length.</p>
|
||||
*/
|
||||
private void removeTag() {
|
||||
final int half = permutations.size() / 2;
|
||||
while (permutations.size() > half) {
|
||||
permutations.remove();
|
||||
}
|
||||
permutations.forEach(DERPath::popNode);
|
||||
}
|
||||
}
|
353
asn1/src/main/java/org/xbib/asn1/DERPath.java
Normal file
353
asn1/src/main/java/org/xbib/asn1/DERPath.java
Normal file
|
@ -0,0 +1,353 @@
|
|||
|
||||
package org.xbib.asn1;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Describes paths to individual elements of an encoded DER object that may be addressed during parsing to associate a
|
||||
* parsed element with a handler to handle that element. Consider the following production rule for a complex type that
|
||||
* may be DER encoded:
|
||||
*
|
||||
* <pre>
|
||||
*
|
||||
* BankAccountSet ::= SET OF {
|
||||
* account BankAccount
|
||||
* }
|
||||
*
|
||||
* BankAccount ::= SEQUENCE OF {
|
||||
* accountNumber OCTET STRING,
|
||||
* accountName OCTET STRING,
|
||||
* accountType AccountType,
|
||||
* balance REAL
|
||||
* }
|
||||
*
|
||||
* AccountType ::= ENUM {
|
||||
* checking (0),
|
||||
* savings (1)
|
||||
* }
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* <p>Given an instance of BankAccountSet with two elements, the path to the balance of each bank account in the set is
|
||||
* given by the following expression:</p>
|
||||
*
|
||||
* <pre>/SET/SEQ/REAL</pre>
|
||||
*
|
||||
* <p>Individual child elements can be accessed by explicitly mentioning the index of the item relative to its parent.
|
||||
* For example, the second bank account in the set can be accessed as follows:</p>
|
||||
*
|
||||
* <pre>/SET/SEQ[1]</pre>
|
||||
*
|
||||
* <p>Node names in DER paths are constrained to the following:</p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link UniversalDERTag} tag names</li>
|
||||
* <li>{@link ApplicationDERTag#TAG_NAME}</li>
|
||||
* <li>{@link ContextDERTag#TAG_NAME}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @see DERParser
|
||||
*/
|
||||
public class DERPath {
|
||||
|
||||
/**
|
||||
* Separates nodes in a path specification.
|
||||
*/
|
||||
public static final String PATH_SEPARATOR = "/";
|
||||
|
||||
/**
|
||||
* General pattern for DER path nodes.
|
||||
*/
|
||||
private static final Pattern NODE_PATTERN;
|
||||
|
||||
/**
|
||||
* hash code seed.
|
||||
*/
|
||||
private static final int HASH_CODE_SEED = 601;
|
||||
|
||||
static {
|
||||
final StringBuilder validNames = new StringBuilder();
|
||||
validNames.append(ApplicationDERTag.TAG_NAME).append("\\(\\d+\\)|");
|
||||
validNames.append(ContextDERTag.TAG_NAME).append("\\(\\d+\\)|");
|
||||
for (UniversalDERTag tag : UniversalDERTag.values()) {
|
||||
validNames.append('|').append(tag.name());
|
||||
}
|
||||
NODE_PATTERN = Pattern.compile(String.format("(%s)(?:\\[(\\d+)\\])?", validNames));
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the path as a FIFO set of nodes.
|
||||
*/
|
||||
private final Deque<Node> nodeStack = new ArrayDeque<>();
|
||||
|
||||
|
||||
/**
|
||||
* Creates an empty path specification.
|
||||
*/
|
||||
public DERPath() {
|
||||
this(PATH_SEPARATOR);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copy constructor.
|
||||
*
|
||||
* @param path to read nodes from
|
||||
*/
|
||||
public DERPath(final DERPath path) {
|
||||
nodeStack.addAll(path.nodeStack);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a path specification from its string representation.
|
||||
*
|
||||
* @param pathSpec string representation of a path, e.g. /SEQ[1]/CHOICE.
|
||||
*/
|
||||
public DERPath(final String pathSpec) {
|
||||
final String[] nodes = pathSpec.split(PATH_SEPARATOR);
|
||||
for (String node : nodes) {
|
||||
if ("".equals(node)) {
|
||||
continue;
|
||||
}
|
||||
// Normalize node names to upper case
|
||||
nodeStack.add(toNode(LdapUtils.toUpperCaseAscii(node)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string representation of a node into a {@link Node} object.
|
||||
*
|
||||
* @param node String representation of node.
|
||||
* @return Node corresponding to given string representation.
|
||||
* @throws IllegalArgumentException for an invalid node name.
|
||||
*/
|
||||
static Node toNode(final String node) {
|
||||
final Matcher matcher = NODE_PATTERN.matcher(node);
|
||||
if (!matcher.matches()) {
|
||||
throw new IllegalArgumentException("Invalid node: " + node);
|
||||
}
|
||||
|
||||
final String name = matcher.group(1);
|
||||
final String index = matcher.group(2);
|
||||
if (index != null) {
|
||||
return new Node(name, Integer.parseInt(index));
|
||||
}
|
||||
return new Node(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a node to the path.
|
||||
*
|
||||
* @param name of the path element to add
|
||||
* @return This instance with new node appended.
|
||||
*/
|
||||
public DERPath pushNode(final String name) {
|
||||
nodeStack.addLast(new Node(name));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a node to the path with the given child index.
|
||||
*
|
||||
* @param name of the path element to add
|
||||
* @param index child index
|
||||
* @return This instance with new node appended.
|
||||
*/
|
||||
public DERPath pushNode(final String name, final int index) {
|
||||
nodeStack.addLast(new Node(name, index));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Examines the first node in the path without removing it.
|
||||
*
|
||||
* @return first node in the path or null if no nodes remain
|
||||
*/
|
||||
public String peekNode() {
|
||||
return nodeStack.peek().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the last node in the path.
|
||||
*
|
||||
* @return last node in the path or null if no more nodes remain.
|
||||
*/
|
||||
public String popNode() {
|
||||
if (nodeStack.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return nodeStack.removeLast().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of nodes in the path.
|
||||
*
|
||||
* @return node count.
|
||||
*/
|
||||
public int getSize() {
|
||||
return nodeStack.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the path contains any nodes.
|
||||
*
|
||||
* @return True if path contains 0 nodes, false otherwise.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return nodeStack.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
if (o instanceof DERPath v) {
|
||||
return LdapUtils.areEqual(
|
||||
nodeStack != null ? nodeStack.toArray() : null,
|
||||
v.nodeStack != null ? v.nodeStack.toArray() : null);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hc = HASH_CODE_SEED;
|
||||
if (nodeStack != null && !nodeStack.isEmpty()) {
|
||||
for (Node n : nodeStack) {
|
||||
hc = HASH_CODE_SEED * hc + n.hashCode();
|
||||
}
|
||||
}
|
||||
return hc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (nodeStack == null) {
|
||||
return "";
|
||||
}
|
||||
final StringBuilder sb = new StringBuilder(nodeStack.size() * 10);
|
||||
for (Node node : nodeStack) {
|
||||
sb.append(PATH_SEPARATOR);
|
||||
node.toString(sb);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* DER path node encapsulates the path name and its location among other children that share a common parent.
|
||||
*
|
||||
*/
|
||||
static class Node {
|
||||
|
||||
/**
|
||||
* hash code seed.
|
||||
*/
|
||||
private static final int HASH_CODE_SEED = 607;
|
||||
|
||||
/**
|
||||
* Name of this node.
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Index of this node.
|
||||
*/
|
||||
private final int childIndex;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new node with an indeterminate index.
|
||||
*
|
||||
* @param n name of this node
|
||||
*/
|
||||
Node(final String n) {
|
||||
name = n;
|
||||
childIndex = -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new node with the given index.
|
||||
*
|
||||
* @param n name of this node
|
||||
* @param i child index location of this node in the path
|
||||
*/
|
||||
Node(final String n, final int i) {
|
||||
if (i < 0) {
|
||||
throw new IllegalArgumentException("Child index cannot be negative.");
|
||||
}
|
||||
name = n;
|
||||
childIndex = i;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the name.
|
||||
*
|
||||
* @return name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the child index.
|
||||
*
|
||||
* @return child index
|
||||
*/
|
||||
public int getChildIndex() {
|
||||
return childIndex;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
if (o instanceof Node v) {
|
||||
return LdapUtils.areEqual(name, v.name) &&
|
||||
LdapUtils.areEqual(childIndex, v.childIndex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = HASH_CODE_SEED + (name == null ? 0 : name.hashCode());
|
||||
result = HASH_CODE_SEED * result + childIndex;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// CheckStyle:MagicNumber OFF
|
||||
final StringBuilder sb = new StringBuilder(name.length() + 4);
|
||||
// CheckStyle:MagicNumber ON
|
||||
toString(sb);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Appends the string representation of this instance to the given string builder.
|
||||
*
|
||||
* @param builder Builder to hold string representation of this instance.
|
||||
*/
|
||||
public void toString(final StringBuilder builder) {
|
||||
builder.append(name);
|
||||
if (childIndex < 0) {
|
||||
return;
|
||||
}
|
||||
builder.append('[').append(childIndex).append(']');
|
||||
}
|
||||
}
|
||||
}
|
46
asn1/src/main/java/org/xbib/asn1/DERTag.java
Normal file
46
asn1/src/main/java/org/xbib/asn1/DERTag.java
Normal file
|
@ -0,0 +1,46 @@
|
|||
|
||||
package org.xbib.asn1;
|
||||
|
||||
/**
|
||||
* Describes the tag of a DER-encoded type.
|
||||
*
|
||||
*/
|
||||
public interface DERTag {
|
||||
|
||||
/**
|
||||
* Constructed tags should have the 6th bit set.
|
||||
*/
|
||||
int ASN_CONSTRUCTED = 0x20;
|
||||
|
||||
|
||||
/**
|
||||
* Gets the decimal value of the tag.
|
||||
*
|
||||
* @return decimal tag number.
|
||||
*/
|
||||
int getTagNo();
|
||||
|
||||
|
||||
/**
|
||||
* Gets the name of the tag.
|
||||
*
|
||||
* @return tag name.
|
||||
*/
|
||||
String name();
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether the tag is constructed or primitive.
|
||||
*
|
||||
* @return true if constructed, false if primitive.
|
||||
*/
|
||||
boolean isConstructed();
|
||||
|
||||
|
||||
/**
|
||||
* Gets the value of this tag for encoding.
|
||||
*
|
||||
* @return byte value of this tag
|
||||
*/
|
||||
int getTagByte();
|
||||
}
|
127
asn1/src/main/java/org/xbib/asn1/DefaultDERBuffer.java
Normal file
127
asn1/src/main/java/org/xbib/asn1/DefaultDERBuffer.java
Normal file
|
@ -0,0 +1,127 @@
|
|||
|
||||
package org.xbib.asn1;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* {@link DERBuffer} that uses a {@link ByteBuffer}.
|
||||
*
|
||||
*/
|
||||
public class DefaultDERBuffer implements DERBuffer {
|
||||
|
||||
/**
|
||||
* Underlying byte buffer.
|
||||
*/
|
||||
private final ByteBuffer buffer;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new default DER buffer. See {@link ByteBuffer#allocate(int)}.
|
||||
*
|
||||
* @param capacity of this buffer
|
||||
*/
|
||||
public DefaultDERBuffer(final int capacity) {
|
||||
buffer = ByteBuffer.allocate(capacity);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new default DER buffer. See {@link ByteBuffer#wrap(byte[])}.
|
||||
*
|
||||
* @param array contents of the buffer
|
||||
*/
|
||||
public DefaultDERBuffer(final byte[] array) {
|
||||
buffer = ByteBuffer.wrap(array);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new default DER buffer.
|
||||
*
|
||||
* @param buf existing byte buffer
|
||||
*/
|
||||
public DefaultDERBuffer(final ByteBuffer buf) {
|
||||
buffer = buf;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new default DER buffer and sets the initial position and limit.
|
||||
*
|
||||
* @param buf existing byte buffer
|
||||
* @param pos initial buffer position
|
||||
* @param lim initial buffer limit
|
||||
*/
|
||||
public DefaultDERBuffer(final ByteBuffer buf, final int pos, final int lim) {
|
||||
buffer = buf;
|
||||
buffer.position(pos);
|
||||
buffer.limit(lim);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int position() {
|
||||
return buffer.position();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DERBuffer position(final int newPosition) {
|
||||
buffer.position(newPosition);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int limit() {
|
||||
return buffer.limit();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int capacity() {
|
||||
return buffer.capacity();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DERBuffer limit(final int newLimit) {
|
||||
buffer.limit(newLimit);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DERBuffer clear() {
|
||||
buffer.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte get() {
|
||||
return buffer.get();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DERBuffer get(final byte[] dst) {
|
||||
buffer.get(dst);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DERBuffer slice() {
|
||||
return new DefaultDERBuffer(buffer.slice());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getName() + "@" + hashCode() + "::" +
|
||||
"pos=" + position() + ", " +
|
||||
"lim=" + limit() + ", " +
|
||||
"cap=" + capacity();
|
||||
}
|
||||
}
|
133
asn1/src/main/java/org/xbib/asn1/IntegerType.java
Normal file
133
asn1/src/main/java/org/xbib/asn1/IntegerType.java
Normal file
|
@ -0,0 +1,133 @@
|
|||
|
||||
package org.xbib.asn1;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* Converts arbitrary-precision integers to and from their DER encoded format.
|
||||
*
|
||||
*/
|
||||
public class IntegerType extends AbstractDERType implements DEREncoder {
|
||||
|
||||
/**
|
||||
* Integer to encode.
|
||||
*/
|
||||
private final byte[] derItem;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new integer type.
|
||||
*
|
||||
* @param item to DER encode
|
||||
*/
|
||||
public IntegerType(final BigInteger item) {
|
||||
super(UniversalDERTag.INT);
|
||||
derItem = item.toByteArray();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new integer type.
|
||||
*
|
||||
* @param item to DER encode
|
||||
*/
|
||||
public IntegerType(final int item) {
|
||||
super(UniversalDERTag.INT);
|
||||
derItem = BigInteger.valueOf(item).toByteArray();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new integer type.
|
||||
*
|
||||
* @param tag der tag associated with this type
|
||||
* @param item to DER encode
|
||||
* @throws IllegalArgumentException if the der tag is constructed
|
||||
*/
|
||||
public IntegerType(final DERTag tag, final BigInteger item) {
|
||||
super(tag);
|
||||
if (tag.isConstructed()) {
|
||||
throw new IllegalArgumentException("DER tag must not be constructed");
|
||||
}
|
||||
derItem = item.toByteArray();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new integer type.
|
||||
*
|
||||
* @param tag der tag associated with this type
|
||||
* @param item to DER encode
|
||||
* @throws IllegalArgumentException if the der tag is constructed
|
||||
*/
|
||||
public IntegerType(final DERTag tag, final int item) {
|
||||
super(tag);
|
||||
if (tag.isConstructed()) {
|
||||
throw new IllegalArgumentException("DER tag must not be constructed");
|
||||
}
|
||||
derItem = BigInteger.valueOf(item).toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts bytes in the buffer to an integer by reading from the current position to the limit, which assumes the
|
||||
* bytes of the integer are in big-endian order.
|
||||
*
|
||||
* @param encoded buffer containing DER-encoded data where the buffer is positioned at the start of integer bytes
|
||||
* and the limit is set beyond the last byte of integer data.
|
||||
* @return decoded bytes as an integer of arbitrary size.
|
||||
*/
|
||||
public static BigInteger decode(final DERBuffer encoded) {
|
||||
return new BigInteger(encoded.getRemainingBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts bytes in the buffer to an unsigned integer by reading from the current position to the limit, which
|
||||
* assumes the bytes of the integer are in big-endian order.
|
||||
*
|
||||
* @param encoded buffer containing DER-encoded data where the buffer is positioned at the start of integer bytes
|
||||
* and the limit is set beyond the last byte of integer data.
|
||||
* @return decoded bytes as an unsigned integer of arbitrary size.
|
||||
*/
|
||||
public static BigInteger decodeUnsigned(final DERBuffer encoded) {
|
||||
return new BigInteger(1, encoded.getRemainingBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts bytes in the buffer to an unsigned primitive integer by reading from the current position to the limit,
|
||||
* which assumes the bytes of the integer are in big-endian order. This method reads up to 4 bytes from the buffer.
|
||||
*
|
||||
* @param encoded buffer containing DER-encoded data where the buffer is positioned at the start of integer bytes
|
||||
* and the limit is set beyond the last byte of integer data.
|
||||
* @return decoded bytes as an unsigned integer.
|
||||
* @throws IllegalArgumentException if the buffer contains more than 4 bytes
|
||||
*/
|
||||
public static int decodeUnsignedPrimitive(final DERBuffer encoded) {
|
||||
// CheckStyle:MagicNumber OFF
|
||||
final byte[] bytes = encoded.getRemainingBytes();
|
||||
if (bytes.length > 4) {
|
||||
throw new IllegalArgumentException("Buffer length must be <= 4 bytes");
|
||||
}
|
||||
int i = 0;
|
||||
for (byte b : bytes) {
|
||||
i <<= 8;
|
||||
i |= b & 0xFF;
|
||||
}
|
||||
return i;
|
||||
// CheckStyle:MagicNumber ON
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the supplied big integer to a byte array.
|
||||
*
|
||||
* @param i to convert
|
||||
* @return byte array
|
||||
*/
|
||||
public static byte[] toBytes(final BigInteger i) {
|
||||
return i.toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encode() {
|
||||
return encode(derItem);
|
||||
}
|
||||
}
|
508
asn1/src/main/java/org/xbib/asn1/LdapUtils.java
Normal file
508
asn1/src/main/java/org/xbib/asn1/LdapUtils.java
Normal file
|
@ -0,0 +1,508 @@
|
|||
package org.xbib.asn1;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Queue;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Provides utility methods for this package.
|
||||
*
|
||||
*/
|
||||
public final class LdapUtils {
|
||||
|
||||
/**
|
||||
* Size of buffer in bytes to use when reading files.
|
||||
*/
|
||||
private static final int READ_BUFFER_SIZE = 128;
|
||||
|
||||
/**
|
||||
* Prime number to assist in calculating hash codes.
|
||||
*/
|
||||
private static final int HASH_CODE_PRIME = 113;
|
||||
|
||||
/**
|
||||
* Pattern to match ipv4 addresses.
|
||||
*/
|
||||
private static final Pattern IPV4_PATTERN = Pattern.compile(
|
||||
"^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)" +
|
||||
"(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$");
|
||||
|
||||
/**
|
||||
* Pattern to match ipv6 addresses.
|
||||
*/
|
||||
private static final Pattern IPV6_STD_PATTERN = Pattern.compile("^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$");
|
||||
|
||||
/**
|
||||
* Pattern to match ipv6 hex compressed addresses.
|
||||
*/
|
||||
private static final Pattern IPV6_HEX_COMPRESSED_PATTERN = Pattern.compile(
|
||||
"^((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::" +
|
||||
"((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)$");
|
||||
|
||||
/**
|
||||
* Pattern that matches control characters.
|
||||
*/
|
||||
private static final Pattern CNTRL_PATTERN = Pattern.compile("\\p{Cntrl}");
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
private LdapUtils() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This will convert the supplied value to a base64 encoded string. Returns null if the supplied byte array is null.
|
||||
*
|
||||
* @param value to base64 encode
|
||||
* @return base64 encoded value
|
||||
*/
|
||||
public static String base64Encode(final byte... value) {
|
||||
return value != null ? new String(Base64.getEncoder().encode(value), StandardCharsets.UTF_8) : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This will convert the supplied value to a base64 encoded string. Returns null if the supplied string is null.
|
||||
*
|
||||
* @param value to base64 encode
|
||||
* @return base64 encoded value
|
||||
*/
|
||||
public static String base64Encode(final String value) {
|
||||
return value != null ? base64Encode(value.getBytes(StandardCharsets.UTF_8)) : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This will convert the supplied value to a UTF-8 encoded string. Returns null if the supplied byte array is null.
|
||||
*
|
||||
* @param value to UTF-8 encode
|
||||
* @return UTF-8 encoded value
|
||||
*/
|
||||
public static String utf8Encode(final byte[] value) {
|
||||
return utf8Encode(value, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This will convert the supplied value to a UTF-8 encoded string.
|
||||
*
|
||||
* @param value to UTF-8 encode
|
||||
* @param allowNull whether to throw {@link NullPointerException} if value is null
|
||||
* @return UTF-8 encoded value
|
||||
* @throws NullPointerException if allowNull is false and value is null
|
||||
*/
|
||||
public static String utf8Encode(final byte[] value, final boolean allowNull) {
|
||||
if (!allowNull && value == null) {
|
||||
throw new NullPointerException("Cannot UTF-8 encode null value");
|
||||
}
|
||||
return value != null ? new String(value, StandardCharsets.UTF_8) : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This will convert the supplied value to a UTF-8 encoded byte array. Returns null if the supplied string is null.
|
||||
*
|
||||
* @param value to UTF-8 encode
|
||||
* @return UTF-8 encoded value
|
||||
*/
|
||||
public static byte[] utf8Encode(final String value) {
|
||||
return utf8Encode(value, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This will convert the supplied value to a UTF-8 encoded byte array.
|
||||
*
|
||||
* @param value to UTF-8 encode
|
||||
* @param allowNull whether to throw {@link NullPointerException} if value is null
|
||||
* @return UTF-8 encoded value
|
||||
* @throws NullPointerException if allowNull is false and value is null
|
||||
*/
|
||||
public static byte[] utf8Encode(final String value, final boolean allowNull) {
|
||||
if (!allowNull && value == null) {
|
||||
throw new NullPointerException("Cannot UTF-8 encode null value");
|
||||
}
|
||||
return value != null ? value.getBytes(StandardCharsets.UTF_8) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the space character from both the beginning and end of the supplied value.
|
||||
*
|
||||
* @param value to trim space character from
|
||||
* @return trimmed value or same value if no trim was performed
|
||||
*/
|
||||
public static String trimSpace(final String value) {
|
||||
if (value == null || value.isEmpty()) {
|
||||
return value;
|
||||
}
|
||||
|
||||
int startIndex = 0;
|
||||
int endIndex = value.length();
|
||||
while (startIndex < endIndex && value.charAt(startIndex) == ' ') {
|
||||
startIndex++;
|
||||
}
|
||||
while (startIndex < endIndex && value.charAt(endIndex - 1) == ' ') {
|
||||
endIndex--;
|
||||
}
|
||||
if (startIndex == 0 && endIndex == value.length()) {
|
||||
return value;
|
||||
}
|
||||
return value.substring(startIndex, endIndex);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Changes the supplied value by replacing multiple spaces with a single space.
|
||||
*
|
||||
* @param value to compress spaces
|
||||
* @param trim whether to remove any leading or trailing space characters
|
||||
* @return normalized value or value if no compress was performed
|
||||
*/
|
||||
public static String compressSpace(final String value, final boolean trim) {
|
||||
if (value == null || value.isEmpty()) {
|
||||
return value;
|
||||
}
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
boolean foundSpace = false;
|
||||
for (int i = 0; i < value.length(); i++) {
|
||||
final char ch = value.charAt(i);
|
||||
if (ch == ' ') {
|
||||
if (i == value.length() - 1) {
|
||||
// last char is a space
|
||||
sb.append(ch);
|
||||
}
|
||||
foundSpace = true;
|
||||
} else {
|
||||
if (foundSpace) {
|
||||
sb.append(' ');
|
||||
}
|
||||
sb.append(ch);
|
||||
foundSpace = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (sb.length() == 0 && foundSpace) {
|
||||
return trim ? "" : " ";
|
||||
}
|
||||
if (trim) {
|
||||
if (sb.length() > 0 && sb.charAt(0) == ' ') {
|
||||
sb.deleteCharAt(0);
|
||||
}
|
||||
if (sb.length() > 0 && sb.charAt(sb.length() - 1) == ' ') {
|
||||
sb.deleteCharAt(sb.length() - 1);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This will decode the supplied value as a base64 encoded string to a byte[]. Returns null if the supplied string is
|
||||
* null.
|
||||
*
|
||||
* @param value to base64 decode
|
||||
* @return base64 decoded value
|
||||
*/
|
||||
public static byte[] base64Decode(final String value) {
|
||||
try {
|
||||
return value != null ? Base64.getDecoder().decode(value.getBytes(StandardCharsets.UTF_8)) : null;
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException("Error decoding value: " + value, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the supplied string to lower case. If the string contains non-ascii characters, {@link Locale#ROOT} is
|
||||
* used.
|
||||
*
|
||||
* @param s to lower case
|
||||
* @return new lower case string
|
||||
*/
|
||||
public static String toLowerCase(final String s) {
|
||||
if (s == null || s.isEmpty()) {
|
||||
return s;
|
||||
}
|
||||
// CheckStyle:MagicNumber OFF
|
||||
// if string contains non-ascii, use locale specific lowercase
|
||||
if (s.chars().anyMatch(c -> c > 0x7F)) {
|
||||
return s.toLowerCase(Locale.ROOT);
|
||||
}
|
||||
return toLowerCaseAscii(s);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts the characters A-Z to a-z.
|
||||
*
|
||||
* @param s to lower case
|
||||
* @return new string with lower case alphabetical characters
|
||||
* @throws IllegalArgumentException if the supplied string contains non-ascii characters
|
||||
*/
|
||||
public static String toLowerCaseAscii(final String s) {
|
||||
if (s == null || s.isEmpty()) {
|
||||
return s;
|
||||
}
|
||||
// mutate A-Z to a-z
|
||||
// CheckStyle:MagicNumber OFF
|
||||
final char[] chars = s.toCharArray();
|
||||
for (int i = 0; i < chars.length; i++) {
|
||||
if (chars[i] > 0x7F) {
|
||||
throw new IllegalArgumentException("String contains non-ascii characters: " + s);
|
||||
} else if (chars[i] >= 'A' && chars[i] <= 'Z') {
|
||||
chars[i] = (char) (chars[i] + 32);
|
||||
}
|
||||
}
|
||||
// CheckStyle:MagicNumber ON
|
||||
return new String(chars);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts the supplied string to upper case. If the string contains non-ascii characters, {@link Locale#ROOT} is
|
||||
* used.
|
||||
*
|
||||
* @param s to upper case
|
||||
* @return new upper case string
|
||||
*/
|
||||
public static String toUpperCase(final String s) {
|
||||
if (s == null || s.isEmpty()) {
|
||||
return s;
|
||||
}
|
||||
// CheckStyle:MagicNumber OFF
|
||||
// if string contains non-ascii, use locale specific uppercase
|
||||
if (s.chars().anyMatch(c -> c > 0x7F)) {
|
||||
return s.toUpperCase(Locale.ROOT);
|
||||
}
|
||||
return toUpperCaseAscii(s);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts the characters a-z to A-Z.
|
||||
*
|
||||
* @param s to upper case
|
||||
* @return new string with upper case alphabetical characters
|
||||
* @throws IllegalArgumentException if the supplied string contains non-ascii characters
|
||||
*/
|
||||
public static String toUpperCaseAscii(final String s) {
|
||||
if (s == null || s.isEmpty()) {
|
||||
return s;
|
||||
}
|
||||
// mutate a-z to A-Z
|
||||
// CheckStyle:MagicNumber OFF
|
||||
final char[] chars = s.toCharArray();
|
||||
for (int i = 0; i < chars.length; i++) {
|
||||
if (chars[i] > 0x7F) {
|
||||
throw new IllegalArgumentException("String contains non-ascii characters: " + s);
|
||||
} else if (chars[i] >= 'a' && chars[i] <= 'z') {
|
||||
chars[i] = (char) (chars[i] - 32);
|
||||
}
|
||||
}
|
||||
// CheckStyle:MagicNumber ON
|
||||
return new String(chars);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads the data in the supplied stream and returns it as a byte array.
|
||||
*
|
||||
* @param is stream to read
|
||||
* @return bytes read from the stream
|
||||
* @throws IOException if an error occurs reading data
|
||||
*/
|
||||
public static byte[] readInputStream(final InputStream is)
|
||||
throws IOException {
|
||||
final ByteArrayOutputStream data = new ByteArrayOutputStream();
|
||||
try (is; data) {
|
||||
final byte[] buffer = new byte[READ_BUFFER_SIZE];
|
||||
int length;
|
||||
while ((length = is.read(buffer)) != -1) {
|
||||
data.write(buffer, 0, length);
|
||||
}
|
||||
}
|
||||
return data.toByteArray();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Concatenates multiple arrays together.
|
||||
*
|
||||
* @param <T> type of array
|
||||
* @param first array to concatenate. Cannot be null.
|
||||
* @param rest of the arrays to concatenate. May be null.
|
||||
* @return array containing the concatenation of all parameters
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T[] concatArrays(final T[] first, final T[]... rest) {
|
||||
int totalLength = first.length;
|
||||
for (T[] array : rest) {
|
||||
if (array != null) {
|
||||
totalLength += array.length;
|
||||
}
|
||||
}
|
||||
|
||||
final T[] result = Arrays.copyOf(first, totalLength);
|
||||
|
||||
int offset = first.length;
|
||||
for (T[] array : rest) {
|
||||
if (array != null) {
|
||||
System.arraycopy(array, 0, result, offset, array.length);
|
||||
offset += array.length;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines equality of the supplied objects. Array types are automatically detected.
|
||||
*
|
||||
* @param o1 to test equality of
|
||||
* @param o2 to test equality of
|
||||
* @return whether o1 equals o2
|
||||
*/
|
||||
public static boolean areEqual(final Object o1, final Object o2) {
|
||||
if (o1 == o2) {
|
||||
return true;
|
||||
}
|
||||
final boolean areEqual;
|
||||
if (o1 instanceof boolean[] && o2 instanceof boolean[]) {
|
||||
areEqual = Arrays.equals((boolean[]) o1, (boolean[]) o2);
|
||||
} else if (o1 instanceof byte[] && o2 instanceof byte[]) {
|
||||
areEqual = Arrays.equals((byte[]) o1, (byte[]) o2);
|
||||
} else if (o1 instanceof char[] && o2 instanceof char[]) {
|
||||
areEqual = Arrays.equals((char[]) o1, (char[]) o2);
|
||||
} else if (o1 instanceof double[] && o2 instanceof double[]) {
|
||||
areEqual = Arrays.equals((double[]) o1, (double[]) o2);
|
||||
} else if (o1 instanceof float[] && o2 instanceof float[]) {
|
||||
areEqual = Arrays.equals((float[]) o1, (float[]) o2);
|
||||
} else if (o1 instanceof int[] && o2 instanceof int[]) {
|
||||
areEqual = Arrays.equals((int[]) o1, (int[]) o2);
|
||||
} else if (o1 instanceof long[] && o2 instanceof long[]) {
|
||||
areEqual = Arrays.equals((long[]) o1, (long[]) o2);
|
||||
} else if (o1 instanceof short[] && o2 instanceof short[]) {
|
||||
areEqual = Arrays.equals((short[]) o1, (short[]) o2);
|
||||
} else if (o1 instanceof Object[] && o2 instanceof Object[]) {
|
||||
areEqual = Arrays.deepEquals((Object[]) o1, (Object[]) o2);
|
||||
} else {
|
||||
areEqual = o1 != null && o1.equals(o2);
|
||||
}
|
||||
return areEqual;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Computes a hash code for the supplied objects using the supplied seed. If a Collection type is found it is iterated
|
||||
* over.
|
||||
*
|
||||
* @param seed odd/prime number
|
||||
* @param objects to calculate hashCode for
|
||||
* @return hash code for the supplied objects
|
||||
*/
|
||||
public static int computeHashCode(final int seed, final Object... objects) {
|
||||
if (objects == null || objects.length == 0) {
|
||||
return seed * HASH_CODE_PRIME;
|
||||
}
|
||||
|
||||
int hc = seed;
|
||||
for (Object object : objects) {
|
||||
hc = HASH_CODE_PRIME * hc;
|
||||
if (object != null) {
|
||||
if (object instanceof List<?> || object instanceof Queue<?>) {
|
||||
int index = 1;
|
||||
for (Object o : (Collection<?>) object) {
|
||||
hc += computeHashCode(o) * index++;
|
||||
}
|
||||
} else if (object instanceof Collection<?>) {
|
||||
for (Object o : (Collection<?>) object) {
|
||||
hc += computeHashCode(o);
|
||||
}
|
||||
} else {
|
||||
hc += computeHashCode(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
return hc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Computes a hash code for the supplied object. Checks for arrays of primitives and Objects then delegates to the
|
||||
* {@link Arrays} class. Otherwise {@link Object#hashCode()} is invoked.
|
||||
*
|
||||
* @param object to calculate hash code for
|
||||
* @return hash code
|
||||
*/
|
||||
private static int computeHashCode(final Object object) {
|
||||
int hc = 0;
|
||||
if (object instanceof boolean[]) {
|
||||
hc += Arrays.hashCode((boolean[]) object);
|
||||
} else if (object instanceof byte[]) {
|
||||
hc += Arrays.hashCode((byte[]) object);
|
||||
} else if (object instanceof char[]) {
|
||||
hc += Arrays.hashCode((char[]) object);
|
||||
} else if (object instanceof double[]) {
|
||||
hc += Arrays.hashCode((double[]) object);
|
||||
} else if (object instanceof float[]) {
|
||||
hc += Arrays.hashCode((float[]) object);
|
||||
} else if (object instanceof int[]) {
|
||||
hc += Arrays.hashCode((int[]) object);
|
||||
} else if (object instanceof long[]) {
|
||||
hc += Arrays.hashCode((long[]) object);
|
||||
} else if (object instanceof short[]) {
|
||||
hc += Arrays.hashCode((short[]) object);
|
||||
} else if (object instanceof Object[]) {
|
||||
hc += Arrays.deepHashCode((Object[]) object);
|
||||
} else {
|
||||
hc += object.hashCode();
|
||||
}
|
||||
return hc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the supplied string represents an IP address. Matches both IPv4 and IPv6 addresses.
|
||||
*
|
||||
* @param s to match
|
||||
* @return whether the supplied string represents an IP address
|
||||
*/
|
||||
public static boolean isIPAddress(final String s) {
|
||||
return
|
||||
s != null &&
|
||||
(IPV4_PATTERN.matcher(s).matches() || IPV6_STD_PATTERN.matcher(s).matches() ||
|
||||
IPV6_HEX_COMPRESSED_PATTERN.matcher(s).matches());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Looks for the supplied system property value and loads a class with that name. The default constructor for that
|
||||
* class is then returned.
|
||||
*
|
||||
* @param property whose value is a class
|
||||
* @return class constructor or null if no system property was found
|
||||
* @throws IllegalArgumentException if an error occurs instantiating the constructor
|
||||
*/
|
||||
public static Constructor<?> createConstructorFromProperty(final String property) {
|
||||
final String clazz = System.getProperty(property);
|
||||
if (clazz != null) {
|
||||
try {
|
||||
return Class.forName(clazz).getDeclaredConstructor();
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Error getting declared constructor for " + clazz, e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
25
asn1/src/main/java/org/xbib/asn1/NullType.java
Normal file
25
asn1/src/main/java/org/xbib/asn1/NullType.java
Normal file
|
@ -0,0 +1,25 @@
|
|||
|
||||
package org.xbib.asn1;
|
||||
|
||||
/**
|
||||
* Convenience type for a tag with a null value.
|
||||
*
|
||||
*/
|
||||
public class NullType extends AbstractDERType implements DEREncoder {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new null type.
|
||||
*
|
||||
* @param tag der tag associated with this type
|
||||
*/
|
||||
public NullType(final DERTag tag) {
|
||||
super(tag);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte[] encode() {
|
||||
return encode((byte[]) null);
|
||||
}
|
||||
}
|
90
asn1/src/main/java/org/xbib/asn1/OctetStringType.java
Normal file
90
asn1/src/main/java/org/xbib/asn1/OctetStringType.java
Normal file
|
@ -0,0 +1,90 @@
|
|||
|
||||
package org.xbib.asn1;
|
||||
|
||||
/**
|
||||
* Converts strings to and from their DER encoded format.
|
||||
*
|
||||
*/
|
||||
public class OctetStringType extends AbstractDERType implements DEREncoder {
|
||||
|
||||
/**
|
||||
* String to encode.
|
||||
*/
|
||||
private final byte[] derItem;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new octet string type.
|
||||
*
|
||||
* @param item to DER encode
|
||||
*/
|
||||
public OctetStringType(final String item) {
|
||||
this(LdapUtils.utf8Encode(item, false));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new octet string type.
|
||||
*
|
||||
* @param item to DER encode
|
||||
*/
|
||||
public OctetStringType(final byte[] item) {
|
||||
super(UniversalDERTag.OCTSTR);
|
||||
derItem = item;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new octet string type.
|
||||
*
|
||||
* @param tag der tag associated with this type
|
||||
* @param item to DER encode
|
||||
* @throws IllegalArgumentException if the der tag is constructed
|
||||
*/
|
||||
public OctetStringType(final DERTag tag, final String item) {
|
||||
this(tag, LdapUtils.utf8Encode(item, false));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new octet string type.
|
||||
*
|
||||
* @param tag der tag associated with this type
|
||||
* @param item to DER encode
|
||||
* @throws IllegalArgumentException if the der tag is constructed
|
||||
*/
|
||||
public OctetStringType(final DERTag tag, final byte[] item) {
|
||||
super(tag);
|
||||
if (tag.isConstructed()) {
|
||||
throw new IllegalArgumentException("DER tag must not be constructed");
|
||||
}
|
||||
derItem = item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts bytes in the buffer to a string by reading from the current position to the limit, which assumes the bytes
|
||||
* of the string are in big-endian order.
|
||||
*
|
||||
* @param encoded buffer containing DER-encoded data where the buffer is positioned at the start of string bytes and
|
||||
* the limit is set beyond the last byte of string data.
|
||||
* @return decoded bytes as an string
|
||||
*/
|
||||
public static String decode(final DERBuffer encoded) {
|
||||
return LdapUtils.utf8Encode(encoded.getRemainingBytes(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the supplied string to a byte array using the UTF-8 encoding.
|
||||
*
|
||||
* @param s to convert
|
||||
* @return byte array
|
||||
*/
|
||||
public static byte[] toBytes(final String s) {
|
||||
return LdapUtils.utf8Encode(s, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encode() {
|
||||
return encode(derItem);
|
||||
}
|
||||
}
|
248
asn1/src/main/java/org/xbib/asn1/OidType.java
Normal file
248
asn1/src/main/java/org/xbib/asn1/OidType.java
Normal file
|
@ -0,0 +1,248 @@
|
|||
|
||||
package org.xbib.asn1;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/**
|
||||
* Converts object identifiers to and from their DER encoded format.
|
||||
*
|
||||
*/
|
||||
public class OidType extends AbstractDERType implements DEREncoder {
|
||||
|
||||
/**
|
||||
* Integer to encode.
|
||||
*/
|
||||
private final byte[] derItem;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new oid type.
|
||||
*
|
||||
* @param item to DER encode
|
||||
*/
|
||||
public OidType(final String item) {
|
||||
super(UniversalDERTag.OID);
|
||||
derItem = toBytes(parse(item));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new oid type.
|
||||
*
|
||||
* @param item to DER encode
|
||||
*/
|
||||
public OidType(final int[] item) {
|
||||
super(UniversalDERTag.OID);
|
||||
derItem = toBytes(item);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new oid type.
|
||||
*
|
||||
* @param tag der tag associated with this type
|
||||
* @param item to DER encode
|
||||
* @throws IllegalArgumentException if the der tag is constructed
|
||||
*/
|
||||
public OidType(final DERTag tag, final String item) {
|
||||
super(tag);
|
||||
if (tag.isConstructed()) {
|
||||
throw new IllegalArgumentException("DER tag must not be constructed");
|
||||
}
|
||||
derItem = toBytes(parse(item));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new oid type.
|
||||
*
|
||||
* @param tag der tag associated with this type
|
||||
* @param item to DER encode
|
||||
* @throws IllegalArgumentException if the der tag is constructed
|
||||
*/
|
||||
public OidType(final DERTag tag, final int[] item) {
|
||||
super(tag);
|
||||
if (tag.isConstructed()) {
|
||||
throw new IllegalArgumentException("DER tag must not be constructed");
|
||||
}
|
||||
derItem = toBytes(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts bytes in the buffer to an OID by reading from the current position to the limit, which assumes the bytes
|
||||
* of the integer are in big-endian order.
|
||||
*
|
||||
* @param encoded buffer containing DER-encoded data where the buffer is positioned at the start of OID bytes and
|
||||
* the limit is set beyond the last byte of OID data.
|
||||
* @return decoded bytes as an OID.
|
||||
*/
|
||||
public static String decode(final DERBuffer encoded) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final int firstId = encoded.get();
|
||||
// CheckStyle:MagicNumber OFF
|
||||
if (firstId < 40) {
|
||||
sb.append("0").append(".").append(firstId).append(".");
|
||||
} else if (firstId < 80) {
|
||||
sb.append("1").append(".").append(firstId - 40).append(".");
|
||||
} else {
|
||||
sb.append("2").append(".").append(firstId - 80).append(".");
|
||||
}
|
||||
// CheckStyle:MagicNumber ON
|
||||
while (encoded.hasRemaining()) {
|
||||
sb.append(readInt(encoded)).append(".");
|
||||
}
|
||||
sb.setLength(sb.length() - 1);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the supplied list of oid components to a byte array.
|
||||
*
|
||||
* @param oid to convert
|
||||
* @return byte array
|
||||
* @throws IllegalArgumentException if the oid is not valid. See {@link #isValid(int[])}
|
||||
*/
|
||||
public static byte[] toBytes(final int[] oid) {
|
||||
isValid(oid);
|
||||
|
||||
final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
try {
|
||||
try {
|
||||
// CheckStyle:MagicNumber OFF
|
||||
if (oid[0] < 2) {
|
||||
// should always fit into one byte, since oid[1] must be <= 38
|
||||
bytes.write((oid[0] * 40) + oid[1]);
|
||||
} else {
|
||||
bytes.write(toBytes((oid[0] * 40) + oid[1]));
|
||||
}
|
||||
for (int i = 2; i < oid.length; i++) {
|
||||
bytes.write(toBytes(oid[i]));
|
||||
}
|
||||
// CheckStyle:MagicNumber ON
|
||||
} finally {
|
||||
bytes.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Byte conversion failed", e);
|
||||
}
|
||||
return bytes.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the supplied oid is valid. Oids must meet the following criteria:
|
||||
*
|
||||
* <ul>
|
||||
* <li>must not be null and must have at least 2 elements</li>
|
||||
* <li>components must not be negative</li>
|
||||
* <li>first component must be 0, 1, or 2</li>
|
||||
* <li>if first component 0 or 1, second component must be <= 38</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param oid to check
|
||||
* @throws IllegalArgumentException if the oid is not valid.
|
||||
*/
|
||||
protected static void isValid(final int[] oid) {
|
||||
// CheckStyle:MagicNumber OFF
|
||||
if (oid == null || oid.length < 2) {
|
||||
throw new IllegalArgumentException("OIDs must have at least two components");
|
||||
}
|
||||
if (oid[0] < 0 || oid[0] > 2) {
|
||||
throw new IllegalArgumentException("The first OID must be 0, 1, or 2");
|
||||
}
|
||||
if (oid[0] < 2 && oid[1] > 39) {
|
||||
throw new IllegalArgumentException("The second OID must be less than or equal to 38");
|
||||
}
|
||||
for (int i : oid) {
|
||||
if (i < 0) {
|
||||
throw new IllegalArgumentException("OIDs cannot be negative");
|
||||
}
|
||||
}
|
||||
// CheckStyle:MagicNumber ON
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the supplied oid component to a byte array. The length of the byte array is the minimal size needed to
|
||||
* contain the oid component.
|
||||
*
|
||||
* @param component to convert to bytes
|
||||
* @return oid bytes
|
||||
*/
|
||||
protected static byte[] toBytes(final int component) {
|
||||
// CheckStyle:MagicNumber OFF
|
||||
final byte[] buffer = new byte[4];
|
||||
int size = 0;
|
||||
int val = component;
|
||||
while (val != 0) {
|
||||
if (size > 0) {
|
||||
buffer[size++] = (byte) ((val & 0x7F) | 0x80);
|
||||
} else {
|
||||
buffer[size++] = (byte) (val & 0x7F);
|
||||
}
|
||||
val >>>= 7;
|
||||
}
|
||||
|
||||
final byte[] bytes = new byte[size];
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
bytes[i] = buffer[--size];
|
||||
}
|
||||
return bytes;
|
||||
// CheckStyle:MagicNumber ON
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the necessary encoded bytes from the supplied buffer to create an integer.
|
||||
*
|
||||
* @param buffer to read
|
||||
* @return OID component integer
|
||||
*/
|
||||
protected static int readInt(final DERBuffer buffer) {
|
||||
// CheckStyle:MagicNumber OFF
|
||||
int val = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
final byte b = buffer.get();
|
||||
if (i == 0 && b == 0x80) {
|
||||
throw new IllegalArgumentException("Component starts with 0x80");
|
||||
}
|
||||
val <<= 7;
|
||||
val |= b & 0x7F;
|
||||
if ((b & 0x80) == 0) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
// CheckStyle:MagicNumber ON
|
||||
throw new IllegalArgumentException("Integer greater than 4 bytes in size");
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the supplied oid into an array on integers.
|
||||
*
|
||||
* @param oid to parse
|
||||
* @return array of oid components
|
||||
* @throws IllegalArgumentException if the oid is not valid. See {@link #isValid(int[])}
|
||||
*/
|
||||
public static int[] parse(final String oid) {
|
||||
if (oid == null) {
|
||||
throw new IllegalArgumentException("OID cannot be null");
|
||||
}
|
||||
|
||||
final StringTokenizer st = new StringTokenizer(oid, ".");
|
||||
final int[] oids = new int[st.countTokens()];
|
||||
int i = 0;
|
||||
while (st.hasMoreTokens()) {
|
||||
try {
|
||||
oids[i++] = Integer.parseInt(st.nextToken());
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
isValid(oids);
|
||||
return oids;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encode() {
|
||||
return encode(derItem);
|
||||
}
|
||||
}
|
18
asn1/src/main/java/org/xbib/asn1/ParseHandler.java
Normal file
18
asn1/src/main/java/org/xbib/asn1/ParseHandler.java
Normal file
|
@ -0,0 +1,18 @@
|
|||
|
||||
package org.xbib.asn1;
|
||||
|
||||
/**
|
||||
* Provides a hook in the DER parser for handling specific paths as they are encountered.
|
||||
*
|
||||
*/
|
||||
public interface ParseHandler {
|
||||
|
||||
|
||||
/**
|
||||
* Invoked when a DER path is encountered that belongs to this parse handler.
|
||||
*
|
||||
* @param parser that invoked this handler
|
||||
* @param encoded to handle
|
||||
*/
|
||||
void handle(DERParser parser, DERBuffer encoded);
|
||||
}
|
248
asn1/src/main/java/org/xbib/asn1/UniversalDERTag.java
Normal file
248
asn1/src/main/java/org/xbib/asn1/UniversalDERTag.java
Normal file
|
@ -0,0 +1,248 @@
|
|||
|
||||
package org.xbib.asn1;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Enumeration with common BER/DER universal tag types.
|
||||
*
|
||||
*/
|
||||
public enum UniversalDERTag implements DERTag {
|
||||
|
||||
/**
|
||||
* BOOLEAN type.
|
||||
*/
|
||||
BOOL(1, false),
|
||||
|
||||
/**
|
||||
* INTEGER type.
|
||||
*/
|
||||
INT(2, false),
|
||||
|
||||
/**
|
||||
* BITSTRING type.
|
||||
*/
|
||||
BITSTR(3, false),
|
||||
|
||||
/**
|
||||
* OCTETSTRING type.
|
||||
*/
|
||||
OCTSTR(4, false),
|
||||
|
||||
/**
|
||||
* NULL type.
|
||||
*/
|
||||
NULL(5, false),
|
||||
|
||||
/**
|
||||
* OBJECT IDENTIFIER type.
|
||||
*/
|
||||
OID(6, false),
|
||||
|
||||
/**
|
||||
* ObjectDescriptor type.
|
||||
*/
|
||||
ODESC(7, false),
|
||||
|
||||
/**
|
||||
* EXTERNAL type.
|
||||
*/
|
||||
EXT(8, false),
|
||||
|
||||
/**
|
||||
* REAL type.
|
||||
*/
|
||||
REAL(9, false),
|
||||
|
||||
/**
|
||||
* ENUMERATED type.
|
||||
*/
|
||||
ENUM(10, false),
|
||||
|
||||
/**
|
||||
* EMBEDDED PDV type.
|
||||
*/
|
||||
EMBPDV(11, false),
|
||||
|
||||
/**
|
||||
* UTF8String type.
|
||||
*/
|
||||
UTF8STR(12, false),
|
||||
|
||||
/**
|
||||
* RELATIVE-OID type.
|
||||
*/
|
||||
ROID(13, false),
|
||||
|
||||
/**
|
||||
* SEQUENCE type.
|
||||
*/
|
||||
SEQ(16, true),
|
||||
|
||||
/**
|
||||
* SET type.
|
||||
*/
|
||||
SET(17, true),
|
||||
|
||||
/**
|
||||
* NumericString type.
|
||||
*/
|
||||
NUMSTR(18, false),
|
||||
|
||||
/**
|
||||
* PrintableString type.
|
||||
*/
|
||||
PRINTSTR(19, false),
|
||||
|
||||
/**
|
||||
* T61String type.
|
||||
*/
|
||||
T61STR(20, false),
|
||||
|
||||
/**
|
||||
* VideotexString type.
|
||||
*/
|
||||
VTEXSTR(21, false),
|
||||
|
||||
/**
|
||||
* IA5String type.
|
||||
*/
|
||||
IA5STR(22, false),
|
||||
|
||||
/**
|
||||
* UTCTime type.
|
||||
*/
|
||||
UTCTIME(23, false),
|
||||
|
||||
/**
|
||||
* GeneralizedTime type.
|
||||
*/
|
||||
GENTIME(24, false),
|
||||
|
||||
/**
|
||||
* GraphicString type.
|
||||
*/
|
||||
GRAPHICSTR(25, false),
|
||||
|
||||
/**
|
||||
* ISO646String type.
|
||||
*/
|
||||
ISO646STR(26, false),
|
||||
|
||||
/**
|
||||
* GeneralString type.
|
||||
*/
|
||||
GENSTR(27, false),
|
||||
|
||||
/**
|
||||
* UniversalString type.
|
||||
*/
|
||||
UNISTR(28, false),
|
||||
|
||||
/**
|
||||
* CharacterString type.
|
||||
*/
|
||||
CHARSTR(29, false),
|
||||
|
||||
/**
|
||||
* BMPString type.
|
||||
*/
|
||||
BMPSTR(30, false);
|
||||
|
||||
/**
|
||||
* Universal tag class is 00b in first two high-order bytes.
|
||||
*/
|
||||
public static final int TAG_CLASS = 0;
|
||||
|
||||
/**
|
||||
* Maps tag numbers to tags.
|
||||
*/
|
||||
private static final Map<Integer, UniversalDERTag> TAGNO_MAP = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Maps tag names to tags.
|
||||
*/
|
||||
private static final Map<String, UniversalDERTag> TAGNAME_MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
// Initializes tag mapping
|
||||
for (UniversalDERTag tag : UniversalDERTag.values()) {
|
||||
TAGNO_MAP.put(tag.getTagNo(), tag);
|
||||
TAGNAME_MAP.put(tag.name(), tag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag number.
|
||||
*/
|
||||
private final int tagNo;
|
||||
|
||||
/**
|
||||
* Flag indicating whether value is primitive or constructed.
|
||||
*/
|
||||
private final boolean constructed;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new universal DER tag.
|
||||
*
|
||||
* @param number of the tag
|
||||
* @param isConstructed whether this tag is primitive or constructed
|
||||
*/
|
||||
UniversalDERTag(final int number, final boolean isConstructed) {
|
||||
tagNo = number;
|
||||
constructed = isConstructed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up a universal tag from a tag number.
|
||||
*
|
||||
* @param number tag number.
|
||||
* @return tag object corresponding to given number.
|
||||
* @throws IllegalArgumentException if tag is unknown
|
||||
*/
|
||||
public static UniversalDERTag fromTagNo(final int number) {
|
||||
final UniversalDERTag derTag = TAGNO_MAP.get(number);
|
||||
if (derTag == null) {
|
||||
throw new IllegalArgumentException("Unknown tag number: " + number);
|
||||
}
|
||||
return derTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up a universal tag from a tag name. This method differs from {@link #valueOf(String)} in that it does not
|
||||
* throw for unknown names.
|
||||
*
|
||||
* @param name tag name.
|
||||
* @return tag object corresponding to given name or null if no tag of the given name is found.
|
||||
*/
|
||||
public static UniversalDERTag fromTagName(final String name) {
|
||||
return TAGNAME_MAP.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the decimal value of the tag.
|
||||
*
|
||||
* @return decimal tag number.
|
||||
*/
|
||||
@Override
|
||||
public int getTagNo() {
|
||||
return tagNo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the tag is constructed or primitive.
|
||||
*
|
||||
* @return true if constructed, false if primitive.
|
||||
*/
|
||||
@Override
|
||||
public boolean isConstructed() {
|
||||
return constructed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTagByte() {
|
||||
return constructed ? tagNo | ASN_CONSTRUCTED : tagNo;
|
||||
}
|
||||
}
|
100
asn1/src/main/java/org/xbib/asn1/UuidType.java
Normal file
100
asn1/src/main/java/org/xbib/asn1/UuidType.java
Normal file
|
@ -0,0 +1,100 @@
|
|||
|
||||
package org.xbib.asn1;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Converts UUIDs to and from their DER encoded format. See RFC 4122.
|
||||
*
|
||||
*/
|
||||
public class UuidType extends AbstractDERType implements DEREncoder {
|
||||
|
||||
/**
|
||||
* Number of bytes in a uuid.
|
||||
*/
|
||||
private static final int UUID_LENGTH = 16;
|
||||
|
||||
/**
|
||||
* UUID to encode.
|
||||
*/
|
||||
private final byte[] derItem;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new uuid type.
|
||||
*
|
||||
* @param item to DER encode
|
||||
*/
|
||||
public UuidType(final UUID item) {
|
||||
super(UniversalDERTag.OCTSTR);
|
||||
derItem = toBytes(item);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new uuid type.
|
||||
*
|
||||
* @param tag der tag associated with this type
|
||||
* @param item to DER encode
|
||||
* @throws IllegalArgumentException if the der tag is constructed
|
||||
*/
|
||||
public UuidType(final DERTag tag, final UUID item) {
|
||||
super(tag);
|
||||
if (tag.isConstructed()) {
|
||||
throw new IllegalArgumentException("DER tag must not be constructed");
|
||||
}
|
||||
derItem = toBytes(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts bytes in the buffer to a uuid by reading from the current position to the limit.
|
||||
*
|
||||
* @param encoded buffer containing DER-encoded data where the buffer is positioned at the start of uuid bytes and
|
||||
* the limit is set beyond the last byte of uuid data.
|
||||
* @return decoded bytes as a uuid.
|
||||
*/
|
||||
public static UUID decode(final DERBuffer encoded) {
|
||||
final long mostSig = readLong(encoded);
|
||||
final long leastSig = readLong(encoded);
|
||||
return new UUID(mostSig, leastSig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the next 8 bytes from the supplied buffer to create a long.
|
||||
*
|
||||
* @param buffer to read
|
||||
* @return UUID component integer
|
||||
*/
|
||||
protected static long readLong(final DERBuffer buffer) {
|
||||
// CheckStyle:MagicNumber OFF
|
||||
return
|
||||
(((long) buffer.get()) << 56) |
|
||||
(((long) buffer.get() & 0xff) << 48) |
|
||||
(((long) buffer.get() & 0xff) << 40) |
|
||||
(((long) buffer.get() & 0xff) << 32) |
|
||||
(((long) buffer.get() & 0xff) << 24) |
|
||||
(((long) buffer.get() & 0xff) << 16) |
|
||||
(((long) buffer.get() & 0xff) << 8) |
|
||||
(((long) buffer.get() & 0xff));
|
||||
// CheckStyle:MagicNumber ON
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the supplied uuid to a byte array.
|
||||
*
|
||||
* @param uuid to convert
|
||||
* @return byte array
|
||||
*/
|
||||
public static byte[] toBytes(final UUID uuid) {
|
||||
final ByteBuffer buffer = ByteBuffer.wrap(new byte[UUID_LENGTH]);
|
||||
buffer.putLong(uuid.getMostSignificantBits());
|
||||
buffer.putLong(uuid.getLeastSignificantBits());
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encode() {
|
||||
return encode(derItem);
|
||||
}
|
||||
}
|
36
build.gradle
Normal file
36
build.gradle
Normal file
|
@ -0,0 +1,36 @@
|
|||
|
||||
plugins {
|
||||
id 'maven-publish'
|
||||
id 'signing'
|
||||
id "io.github.gradle-nexus.publish-plugin" version "2.0.0-rc-1"
|
||||
}
|
||||
|
||||
wrapper {
|
||||
gradleVersion = libs.versions.gradle.get()
|
||||
distributionType = Wrapper.DistributionType.BIN
|
||||
}
|
||||
|
||||
ext {
|
||||
user = 'joerg'
|
||||
name = 'net-ldap'
|
||||
description = 'LDAP classes for Java'
|
||||
inceptionYear = '2034'
|
||||
url = 'https://xbib.org/' + user + '/' + name
|
||||
scmUrl = 'https://xbib.org/' + user + '/' + name
|
||||
scmConnection = 'scm:git:git://xbib.org/' + user + '/' + name + '.git'
|
||||
scmDeveloperConnection = 'scm:git:ssh://forgejo@xbib.org:' + user + '/' + name + '.git'
|
||||
issueManagementSystem = 'Github'
|
||||
issueManagementUrl = ext.scmUrl + '/issues'
|
||||
licenseName = 'The Apache License, Version 2.0'
|
||||
licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||
}
|
||||
|
||||
subprojects {
|
||||
apply from: rootProject.file('gradle/ide/idea.gradle')
|
||||
apply from: rootProject.file('gradle/repositories/maven.gradle')
|
||||
apply from: rootProject.file('gradle/compile/java.gradle')
|
||||
apply from: rootProject.file('gradle/test/junit5.gradle')
|
||||
apply from: rootProject.file('gradle/publish/maven.gradle')
|
||||
}
|
||||
apply from: rootProject.file('gradle/publish/sonatype.gradle')
|
||||
apply from: rootProject.file('gradle/publish/forgejo.gradle')
|
3
gradle.properties
Normal file
3
gradle.properties
Normal file
|
@ -0,0 +1,3 @@
|
|||
group = org.xbib
|
||||
name = net-ldap
|
||||
version = 1.0.0
|
45
gradle/compile/java.gradle
Normal file
45
gradle/compile/java.gradle
Normal file
|
@ -0,0 +1,45 @@
|
|||
apply plugin: 'java-library'
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(21)
|
||||
}
|
||||
modularity.inferModulePath.set(true)
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes('Implementation-Version': project.version)
|
||||
}
|
||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
doFirst {
|
||||
options.fork = true
|
||||
options.forkOptions.jvmArgs += ['-Duser.language=en','-Duser.country=US']
|
||||
options.encoding = 'UTF-8'
|
||||
options.compilerArgs.add('-Xlint:all')
|
||||
// enforce presence of module-info.java
|
||||
options.compilerArgs.add("--module-path")
|
||||
options.compilerArgs.add(classpath.asPath)
|
||||
classpath = files()
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(Javadoc) {
|
||||
doFirst {
|
||||
options.addStringOption('Xdoclint:none', '-quiet')
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaExec) {
|
||||
doFirst {
|
||||
jvmArguments.add("--module-path")
|
||||
jvmArguments.add(classpath.asPath)
|
||||
classpath = files()
|
||||
}
|
||||
}
|
13
gradle/documentation/asciidoc.gradle
Normal file
13
gradle/documentation/asciidoc.gradle
Normal file
|
@ -0,0 +1,13 @@
|
|||
apply plugin: 'org.xbib.gradle.plugin.asciidoctor'
|
||||
|
||||
asciidoctor {
|
||||
attributes 'source-highlighter': 'coderay',
|
||||
toc: 'left',
|
||||
doctype: 'book',
|
||||
icons: 'font',
|
||||
encoding: 'utf-8',
|
||||
sectlink: true,
|
||||
sectanchors: true,
|
||||
linkattrs: true,
|
||||
imagesdir: 'img'
|
||||
}
|
8
gradle/ide/idea.gradle
Normal file
8
gradle/ide/idea.gradle
Normal file
|
@ -0,0 +1,8 @@
|
|||
apply plugin: 'idea'
|
||||
|
||||
idea {
|
||||
module {
|
||||
outputDir file('build/classes/java/main')
|
||||
testOutputDir file('build/classes/java/test')
|
||||
}
|
||||
}
|
16
gradle/publish/forgejo.gradle
Normal file
16
gradle/publish/forgejo.gradle
Normal file
|
@ -0,0 +1,16 @@
|
|||
if (project.hasProperty('forgeJoToken')) {
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://xbib.org/api/packages/joerg/maven'
|
||||
credentials(HttpHeaderCredentials) {
|
||||
name = "Authorization"
|
||||
value = "token ${project.property('forgeJoToken')}"
|
||||
}
|
||||
authentication {
|
||||
header(HttpHeaderAuthentication)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
27
gradle/publish/ivy.gradle
Normal file
27
gradle/publish/ivy.gradle
Normal file
|
@ -0,0 +1,27 @@
|
|||
apply plugin: 'ivy-publish'
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
ivy {
|
||||
url = "https://xbib.org/repo"
|
||||
}
|
||||
}
|
||||
publications {
|
||||
ivy(IvyPublication) {
|
||||
from components.java
|
||||
descriptor {
|
||||
license {
|
||||
name = 'The Apache License, Version 2.0'
|
||||
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||
}
|
||||
author {
|
||||
name = 'Jörg Prante'
|
||||
url = 'http://example.com/users/jane'
|
||||
}
|
||||
descriptor.description {
|
||||
text = rootProject.ext.description
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
51
gradle/publish/maven.gradle
Normal file
51
gradle/publish/maven.gradle
Normal file
|
@ -0,0 +1,51 @@
|
|||
|
||||
publishing {
|
||||
publications {
|
||||
"${project.name}"(MavenPublication) {
|
||||
from components.java
|
||||
pom {
|
||||
artifactId = project.name
|
||||
name = project.name
|
||||
description = rootProject.ext.description
|
||||
url = rootProject.ext.url
|
||||
inceptionYear = rootProject.ext.inceptionYear
|
||||
packaging = 'jar'
|
||||
organization {
|
||||
name = 'xbib'
|
||||
url = 'https://xbib.org'
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id = 'jprante'
|
||||
name = 'Jörg Prante'
|
||||
email = 'joergprante@gmail.com'
|
||||
url = 'https://xbib.org/joerg'
|
||||
}
|
||||
}
|
||||
scm {
|
||||
url = rootProject.ext.scmUrl
|
||||
connection = rootProject.ext.scmConnection
|
||||
developerConnection = rootProject.ext.scmDeveloperConnection
|
||||
}
|
||||
issueManagement {
|
||||
system = rootProject.ext.issueManagementSystem
|
||||
url = rootProject.ext.issueManagementUrl
|
||||
}
|
||||
licenses {
|
||||
license {
|
||||
name = rootProject.ext.licenseName
|
||||
url = rootProject.ext.licenseUrl
|
||||
distribution = 'repo'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (project.hasProperty("signing.keyId")) {
|
||||
apply plugin: 'signing'
|
||||
signing {
|
||||
sign publishing.publications."${project.name}"
|
||||
}
|
||||
}
|
12
gradle/publish/sonatype.gradle
Normal file
12
gradle/publish/sonatype.gradle
Normal file
|
@ -0,0 +1,12 @@
|
|||
|
||||
if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) {
|
||||
nexusPublishing {
|
||||
repositories {
|
||||
sonatype {
|
||||
username = project.property('ossrhUsername')
|
||||
password = project.property('ossrhPassword')
|
||||
packageGroup = "org.xbib"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
gradle/quality/checkstyle.gradle
Normal file
19
gradle/quality/checkstyle.gradle
Normal file
|
@ -0,0 +1,19 @@
|
|||
|
||||
apply plugin: 'checkstyle'
|
||||
|
||||
tasks.withType(Checkstyle) {
|
||||
ignoreFailures = true
|
||||
reports {
|
||||
xml.getRequired().set(true)
|
||||
html.getRequired().set(true)
|
||||
}
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
configFile = rootProject.file('gradle/quality/checkstyle.xml')
|
||||
ignoreFailures = true
|
||||
showViolations = true
|
||||
checkstyleMain {
|
||||
source = sourceSets.main.allSource
|
||||
}
|
||||
}
|
333
gradle/quality/checkstyle.xml
Normal file
333
gradle/quality/checkstyle.xml
Normal file
|
@ -0,0 +1,333 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE module PUBLIC
|
||||
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
|
||||
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
|
||||
|
||||
<!-- This is a checkstyle configuration file. For descriptions of
|
||||
what the following rules do, please see the checkstyle configuration
|
||||
page at http://checkstyle.sourceforge.net/config.html -->
|
||||
|
||||
<module name="Checker">
|
||||
|
||||
<module name="BeforeExecutionExclusionFileFilter">
|
||||
<property name="fileNamePattern" value=".*(Example|Test|module-info)(\$.*)?"/>
|
||||
</module>
|
||||
|
||||
<module name="FileTabCharacter">
|
||||
<!-- Checks that there are no tab characters in the file.
|
||||
-->
|
||||
</module>
|
||||
|
||||
<module name="NewlineAtEndOfFile">
|
||||
<property name="lineSeparator" value="lf"/>
|
||||
</module>
|
||||
|
||||
<module name="RegexpSingleline">
|
||||
<!-- Checks that FIXME is not used in comments. TODO is preferred.
|
||||
-->
|
||||
<property name="format" value="((//.*)|(\*.*))FIXME" />
|
||||
<property name="message" value='TODO is preferred to FIXME. e.g. "TODO(johndoe): Refactor when v2 is released."' />
|
||||
</module>
|
||||
|
||||
<module name="RegexpSingleline">
|
||||
<!-- Checks that TODOs are named. (Actually, just that they are followed
|
||||
by an open paren.)
|
||||
-->
|
||||
<property name="format" value="((//.*)|(\*.*))TODO[^(]" />
|
||||
<property name="message" value='All TODOs should be named. e.g. "TODO(johndoe): Refactor when v2 is released."' />
|
||||
</module>
|
||||
|
||||
<module name="JavadocPackage">
|
||||
<!-- Checks that each Java package has a Javadoc file used for commenting.
|
||||
Only allows a package-info.java, not package.html. -->
|
||||
</module>
|
||||
|
||||
<!-- All Java AST specific tests live under TreeWalker module. -->
|
||||
<module name="TreeWalker">
|
||||
|
||||
<!--
|
||||
|
||||
IMPORT CHECKS
|
||||
|
||||
-->
|
||||
|
||||
<module name="RedundantImport">
|
||||
<!-- Checks for redundant import statements. -->
|
||||
<property name="severity" value="error"/>
|
||||
</module>
|
||||
|
||||
<module name="ImportOrder">
|
||||
<!-- Checks for out of order import statements. -->
|
||||
|
||||
<property name="severity" value="warning"/>
|
||||
<!-- <property name="tokens" value="IMPORT, STATIC_IMPORT"/> -->
|
||||
<property name="separated" value="false"/>
|
||||
<property name="groups" value="*"/>
|
||||
<!-- <property name="option" value="above"/> -->
|
||||
<property name="sortStaticImportsAlphabetically" value="true"/>
|
||||
</module>
|
||||
|
||||
<module name="CustomImportOrder">
|
||||
<!-- <property name="customImportOrderRules" value="THIRD_PARTY_PACKAGE###SPECIAL_IMPORTS###STANDARD_JAVA_PACKAGE###STATIC"/> -->
|
||||
<!-- <property name="specialImportsRegExp" value="^javax\."/> -->
|
||||
<!-- <property name="standardPackageRegExp" value="^java\."/> -->
|
||||
<property name="sortImportsInGroupAlphabetically" value="true"/>
|
||||
<property name="separateLineBetweenGroups" value="false"/>
|
||||
</module>
|
||||
|
||||
<!--
|
||||
|
||||
JAVADOC CHECKS
|
||||
|
||||
-->
|
||||
|
||||
<!-- Checks for Javadoc comments. -->
|
||||
<!-- See http://checkstyle.sf.net/config_javadoc.html -->
|
||||
<module name="JavadocMethod">
|
||||
<property name="accessModifiers" value="protected"/>
|
||||
<property name="severity" value="warning"/>
|
||||
<property name="allowMissingParamTags" value="true"/>
|
||||
<property name="allowMissingReturnTag" value="true"/>
|
||||
</module>
|
||||
|
||||
<module name="JavadocType">
|
||||
<property name="scope" value="protected"/>
|
||||
<property name="severity" value="error"/>
|
||||
</module>
|
||||
|
||||
<module name="JavadocStyle">
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<!--
|
||||
|
||||
NAMING CHECKS
|
||||
|
||||
-->
|
||||
|
||||
<!-- Item 38 - Adhere to generally accepted naming conventions -->
|
||||
|
||||
<module name="PackageName">
|
||||
<!-- Validates identifiers for package names against the
|
||||
supplied expression. -->
|
||||
<!-- Here the default checkstyle rule restricts package name parts to
|
||||
seven characters, this is not in line with common practice at Google.
|
||||
-->
|
||||
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]{1,})*$"/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="TypeNameCheck">
|
||||
<!-- Validates static, final fields against the
|
||||
expression "^[A-Z][a-zA-Z0-9]*$". -->
|
||||
<metadata name="altname" value="TypeName"/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="ConstantNameCheck">
|
||||
<!-- Validates non-private, static, final fields against the supplied
|
||||
public/package final fields "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$". -->
|
||||
<metadata name="altname" value="ConstantName"/>
|
||||
<property name="applyToPublic" value="true"/>
|
||||
<property name="applyToProtected" value="true"/>
|
||||
<property name="applyToPackage" value="true"/>
|
||||
<property name="applyToPrivate" value="false"/>
|
||||
<property name="format" value="^([A-Z][A-Z0-9]*(_[A-Z0-9]+)*|FLAG_.*)$"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Variable ''{0}'' should be in ALL_CAPS (if it is a constant) or be private (otherwise)."/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="StaticVariableNameCheck">
|
||||
<!-- Validates static, non-final fields against the supplied
|
||||
expression "^[a-z][a-zA-Z0-9]*_?$". -->
|
||||
<metadata name="altname" value="StaticVariableName"/>
|
||||
<property name="applyToPublic" value="true"/>
|
||||
<property name="applyToProtected" value="true"/>
|
||||
<property name="applyToPackage" value="true"/>
|
||||
<property name="applyToPrivate" value="true"/>
|
||||
<property name="format" value="^[a-z][a-zA-Z0-9]*_?$"/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="MemberNameCheck">
|
||||
<!-- Validates non-static members against the supplied expression. -->
|
||||
<metadata name="altname" value="MemberName"/>
|
||||
<property name="applyToPublic" value="true"/>
|
||||
<property name="applyToProtected" value="true"/>
|
||||
<property name="applyToPackage" value="true"/>
|
||||
<property name="applyToPrivate" value="true"/>
|
||||
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="MethodNameCheck">
|
||||
<!-- Validates identifiers for method names. -->
|
||||
<metadata name="altname" value="MethodName"/>
|
||||
<property name="format" value="^[a-z][a-zA-Z0-9]*(_[a-zA-Z0-9]+)*$"/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="ParameterName">
|
||||
<!-- Validates identifiers for method parameters against the
|
||||
expression "^[a-z][a-zA-Z0-9]*$". -->
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="LocalFinalVariableName">
|
||||
<!-- Validates identifiers for local final variables against the
|
||||
expression "^[a-z][a-zA-Z0-9]*$". -->
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="LocalVariableName">
|
||||
<!-- Validates identifiers for local variables against the
|
||||
expression "^[a-z][a-zA-Z0-9]*$". -->
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
|
||||
<!--
|
||||
|
||||
LENGTH and CODING CHECKS
|
||||
|
||||
-->
|
||||
|
||||
|
||||
<module name="LeftCurly">
|
||||
<!-- Checks for placement of the left curly brace ('{'). -->
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="RightCurly">
|
||||
<!-- Checks right curlies on CATCH, ELSE, and TRY blocks are on
|
||||
the same line. e.g., the following example is fine:
|
||||
<pre>
|
||||
if {
|
||||
...
|
||||
} else
|
||||
</pre>
|
||||
-->
|
||||
<!-- This next example is not fine:
|
||||
<pre>
|
||||
if {
|
||||
...
|
||||
}
|
||||
else
|
||||
</pre>
|
||||
-->
|
||||
<property name="option" value="same"/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<!-- Checks for braces around if and else blocks -->
|
||||
<module name="NeedBraces">
|
||||
<property name="severity" value="warning"/>
|
||||
<property name="tokens" value="LITERAL_IF, LITERAL_ELSE, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO"/>
|
||||
</module>
|
||||
|
||||
<module name="UpperEll">
|
||||
<!-- Checks that long constants are defined with an upper ell.-->
|
||||
<property name="severity" value="error"/>
|
||||
</module>
|
||||
|
||||
<module name="FallThrough">
|
||||
<!-- Warn about falling through to the next case statement. Similar to
|
||||
javac -Xlint:fallthrough, but the check is suppressed if a single-line comment
|
||||
on the last non-blank line preceding the fallen-into case contains 'fall through' (or
|
||||
some other variants which we don't publicized to promote consistency).
|
||||
-->
|
||||
<property name="reliefPattern"
|
||||
value="fall through|Fall through|fallthru|Fallthru|falls through|Falls through|fallthrough|Fallthrough|No break|NO break|no break|continue on"/>
|
||||
<property name="severity" value="error"/>
|
||||
</module>
|
||||
|
||||
|
||||
<!--
|
||||
|
||||
MODIFIERS CHECKS
|
||||
|
||||
-->
|
||||
|
||||
<module name="ModifierOrder">
|
||||
<!-- Warn if modifier order is inconsistent with JLS3 8.1.1, 8.3.1, and
|
||||
8.4.3. The prescribed order is:
|
||||
public, protected, private, abstract, static, final, transient, volatile,
|
||||
synchronized, native, strictfp
|
||||
-->
|
||||
</module>
|
||||
|
||||
|
||||
<!--
|
||||
|
||||
WHITESPACE CHECKS
|
||||
|
||||
-->
|
||||
|
||||
<module name="WhitespaceAround">
|
||||
<!-- Checks that various tokens are surrounded by whitespace.
|
||||
This includes most binary operators and keywords followed
|
||||
by regular or curly braces.
|
||||
-->
|
||||
<property name="tokens" value="ASSIGN, BAND, BAND_ASSIGN, BOR,
|
||||
BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN,
|
||||
EQUAL, GE, GT, LAND, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE,
|
||||
LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN,
|
||||
LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS,
|
||||
MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION,
|
||||
SL, SL_ASSIGN, SR_ASSIGN, STAR, STAR_ASSIGN"/>
|
||||
<property name="severity" value="error"/>
|
||||
</module>
|
||||
|
||||
<module name="WhitespaceAfter">
|
||||
<!-- Checks that commas, semicolons and typecasts are followed by
|
||||
whitespace.
|
||||
-->
|
||||
<property name="tokens" value="COMMA, SEMI, TYPECAST"/>
|
||||
</module>
|
||||
|
||||
<module name="NoWhitespaceAfter">
|
||||
<!-- Checks that there is no whitespace after various unary operators.
|
||||
Linebreaks are allowed.
|
||||
-->
|
||||
<property name="tokens" value="BNOT, DEC, DOT, INC, LNOT, UNARY_MINUS,
|
||||
UNARY_PLUS"/>
|
||||
<property name="allowLineBreaks" value="true"/>
|
||||
<property name="severity" value="error"/>
|
||||
</module>
|
||||
|
||||
<module name="NoWhitespaceBefore">
|
||||
<!-- Checks that there is no whitespace before various unary operators.
|
||||
Linebreaks are allowed.
|
||||
-->
|
||||
<property name="tokens" value="SEMI, DOT, POST_DEC, POST_INC"/>
|
||||
<property name="allowLineBreaks" value="true"/>
|
||||
<property name="severity" value="error"/>
|
||||
</module>
|
||||
|
||||
<module name="ParenPad">
|
||||
<!-- Checks that there is no whitespace before close parens or after
|
||||
open parens.
|
||||
-->
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
</module>
|
||||
|
||||
<module name="LineLength">
|
||||
<!-- Checks if a line is too long. -->
|
||||
<property name="max" value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.max}" default="128"/>
|
||||
<property name="severity" value="error"/>
|
||||
|
||||
<!--
|
||||
The default ignore pattern exempts the following elements:
|
||||
- import statements
|
||||
- long URLs inside comments
|
||||
-->
|
||||
|
||||
<property name="ignorePattern"
|
||||
value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.ignorePattern}"
|
||||
default="^(package .*;\s*)|(import .*;\s*)|( *(\*|//).*https?://.*)$"/>
|
||||
</module>
|
||||
</module>
|
||||
|
11
gradle/quality/cyclonedx.gradle
Normal file
11
gradle/quality/cyclonedx.gradle
Normal file
|
@ -0,0 +1,11 @@
|
|||
cyclonedxBom {
|
||||
includeConfigs = [ 'runtimeClasspath' ]
|
||||
skipConfigs = [ 'compileClasspath', 'testCompileClasspath' ]
|
||||
projectType = "library"
|
||||
schemaVersion = "1.4"
|
||||
destination = file("build/reports")
|
||||
outputName = "bom"
|
||||
outputFormat = "json"
|
||||
includeBomSerialNumber = true
|
||||
componentVersion = "2.0.0"
|
||||
}
|
17
gradle/quality/pmd.gradle
Normal file
17
gradle/quality/pmd.gradle
Normal file
|
@ -0,0 +1,17 @@
|
|||
|
||||
apply plugin: 'pmd'
|
||||
|
||||
tasks.withType(Pmd) {
|
||||
ignoreFailures = true
|
||||
reports {
|
||||
xml.getRequired().set(true)
|
||||
html.getRequired().set(true)
|
||||
}
|
||||
}
|
||||
|
||||
pmd {
|
||||
ignoreFailures = true
|
||||
consoleOutput = false
|
||||
toolVersion = "6.51.0"
|
||||
ruleSetFiles = rootProject.files('gradle/quality/pmd/category/java/bestpractices.xml')
|
||||
}
|
10
gradle/quality/sonarqube.gradle
Normal file
10
gradle/quality/sonarqube.gradle
Normal file
|
@ -0,0 +1,10 @@
|
|||
|
||||
sonarqube {
|
||||
properties {
|
||||
property "sonar.projectName", "${project.group} ${project.name}"
|
||||
property "sonar.sourceEncoding", "UTF-8"
|
||||
property "sonar.tests", "src/test/java"
|
||||
property "sonar.scm.provider", "git"
|
||||
property "sonar.junit.reportsPath", "build/test-results/test/"
|
||||
}
|
||||
}
|
15
gradle/quality/spotbugs.gradle
Normal file
15
gradle/quality/spotbugs.gradle
Normal file
|
@ -0,0 +1,15 @@
|
|||
|
||||
apply plugin: 'com.github.spotbugs'
|
||||
|
||||
spotbugs {
|
||||
effort = "max"
|
||||
reportLevel = "low"
|
||||
ignoreFailures = true
|
||||
}
|
||||
|
||||
spotbugsMain {
|
||||
reports {
|
||||
xml.getRequired().set(false)
|
||||
html.getRequired().set(true)
|
||||
}
|
||||
}
|
4
gradle/repositories/maven.gradle
Normal file
4
gradle/repositories/maven.gradle
Normal file
|
@ -0,0 +1,4 @@
|
|||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
22
gradle/test/jmh.gradle
Normal file
22
gradle/test/jmh.gradle
Normal file
|
@ -0,0 +1,22 @@
|
|||
sourceSets {
|
||||
jmh {
|
||||
java.srcDirs = ['src/jmh/java']
|
||||
resources.srcDirs = ['src/jmh/resources']
|
||||
compileClasspath += sourceSets.main.runtimeClasspath
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
jmhImplementation 'org.openjdk.jmh:jmh-core:1.34'
|
||||
jmhAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.34'
|
||||
}
|
||||
|
||||
task jmh(type: JavaExec, group: 'jmh', dependsOn: jmhClasses) {
|
||||
mainClass.set('org.openjdk.jmh.Main')
|
||||
classpath = sourceSets.jmh.compileClasspath + sourceSets.jmh.runtimeClasspath
|
||||
project.file('build/reports/jmh').mkdirs()
|
||||
args '-rf', 'json'
|
||||
args '-rff', project.file('build/reports/jmh/result.json')
|
||||
}
|
||||
|
||||
classes.finalizedBy(jmhClasses)
|
34
gradle/test/junit5.gradle
Normal file
34
gradle/test/junit5.gradle
Normal file
|
@ -0,0 +1,34 @@
|
|||
dependencies {
|
||||
testImplementation testLibs.junit.jupiter.api
|
||||
testImplementation testLibs.hamcrest
|
||||
testRuntimeOnly testLibs.junit.jupiter.engine
|
||||
testRuntimeOnly testLibs.junit.jupiter.platform.launcher
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
failFast = false
|
||||
jvmArgs '--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED',
|
||||
'--add-exports=java.base/jdk.internal.ref=ALL-UNNAMED',
|
||||
'--add-exports=java.base/sun.nio.ch=ALL-UNNAMED',
|
||||
'--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED',
|
||||
'--add-opens=jdk.compiler/com.sun.tools.javac=ALL-UNNAMED',
|
||||
'--add-opens=java.base/java.lang=ALL-UNNAMED',
|
||||
'--add-opens=java.base/java.lang.reflect=ALL-UNNAMED',
|
||||
'--add-opens=java.base/java.io=ALL-UNNAMED',
|
||||
'--add-opens=java.base/java.nio=ALL-UNNAMED',
|
||||
'--add-opens=java.base/java.util=ALL-UNNAMED'
|
||||
systemProperty 'java.util.logging.config.file', 'src/test/resources/logging.properties'
|
||||
testLogging {
|
||||
events 'STARTED', 'PASSED', 'FAILED', 'SKIPPED'
|
||||
}
|
||||
afterSuite { desc, result ->
|
||||
if (!desc.parent) {
|
||||
println "\nTest result: ${result.resultType}"
|
||||
println "Test summary: ${result.testCount} tests, " +
|
||||
"${result.successfulTestCount} succeeded, " +
|
||||
"${result.failedTestCount} failed, " +
|
||||
"${result.skippedTestCount} skipped"
|
||||
}
|
||||
}
|
||||
}
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
249
gradlew
vendored
Executable file
249
gradlew
vendored
Executable file
|
@ -0,0 +1,249 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
92
gradlew.bat
vendored
Normal file
92
gradlew.bat
vendored
Normal file
|
@ -0,0 +1,92 @@
|
|||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
7
net-ldap/build.gradle
Normal file
7
net-ldap/build.gradle
Normal file
|
@ -0,0 +1,7 @@
|
|||
|
||||
dependencies {
|
||||
api project(':asn1')
|
||||
api libs.netty.handler
|
||||
api libs.netty.epoll
|
||||
api libs.netty.kqueue
|
||||
}
|
11
net-ldap/src/main/java/module-info.java
Normal file
11
net-ldap/src/main/java/module-info.java
Normal file
|
@ -0,0 +1,11 @@
|
|||
module org.xbib.net.ldap {
|
||||
requires java.security.sasl;
|
||||
requires java.naming;
|
||||
requires jdk.security.auth;
|
||||
requires io.netty.buffer;
|
||||
requires io.netty.handler;
|
||||
requires io.netty.transport;
|
||||
requires io.netty.codec;
|
||||
requires io.netty.common;
|
||||
requires org.xbib.net.ldap.asnone;
|
||||
}
|
103
net-ldap/src/main/java/org/xbib/net/ldap/AbandonRequest.java
Normal file
103
net-ldap/src/main/java/org/xbib/net/ldap/AbandonRequest.java
Normal file
|
@ -0,0 +1,103 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import org.xbib.asn1.ApplicationDERTag;
|
||||
import org.xbib.asn1.DEREncoder;
|
||||
import org.xbib.asn1.IntegerType;
|
||||
|
||||
/**
|
||||
* LDAP abandon request defined as:
|
||||
*
|
||||
* <pre>
|
||||
* AbandonRequest ::= [APPLICATION 16] MessageID
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public class AbandonRequest extends AbstractRequestMessage {
|
||||
|
||||
/**
|
||||
* Protocol operation identifier.
|
||||
*/
|
||||
public static final int PROTOCOL_OP = 16;
|
||||
|
||||
/**
|
||||
* Protocol message ID.
|
||||
*/
|
||||
private int messageID;
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
private AbandonRequest() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new abandon request.
|
||||
*
|
||||
* @param id message ID
|
||||
*/
|
||||
public AbandonRequest(final int id) {
|
||||
messageID = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for this class.
|
||||
*
|
||||
* @return new builder
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public int getMessageID() {
|
||||
return messageID;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DEREncoder[] getRequestEncoders(final int id) {
|
||||
return new DEREncoder[]{
|
||||
new IntegerType(id),
|
||||
new IntegerType(new ApplicationDERTag(PROTOCOL_OP, false), messageID),
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ", " +
|
||||
"messageID=" + messageID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abandon request builder.
|
||||
*/
|
||||
public static class Builder extends AbstractRequestMessage.AbstractBuilder<AbandonRequest.Builder, AbandonRequest> {
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
protected Builder() {
|
||||
super(new AbandonRequest());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Builder self() {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the message ID.
|
||||
*
|
||||
* @param id message ID
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder id(final int id) {
|
||||
object.messageID = id;
|
||||
return self();
|
||||
}
|
||||
}
|
||||
}
|
45
net-ldap/src/main/java/org/xbib/net/ldap/AbstractConfig.java
Normal file
45
net-ldap/src/main/java/org/xbib/net/ldap/AbstractConfig.java
Normal file
|
@ -0,0 +1,45 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
/**
|
||||
* Provides common implementations for configuration objects.
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractConfig {
|
||||
|
||||
/**
|
||||
* Verifies that an array does not contain a null element.
|
||||
*
|
||||
* @param array to verify
|
||||
* @throws IllegalArgumentException if the array contains null
|
||||
*/
|
||||
protected void checkArrayContainsNull(final Object[] array) {
|
||||
if (array != null) {
|
||||
for (Object o : array) {
|
||||
if (o == null) {
|
||||
throw new IllegalArgumentException("Array element cannot be null");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies that a string is not null or empty.
|
||||
*
|
||||
* @param s to verify
|
||||
* @param allowNull whether null strings are valid
|
||||
* @throws IllegalArgumentException if the string is null or empty
|
||||
*/
|
||||
protected void checkStringInput(final String s, final boolean allowNull) {
|
||||
if (allowNull) {
|
||||
if ("".equals(s)) {
|
||||
throw new IllegalArgumentException("Input cannot be empty");
|
||||
}
|
||||
} else {
|
||||
if (s == null || "".equals(s)) {
|
||||
throw new IllegalArgumentException("Input cannot be null or empty");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Base class for connection strategy implementations.
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractConnectionStrategy implements ConnectionStrategy {
|
||||
|
||||
/**
|
||||
* Set of LDAP URLs to attempt connections to.
|
||||
*/
|
||||
protected LdapURLSet ldapURLSet;
|
||||
|
||||
/**
|
||||
* Whether this strategy has been successfully initialized.
|
||||
*/
|
||||
private boolean initialized;
|
||||
|
||||
/**
|
||||
* Condition used to determine whether to activate a URL.
|
||||
*/
|
||||
private Predicate<LdapURL> activateCondition;
|
||||
|
||||
/**
|
||||
* Condition used to determine whether to test an inactive URL.
|
||||
*/
|
||||
private Predicate<LdapURL> retryCondition = new Predicate<>() {
|
||||
@Override
|
||||
public boolean test(final LdapURL url) {
|
||||
return Instant.now().isAfter(url.getRetryMetadata().getFailureTime().plus(LdapURLActivatorService.getPeriod()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DEFAULT_RETRY_CONDITION";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public synchronized void initialize(final String urls, final Predicate<LdapURL> condition) {
|
||||
if (isInitialized()) {
|
||||
throw new IllegalStateException("Strategy has already been initialized");
|
||||
}
|
||||
ldapURLSet = new LdapURLSet(this, urls);
|
||||
activateCondition = condition;
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void populate(final String urls, final LdapURLSet urlSet) {
|
||||
if (urls == null || urls.isEmpty()) {
|
||||
throw new IllegalArgumentException("urls cannot be empty or null");
|
||||
}
|
||||
if (urls.contains(" ")) {
|
||||
urlSet.populate(Stream.of(urls.split(" "))
|
||||
.map(s -> {
|
||||
final LdapURL url = new LdapURL(s);
|
||||
url.setRetryMetadata(new LdapURLRetryMetadata(this));
|
||||
return url;
|
||||
}).collect(Collectors.toList()));
|
||||
} else {
|
||||
final LdapURL url = new LdapURL(urls);
|
||||
url.setRetryMetadata(new LdapURLRetryMetadata(this));
|
||||
urlSet.populate(Collections.singletonList(url));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Predicate<LdapURL> getActivateCondition() {
|
||||
return activateCondition;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Predicate<LdapURL> getRetryCondition() {
|
||||
return retryCondition;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the retry condition which determines whether an attempt should be made to activate a URL.
|
||||
*
|
||||
* @param condition that determines whether to test an inactive URL
|
||||
*/
|
||||
public void setRetryCondition(final Predicate<LdapURL> condition) {
|
||||
retryCondition = condition;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void success(final LdapURL url) {
|
||||
url.activate();
|
||||
url.getRetryMetadata().recordSuccess(Instant.now());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void failure(final LdapURL url) {
|
||||
url.deactivate();
|
||||
url.getRetryMetadata().recordFailure(Instant.now());
|
||||
LdapURLActivatorService.getInstance().registerUrl(url);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" +
|
||||
getClass().getName() + "@" + hashCode() + "::" +
|
||||
"ldapURLSet=" + ldapURLSet + ", " +
|
||||
"activateCondition=" + activateCondition + ", " +
|
||||
"retryCondition=" + retryCondition + ", " +
|
||||
"initialized=" + initialized + "]";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Default iterator implementation.
|
||||
*/
|
||||
protected static class DefaultLdapURLIterator implements Iterator<LdapURL> {
|
||||
|
||||
/**
|
||||
* URLs to iterate over.
|
||||
*/
|
||||
private final List<LdapURL> ldapUrls;
|
||||
|
||||
/**
|
||||
* Iterator index.
|
||||
*/
|
||||
private int i;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new default LDAP URL iterator.
|
||||
*
|
||||
* @param urls to iterate over
|
||||
*/
|
||||
public DefaultLdapURLIterator(final List<LdapURL> urls) {
|
||||
ldapUrls = urls;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return i < ldapUrls.size();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public LdapURL next() {
|
||||
return ldapUrls.get(i++);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,290 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Base class for connection validator implementations.
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractConnectionValidator implements ConnectionValidator {
|
||||
|
||||
/**
|
||||
* Default validation period, value is 30 minutes.
|
||||
*/
|
||||
public static final Duration DEFAULT_VALIDATE_PERIOD = Duration.ofMinutes(30);
|
||||
|
||||
/**
|
||||
* Default per connection validate timeout, value is 5 seconds.
|
||||
*/
|
||||
public static final Duration DEFAULT_VALIDATE_TIMEOUT = Duration.ofSeconds(5);
|
||||
|
||||
/**
|
||||
* Validation period.
|
||||
*/
|
||||
private Duration validatePeriod;
|
||||
|
||||
/**
|
||||
* Maximum length of time a connection validation should block.
|
||||
*/
|
||||
private Duration validateTimeout;
|
||||
|
||||
/**
|
||||
* Consumer to execute on a successful validation.
|
||||
*/
|
||||
private Consumer<Connection> onSuccess;
|
||||
|
||||
/**
|
||||
* Consumer to execute on a failed validation.
|
||||
*/
|
||||
private Consumer<Connection> onFailure;
|
||||
|
||||
/**
|
||||
* Whether the occurrence of a timeout should result in a validation failure.
|
||||
*/
|
||||
private boolean timeoutIsFailure = true;
|
||||
|
||||
|
||||
@Override
|
||||
public Duration getValidatePeriod() {
|
||||
return validatePeriod;
|
||||
}
|
||||
|
||||
|
||||
public void setValidatePeriod(final Duration period) {
|
||||
if (period == null || period.isNegative() || period.isZero()) {
|
||||
throw new IllegalArgumentException("Period cannot be null, negative or zero");
|
||||
}
|
||||
validatePeriod = period;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getValidateTimeout() {
|
||||
return validateTimeout;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the validate timeout.
|
||||
*
|
||||
* @param timeout to set
|
||||
*/
|
||||
public void setValidateTimeout(final Duration timeout) {
|
||||
if (timeout == null || timeout.isNegative()) {
|
||||
throw new IllegalArgumentException("Timeout cannot be null or negative");
|
||||
}
|
||||
validateTimeout = timeout;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a consumer to handle a connection that has been successfully validated.
|
||||
*
|
||||
* @return success consumer
|
||||
*/
|
||||
public Consumer<Connection> getOnSuccess() {
|
||||
return onSuccess;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets a consumer to handle a connection that has been successfully validated.
|
||||
*
|
||||
* @param consumer to invoke on success
|
||||
*/
|
||||
public void setOnSuccess(final Consumer<Connection> consumer) {
|
||||
onSuccess = consumer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a consumer to handle a connection that has failed validation.
|
||||
*
|
||||
* @return failure consumer
|
||||
*/
|
||||
public Consumer<Connection> getOnFailure() {
|
||||
return onFailure;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets a consumer to handle a connection that has failed validation.
|
||||
*
|
||||
* @param consumer to invoke on failure
|
||||
*/
|
||||
public void setOnFailure(final Consumer<Connection> consumer) {
|
||||
onFailure = consumer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether a timeout should be considered a validation failure.
|
||||
*
|
||||
* @return whether a timeout should be considered a validation failure
|
||||
*/
|
||||
public boolean getTimeoutIsFailure() {
|
||||
return timeoutIsFailure;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether a timeout should be considered a validation failure.
|
||||
*
|
||||
* @param failure whether a timeout should be considered a validation failure
|
||||
*/
|
||||
public void setTimeoutIsFailure(final boolean failure) {
|
||||
timeoutIsFailure = failure;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Boolean apply(final Connection conn) {
|
||||
if (conn == null) {
|
||||
if (onFailure != null) {
|
||||
onFailure.accept(null);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
final Boolean result = applyAsync(conn).get();
|
||||
if (result && onSuccess != null) {
|
||||
onSuccess.accept(conn);
|
||||
} else if (!result && onFailure != null) {
|
||||
onFailure.accept(conn);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Supplier<Boolean> applyAsync(final Connection conn) {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final AtomicBoolean result = new AtomicBoolean();
|
||||
applyAsync(conn, value -> {
|
||||
result.compareAndSet(false, value);
|
||||
latch.countDown();
|
||||
});
|
||||
return () -> {
|
||||
try {
|
||||
if (Duration.ZERO.equals(getValidateTimeout())) {
|
||||
// waits indefinitely for the validation response
|
||||
latch.await();
|
||||
} else {
|
||||
if (!latch.await(getValidateTimeout().toMillis(), TimeUnit.MILLISECONDS) && !timeoutIsFailure) {
|
||||
result.compareAndSet(false, true);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
//
|
||||
}
|
||||
return result.get();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return
|
||||
getClass().getName() + "@" + hashCode() + "::" +
|
||||
"validatePeriod=" + validatePeriod + ", " +
|
||||
"validateTimeout=" + validateTimeout + ", " +
|
||||
"onSuccess=" + onSuccess + ", " +
|
||||
"onFailure=" + onFailure + ", " +
|
||||
"timeoutIsFailure=" + timeoutIsFailure;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Base class for validator builders.
|
||||
*
|
||||
* @param <B> type of builder
|
||||
* @param <T> type of validator
|
||||
*/
|
||||
protected abstract static class AbstractBuilder<B, T extends AbstractConnectionValidator> {
|
||||
|
||||
/**
|
||||
* Validator to build.
|
||||
*/
|
||||
protected final T object;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new abstract builder.
|
||||
*
|
||||
* @param t validator to build
|
||||
*/
|
||||
protected AbstractBuilder(final T t) {
|
||||
object = t;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns this builder.
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
protected abstract B self();
|
||||
|
||||
|
||||
/**
|
||||
* Sets the validation period.
|
||||
*
|
||||
* @param period to set
|
||||
* @return this builder
|
||||
*/
|
||||
public B period(final Duration period) {
|
||||
object.setValidatePeriod(period);
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the validation timeout.
|
||||
*
|
||||
* @param timeout to set
|
||||
* @return this builder
|
||||
*/
|
||||
public B timeout(final Duration timeout) {
|
||||
object.setValidateTimeout(timeout);
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
public B onSuccess(final Consumer<Connection> consumer) {
|
||||
object.setOnSuccess(consumer);
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
public B onFailure(final Consumer<Connection> consumer) {
|
||||
object.setOnFailure(consumer);
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether timeout is a validation failure.
|
||||
*
|
||||
* @param failure whether timeout is a validation failure
|
||||
* @return this builder
|
||||
*/
|
||||
public B timeoutIsFailure(final boolean failure) {
|
||||
object.setTimeoutIsFailure(failure);
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the connection validator.
|
||||
*
|
||||
* @return connection validator
|
||||
*/
|
||||
public T build() {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
}
|
313
net-ldap/src/main/java/org/xbib/net/ldap/AbstractMessage.java
Normal file
313
net-ldap/src/main/java/org/xbib/net/ldap/AbstractMessage.java
Normal file
|
@ -0,0 +1,313 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.xbib.asn1.AbstractParseHandler;
|
||||
import org.xbib.asn1.BooleanType;
|
||||
import org.xbib.asn1.DERBuffer;
|
||||
import org.xbib.asn1.DERParser;
|
||||
import org.xbib.asn1.DERPath;
|
||||
import org.xbib.asn1.IntegerType;
|
||||
import org.xbib.asn1.OctetStringType;
|
||||
import org.xbib.net.ldap.control.ControlFactory;
|
||||
import org.xbib.net.ldap.control.ResponseControl;
|
||||
|
||||
/**
|
||||
* LDAP message envelope defined as:
|
||||
*
|
||||
* <pre>
|
||||
* LDAPMessage ::= SEQUENCE {
|
||||
* messageID MessageID,
|
||||
* protocolOp CHOICE {
|
||||
* ...,
|
||||
* controls [0] Controls OPTIONAL }
|
||||
*
|
||||
* Control ::= SEQUENCE {
|
||||
* controlType LDAPOID,
|
||||
* criticality BOOLEAN DEFAULT FALSE,
|
||||
* controlValue OCTET STRING OPTIONAL }
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractMessage implements Message {
|
||||
|
||||
/**
|
||||
* Protocol message ID.
|
||||
*/
|
||||
private int messageID;
|
||||
|
||||
/**
|
||||
* LDAP controls.
|
||||
*/
|
||||
private List<ResponseControl> controls = new ArrayList<>();
|
||||
|
||||
|
||||
@Override
|
||||
public int getMessageID() {
|
||||
return messageID;
|
||||
}
|
||||
|
||||
|
||||
public void setMessageID(final int id) {
|
||||
messageID = id;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ResponseControl[] getControls() {
|
||||
return controls != null ? controls.toArray(new ResponseControl[0]) : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds the supplied controls to this message.
|
||||
*
|
||||
* @param cntrls to add
|
||||
*/
|
||||
public void addControls(final ResponseControl... cntrls) {
|
||||
Collections.addAll(controls, cntrls);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copies the property values from the supplied message to this message.
|
||||
*
|
||||
* @param <T> type of message
|
||||
* @param message to copy from
|
||||
*/
|
||||
protected <T extends Message> void copyValues(final T message) {
|
||||
setMessageID(message.getMessageID());
|
||||
addControls(message.getControls());
|
||||
}
|
||||
|
||||
|
||||
// CheckStyle:EqualsHashCode OFF
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
if (o instanceof AbstractMessage) {
|
||||
final AbstractMessage v = (AbstractMessage) o;
|
||||
return LdapUtils.areEqual(getMessageID(), v.getMessageID()) &&
|
||||
LdapUtils.areEqual(getControls(), v.getControls());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// CheckStyle:EqualsHashCode ON
|
||||
|
||||
|
||||
/**
|
||||
* Returns the hash code for this object.
|
||||
*
|
||||
* @return hash code
|
||||
*/
|
||||
@Override
|
||||
public abstract int hashCode();
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getName() + "@" + hashCode() + "::" + "messageID=" + messageID + ", " + "controls=" + controls;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse handler implementation for the message ID.
|
||||
*/
|
||||
protected static class MessageIDHandler extends AbstractParseHandler<AbstractMessage> {
|
||||
|
||||
/**
|
||||
* DER path to message id.
|
||||
*/
|
||||
public static final DERPath PATH = new DERPath("/SEQ/INT[0]");
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new message ID handler.
|
||||
*
|
||||
* @param response to configure
|
||||
*/
|
||||
public MessageIDHandler(final AbstractMessage response) {
|
||||
super(response);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handle(final DERParser parser, final DERBuffer encoded) {
|
||||
getObject().setMessageID(IntegerType.decodeUnsignedPrimitive(encoded));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse handler implementation for the message controls.
|
||||
*/
|
||||
protected static class ControlsHandler extends AbstractParseHandler<AbstractMessage> {
|
||||
|
||||
/**
|
||||
* DER path to controls.
|
||||
*/
|
||||
public static final DERPath PATH = new DERPath("/SEQ/CTX(0)/SEQ");
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new controls handler.
|
||||
*
|
||||
* @param response to configure
|
||||
*/
|
||||
public ControlsHandler(final AbstractMessage response) {
|
||||
super(response);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handle(final DERParser parser, final DERBuffer encoded) {
|
||||
final ControlParser p = new ControlParser();
|
||||
p.parse(encoded);
|
||||
getObject().addControls(
|
||||
ControlFactory.createResponseControl(
|
||||
p.getOid().isPresent() ? p.getOid().get() : null,
|
||||
p.getCritical().isPresent() ? p.getCritical().get() : false,
|
||||
p.getValue().isPresent() ? p.getValue().get() : null));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses a buffer containing an LDAP control.
|
||||
*/
|
||||
protected static class ControlParser {
|
||||
|
||||
/**
|
||||
* DER path to criticality.
|
||||
*/
|
||||
private static final DERPath CRITICAL_PATH = new DERPath("/BOOL[1]");
|
||||
|
||||
/**
|
||||
* DER path to OID.
|
||||
*/
|
||||
private static final DERPath OID_PATH = new DERPath("/OCTSTR[0]");
|
||||
|
||||
/**
|
||||
* DER path to value.
|
||||
*/
|
||||
private static final DERPath VALUE_PATH = new DERPath("/OCTSTR[1]");
|
||||
|
||||
/**
|
||||
* DER path to alternate value.
|
||||
*/
|
||||
private static final DERPath ALT_VALUE_PATH = new DERPath("/OCTSTR[2]");
|
||||
|
||||
/**
|
||||
* Parser for decoding LDAP controls.
|
||||
*/
|
||||
private final DERParser parser = new DERParser();
|
||||
|
||||
/**
|
||||
* Control criticality.
|
||||
*/
|
||||
private Boolean critical;
|
||||
|
||||
/**
|
||||
* Control oid.
|
||||
*/
|
||||
private String oid;
|
||||
|
||||
/**
|
||||
* Control value.
|
||||
*/
|
||||
private DERBuffer value;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new control parser.
|
||||
*/
|
||||
public ControlParser() {
|
||||
parser.registerHandler(CRITICAL_PATH, (p, e) -> critical = BooleanType.decode(e));
|
||||
parser.registerHandler(OID_PATH, (p, e) -> oid = OctetStringType.decode(e));
|
||||
parser.registerHandler(VALUE_PATH, (p, e) -> value = e.slice());
|
||||
parser.registerHandler(ALT_VALUE_PATH, (p, e) -> {
|
||||
if (value == null) {
|
||||
value = e.slice();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Examines the supplied buffer and parses an LDAP control if one is found.
|
||||
*
|
||||
* @param buffer to parse
|
||||
*/
|
||||
public void parse(final DERBuffer buffer) {
|
||||
parser.parse(buffer);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the control criticality.
|
||||
*
|
||||
* @return criticality or empty
|
||||
*/
|
||||
public Optional<Boolean> getCritical() {
|
||||
return Optional.ofNullable(critical);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the control oid.
|
||||
*
|
||||
* @return control oid or empty
|
||||
*/
|
||||
public Optional<String> getOid() {
|
||||
return Optional.ofNullable(oid);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the control value.
|
||||
*
|
||||
* @return control value or empty
|
||||
*/
|
||||
public Optional<DERBuffer> getValue() {
|
||||
return Optional.ofNullable(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// CheckStyle:OFF
|
||||
protected abstract static class AbstractBuilder<B, T extends AbstractMessage> {
|
||||
|
||||
protected final T object;
|
||||
|
||||
|
||||
protected AbstractBuilder(final T t) {
|
||||
object = t;
|
||||
}
|
||||
|
||||
|
||||
protected abstract B self();
|
||||
|
||||
|
||||
public B messageID(final int id) {
|
||||
object.setMessageID(id);
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
public B controls(final ResponseControl... controls) {
|
||||
object.addControls(controls);
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
public T build() {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
// CheckStyle:ON
|
||||
}
|
378
net-ldap/src/main/java/org/xbib/net/ldap/AbstractOperation.java
Normal file
378
net-ldap/src/main/java/org/xbib/net/ldap/AbstractOperation.java
Normal file
|
@ -0,0 +1,378 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import java.util.Arrays;
|
||||
import org.xbib.net.ldap.handler.ExceptionHandler;
|
||||
import org.xbib.net.ldap.handler.IntermediateResponseHandler;
|
||||
import org.xbib.net.ldap.handler.ReferralHandler;
|
||||
import org.xbib.net.ldap.handler.RequestHandler;
|
||||
import org.xbib.net.ldap.handler.ResponseControlHandler;
|
||||
import org.xbib.net.ldap.handler.ResultHandler;
|
||||
import org.xbib.net.ldap.handler.ResultPredicate;
|
||||
import org.xbib.net.ldap.handler.UnsolicitedNotificationHandler;
|
||||
|
||||
/**
|
||||
* Base class for operations.
|
||||
*
|
||||
* @param <Q> type of request
|
||||
* @param <S> type of result
|
||||
*/
|
||||
public abstract class AbstractOperation<Q extends Request, S extends Result> implements Operation<Q, S> {
|
||||
|
||||
/**
|
||||
* Connection factory.
|
||||
*/
|
||||
private ConnectionFactory connectionFactory;
|
||||
|
||||
/**
|
||||
* Functions to handle requests.
|
||||
*/
|
||||
private RequestHandler<Q>[] requestHandlers;
|
||||
|
||||
/**
|
||||
* Functions to handle response results.
|
||||
*/
|
||||
private ResultHandler[] resultHandlers;
|
||||
|
||||
/**
|
||||
* Functions to handle response controls.
|
||||
*/
|
||||
private ResponseControlHandler[] controlHandlers;
|
||||
|
||||
/**
|
||||
* Functions to handle referrals.
|
||||
*/
|
||||
private ReferralHandler[] referralHandlers;
|
||||
|
||||
/**
|
||||
* Functions to handle intermediate responses.
|
||||
*/
|
||||
private IntermediateResponseHandler[] intermediateResponseHandlers;
|
||||
|
||||
/**
|
||||
* Function to handle exceptions.
|
||||
*/
|
||||
private ExceptionHandler exceptionHandler;
|
||||
|
||||
/**
|
||||
* Function to test results.
|
||||
*/
|
||||
private ResultPredicate throwCondition;
|
||||
|
||||
/**
|
||||
* Functions to handle unsolicited notifications.
|
||||
*/
|
||||
private UnsolicitedNotificationHandler[] unsolicitedNotificationHandlers;
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public AbstractOperation() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new abstract operation.
|
||||
*
|
||||
* @param factory connection factory
|
||||
*/
|
||||
public AbstractOperation(final ConnectionFactory factory) {
|
||||
setConnectionFactory(factory);
|
||||
}
|
||||
|
||||
|
||||
public ConnectionFactory getConnectionFactory() {
|
||||
return connectionFactory;
|
||||
}
|
||||
|
||||
|
||||
public void setConnectionFactory(final ConnectionFactory factory) {
|
||||
connectionFactory = factory;
|
||||
}
|
||||
|
||||
|
||||
public RequestHandler<Q>[] getRequestHandlers() {
|
||||
return requestHandlers;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setRequestHandlers(final RequestHandler<Q>... handlers) {
|
||||
requestHandlers = handlers;
|
||||
}
|
||||
|
||||
|
||||
public ResultHandler[] getResultHandlers() {
|
||||
return resultHandlers;
|
||||
}
|
||||
|
||||
|
||||
public void setResultHandlers(final ResultHandler... handlers) {
|
||||
resultHandlers = handlers;
|
||||
}
|
||||
|
||||
|
||||
public ResponseControlHandler[] getControlHandlers() {
|
||||
return controlHandlers;
|
||||
}
|
||||
|
||||
|
||||
public void setControlHandlers(final ResponseControlHandler... handlers) {
|
||||
controlHandlers = handlers;
|
||||
}
|
||||
|
||||
|
||||
public ReferralHandler[] getReferralHandlers() {
|
||||
return referralHandlers;
|
||||
}
|
||||
|
||||
|
||||
public void setReferralHandlers(final ReferralHandler... handlers) {
|
||||
referralHandlers = handlers;
|
||||
}
|
||||
|
||||
|
||||
public IntermediateResponseHandler[] getIntermediateResponseHandlers() {
|
||||
return intermediateResponseHandlers;
|
||||
}
|
||||
|
||||
|
||||
public void setIntermediateResponseHandlers(final IntermediateResponseHandler... handlers) {
|
||||
intermediateResponseHandlers = handlers;
|
||||
}
|
||||
|
||||
|
||||
public ExceptionHandler getExceptionHandler() {
|
||||
return exceptionHandler;
|
||||
}
|
||||
|
||||
|
||||
public void setExceptionHandler(final ExceptionHandler handler) {
|
||||
exceptionHandler = handler;
|
||||
}
|
||||
|
||||
|
||||
public ResultPredicate getThrowCondition() {
|
||||
return throwCondition;
|
||||
}
|
||||
|
||||
|
||||
public void setThrowCondition(final ResultPredicate function) {
|
||||
throwCondition = function;
|
||||
}
|
||||
|
||||
|
||||
public UnsolicitedNotificationHandler[] getUnsolicitedNotificationHandlers() {
|
||||
return unsolicitedNotificationHandlers;
|
||||
}
|
||||
|
||||
|
||||
public void setUnsolicitedNotificationHandlers(final UnsolicitedNotificationHandler... handlers) {
|
||||
unsolicitedNotificationHandlers = handlers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Applies any configured request handlers to the supplied request. Returns the supplied request unaltered if no
|
||||
* request handlers are configured.
|
||||
*
|
||||
* @param request to configure
|
||||
* @return configured request
|
||||
*/
|
||||
protected Q configureRequest(final Q request) {
|
||||
if (requestHandlers == null || requestHandlers.length == 0) {
|
||||
return request;
|
||||
}
|
||||
Q req = request;
|
||||
for (RequestHandler<Q> func : requestHandlers) {
|
||||
req = func.apply(req);
|
||||
}
|
||||
return req;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds configured functions to the supplied handle.
|
||||
*
|
||||
* @param handle to configure
|
||||
* @return configured handle
|
||||
*/
|
||||
protected OperationHandle<Q, S> configureHandle(final OperationHandle<Q, S> handle) {
|
||||
return handle
|
||||
.onControl(getControlHandlers())
|
||||
.onReferral(getReferralHandlers())
|
||||
.onIntermediate(getIntermediateResponseHandlers())
|
||||
.onException(getExceptionHandler())
|
||||
.throwIf(getThrowCondition())
|
||||
.onUnsolicitedNotification(getUnsolicitedNotificationHandlers())
|
||||
.onResult(getResultHandlers());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getName() + "@" + hashCode() + "::" +
|
||||
"connectionFactory=" + connectionFactory + ", " +
|
||||
"requestHandlers=" + Arrays.toString(requestHandlers) + ", " +
|
||||
"resultHandlers=" + Arrays.toString(resultHandlers) + ", " +
|
||||
"controlHandlers=" + Arrays.toString(controlHandlers) + ", " +
|
||||
"referralHandlers=" + Arrays.toString(referralHandlers) + ", " +
|
||||
"intermediateResponseHandlers=" + Arrays.toString(intermediateResponseHandlers) + ", " +
|
||||
"exceptionHandler=" + exceptionHandler + ", " +
|
||||
"throwCondition=" + throwCondition + ", " +
|
||||
"unsolicitedNotificationHandlers=" + Arrays.toString(unsolicitedNotificationHandlers);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Base class for operation builders.
|
||||
*
|
||||
* @param <B> type of builder
|
||||
* @param <T> type of operation
|
||||
*/
|
||||
protected abstract static class AbstractBuilder<B, T extends AbstractOperation> {
|
||||
|
||||
/**
|
||||
* Operation to build.
|
||||
*/
|
||||
protected final T object;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new abstract builder.
|
||||
*
|
||||
* @param t operation to build
|
||||
*/
|
||||
protected AbstractBuilder(final T t) {
|
||||
object = t;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns this builder.
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
protected abstract B self();
|
||||
|
||||
|
||||
/**
|
||||
* Sets the connection factory.
|
||||
*
|
||||
* @param factory to set
|
||||
* @return this builder
|
||||
*/
|
||||
public B factory(final ConnectionFactory factory) {
|
||||
object.setConnectionFactory(factory);
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the functions to execute before a request is sent.
|
||||
*
|
||||
* @param handlers to execute on a request
|
||||
* @return this builder
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public B onRequest(final RequestHandler... handlers) {
|
||||
object.setRequestHandlers(handlers);
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the functions to execute when a result is received.
|
||||
*
|
||||
* @param handlers to execute on a result
|
||||
* @return this builder
|
||||
*/
|
||||
public B onResult(final ResultHandler... handlers) {
|
||||
object.setResultHandlers(handlers);
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the functions to execute when a control is received.
|
||||
*
|
||||
* @param handlers to execute on a control
|
||||
* @return this builder
|
||||
*/
|
||||
public B onControl(final ResponseControlHandler... handlers) {
|
||||
object.setControlHandlers(handlers);
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the functions to execute when a referral is received.
|
||||
*
|
||||
* @param handlers to execute on a referral
|
||||
* @return this builder
|
||||
*/
|
||||
public B onReferral(final ReferralHandler... handlers) {
|
||||
object.setReferralHandlers(handlers);
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the functions to execute when an intermediate response is received.
|
||||
*
|
||||
* @param handlers to execute on an intermediate response
|
||||
* @return this builder
|
||||
*/
|
||||
public B onIntermediate(final IntermediateResponseHandler... handlers) {
|
||||
object.setIntermediateResponseHandlers(handlers);
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the functions to execute when an unsolicited notification is received.
|
||||
*
|
||||
* @param handlers to execute on an unsolicited notification
|
||||
* @return this builder
|
||||
*/
|
||||
public B onUnsolicitedNotification(final UnsolicitedNotificationHandler... handlers) {
|
||||
object.setUnsolicitedNotificationHandlers(handlers);
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the function to execute when an exception occurs.
|
||||
*
|
||||
* @param handler to execute on an exception occurs
|
||||
* @return this builder
|
||||
*/
|
||||
public B onException(final ExceptionHandler handler) {
|
||||
object.setExceptionHandler(handler);
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the function to test a result.
|
||||
*
|
||||
* @param function to test a result
|
||||
* @return this builder
|
||||
*/
|
||||
public B throwIf(final ResultPredicate function) {
|
||||
object.setThrowCondition(function);
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the operation.
|
||||
*
|
||||
* @return operation
|
||||
*/
|
||||
public T build() {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Base class for validators that use an operation to perform validation. By default, validation is considered
|
||||
* successful if the operation result contains any result code. Stricter validation can be configured by setting {@link
|
||||
* #validResultCodes}.
|
||||
*
|
||||
* @param <Q> type of request
|
||||
* @param <S> type of result
|
||||
*/
|
||||
public abstract class AbstractOperationConnectionValidator<Q extends Request, S extends Result>
|
||||
extends AbstractConnectionValidator {
|
||||
|
||||
/**
|
||||
* Operation request.
|
||||
*/
|
||||
private Q request;
|
||||
|
||||
/**
|
||||
* Valid result codes.
|
||||
*/
|
||||
private ResultCode[] validResultCodes;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the operation request.
|
||||
*
|
||||
* @return operation request
|
||||
*/
|
||||
public Q getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the operation request.
|
||||
*
|
||||
* @param req operation request
|
||||
*/
|
||||
public void setRequest(final Q req) {
|
||||
request = req;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the valid result codes.
|
||||
*
|
||||
* @return valid result codes
|
||||
*/
|
||||
public ResultCode[] getValidResultCodes() {
|
||||
return validResultCodes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the valid result codes.
|
||||
*
|
||||
* @param codes that represent a valid connection
|
||||
*/
|
||||
public void setValidResultCodes(final ResultCode... codes) {
|
||||
validResultCodes = codes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Perform the operation for this validator.
|
||||
*
|
||||
* @param conn to validate
|
||||
* @return operation handle
|
||||
*/
|
||||
protected abstract OperationHandle<Q, S> performOperation(Connection conn);
|
||||
|
||||
|
||||
@Override
|
||||
public void applyAsync(final Connection conn, final Consumer<Boolean> function) {
|
||||
if (conn == null) {
|
||||
function.accept(false);
|
||||
} else {
|
||||
final OperationHandle<Q, S> h = performOperation(conn);
|
||||
h.onResult(r -> {
|
||||
if (validResultCodes != null) {
|
||||
function.accept(Arrays.stream(validResultCodes).anyMatch(rc -> rc.equals(r.getResultCode())));
|
||||
} else {
|
||||
function.accept(r.getResultCode() != null);
|
||||
}
|
||||
});
|
||||
h.onException(e -> {
|
||||
if (e != null && e.getResultCode() == ResultCode.LDAP_TIMEOUT && !getTimeoutIsFailure()) {
|
||||
function.accept(true);
|
||||
} else {
|
||||
function.accept(false);
|
||||
}
|
||||
});
|
||||
h.send();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ", request=" + request + ", validResultCodes=" + Arrays.toString(validResultCodes);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Base class for operation validator builders.
|
||||
*
|
||||
* @param <Q> type of request
|
||||
* @param <S> type of result
|
||||
* @param <B> type of builder
|
||||
* @param <T> type of validator
|
||||
*/
|
||||
protected abstract static class AbstractBuilder
|
||||
<Q extends Request, S extends Result, B, T extends AbstractOperationConnectionValidator<Q, S>> extends
|
||||
AbstractConnectionValidator.AbstractBuilder<B, T> {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new abstract builder.
|
||||
*
|
||||
* @param t validator to build
|
||||
*/
|
||||
protected AbstractBuilder(final T t) {
|
||||
super(t);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns this builder.
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
protected abstract B self();
|
||||
|
||||
|
||||
/**
|
||||
* Sets the request to use for validation.
|
||||
*
|
||||
* @param request operation request
|
||||
* @return this builder
|
||||
*/
|
||||
public B request(final Q request) {
|
||||
object.setRequest(request);
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the result codes to use for validation.
|
||||
*
|
||||
* @param codes valid result codes
|
||||
* @return this builder
|
||||
*/
|
||||
public B validResultCodes(final ResultCode... codes) {
|
||||
object.setValidResultCodes(codes);
|
||||
return self();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import org.xbib.asn1.BooleanType;
|
||||
import org.xbib.asn1.ConstructedDEREncoder;
|
||||
import org.xbib.asn1.ContextDERTag;
|
||||
import org.xbib.asn1.DEREncoder;
|
||||
import org.xbib.asn1.OctetStringType;
|
||||
import org.xbib.asn1.UniversalDERTag;
|
||||
import org.xbib.net.ldap.control.RequestControl;
|
||||
|
||||
/**
|
||||
* LDAP message envelope defined as:
|
||||
*
|
||||
* <pre>
|
||||
* LDAPMessage ::= SEQUENCE {
|
||||
* messageID MessageID,
|
||||
* protocolOp CHOICE {
|
||||
* ...,
|
||||
* controls [0] Controls OPTIONAL }
|
||||
*
|
||||
* Control ::= SEQUENCE {
|
||||
* controlType LDAPOID,
|
||||
* criticality BOOLEAN DEFAULT FALSE,
|
||||
* controlValue OCTET STRING OPTIONAL }
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractRequestMessage implements Request {
|
||||
|
||||
/**
|
||||
* LDAP controls.
|
||||
*/
|
||||
private RequestControl[] controls;
|
||||
|
||||
/**
|
||||
* Duration of time to wait for a response. This property is not part of the request specification.
|
||||
*/
|
||||
private Duration responseTimeout;
|
||||
|
||||
|
||||
public RequestControl[] getControls() {
|
||||
return controls;
|
||||
}
|
||||
|
||||
|
||||
public void setControls(final RequestControl... cntrls) {
|
||||
controls = cntrls;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the response timeout.
|
||||
*
|
||||
* @return timeout
|
||||
*/
|
||||
public Duration getResponseTimeout() {
|
||||
return responseTimeout;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the maximum amount of time to wait for a response from this request.
|
||||
*
|
||||
* @param time timeout for a response
|
||||
*/
|
||||
public void setResponseTimeout(final Duration time) {
|
||||
if (time == null || time.isNegative()) {
|
||||
throw new IllegalArgumentException("Response timeout cannot be null or negative");
|
||||
}
|
||||
responseTimeout = time;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte[] encode(final int id) {
|
||||
final DEREncoder[] requestEncoders = getRequestEncoders(id);
|
||||
final DEREncoder controlEncoder = getControlEncoder();
|
||||
final DEREncoder[] encoders;
|
||||
if (controlEncoder != null) {
|
||||
encoders = LdapUtils.concatArrays(requestEncoders, new DEREncoder[]{controlEncoder});
|
||||
} else {
|
||||
encoders = requestEncoders;
|
||||
}
|
||||
final ConstructedDEREncoder se = new ConstructedDEREncoder(UniversalDERTag.SEQ, encoders);
|
||||
return se.encode();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the request encoders for this message.
|
||||
*
|
||||
* @param id message ID
|
||||
* @return request encoders
|
||||
*/
|
||||
protected abstract DEREncoder[] getRequestEncoders(int id);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the encoder to any controls that may be set on this message.
|
||||
*
|
||||
* @return control encoder
|
||||
*/
|
||||
private DEREncoder getControlEncoder() {
|
||||
if (controls == null || controls.length == 0) {
|
||||
return null;
|
||||
}
|
||||
final DEREncoder[] controlEncoders = new DEREncoder[controls.length];
|
||||
for (int i = 0; i < controls.length; i++) {
|
||||
if (controls[i].hasValue()) {
|
||||
controlEncoders[i] = new ConstructedDEREncoder(
|
||||
UniversalDERTag.SEQ,
|
||||
new OctetStringType(controls[i].getOID()),
|
||||
new BooleanType(controls[i].getCriticality()),
|
||||
new OctetStringType(controls[i].encode()));
|
||||
} else {
|
||||
controlEncoders[i] = new ConstructedDEREncoder(
|
||||
UniversalDERTag.SEQ,
|
||||
new OctetStringType(controls[i].getOID()),
|
||||
new BooleanType(controls[i].getCriticality()));
|
||||
}
|
||||
}
|
||||
return new ConstructedDEREncoder(new ContextDERTag(0, true), controlEncoders);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getName() + "@" + hashCode() + "::" +
|
||||
"controls=" + Arrays.toString(controls) + ", " +
|
||||
"responseTimeout=" + responseTimeout;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Base class for request builders.
|
||||
*
|
||||
* @param <B> type of builder
|
||||
* @param <T> type of message
|
||||
*/
|
||||
protected abstract static class AbstractBuilder<B, T extends AbstractRequestMessage> {
|
||||
|
||||
/**
|
||||
* Message to build.
|
||||
*/
|
||||
protected final T object;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new abstract builder.
|
||||
*
|
||||
* @param t message to build
|
||||
*/
|
||||
protected AbstractBuilder(final T t) {
|
||||
object = t;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns this builder.
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
protected abstract B self();
|
||||
|
||||
|
||||
/**
|
||||
* Sets controls on the message.
|
||||
*
|
||||
* @param cntrls controls
|
||||
* @return this builder
|
||||
*/
|
||||
public B controls(final RequestControl... cntrls) {
|
||||
object.setControls(cntrls);
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the response timeout on the message.
|
||||
*
|
||||
* @param time response timeout
|
||||
* @return this builder
|
||||
*/
|
||||
public B responseTimeout(final Duration time) {
|
||||
object.setResponseTimeout(time);
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the message.
|
||||
*
|
||||
* @return message
|
||||
*/
|
||||
public T build() {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
}
|
267
net-ldap/src/main/java/org/xbib/net/ldap/AbstractResult.java
Normal file
267
net-ldap/src/main/java/org/xbib/net/ldap/AbstractResult.java
Normal file
|
@ -0,0 +1,267 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.xbib.asn1.AbstractParseHandler;
|
||||
import org.xbib.asn1.DERBuffer;
|
||||
import org.xbib.asn1.DERParser;
|
||||
import org.xbib.asn1.IntegerType;
|
||||
import org.xbib.asn1.OctetStringType;
|
||||
|
||||
/**
|
||||
* LDAP result message defined as:
|
||||
*
|
||||
* <pre>
|
||||
* LDAPResult ::= SEQUENCE {
|
||||
* resultCode ENUMERATED {
|
||||
* ... },
|
||||
* matchedDN LDAPDN,
|
||||
* diagnosticMessage LDAPString,
|
||||
* referral [3] Referral OPTIONAL }
|
||||
*
|
||||
* Referral ::= SEQUENCE SIZE (1..MAX) OF uri URI
|
||||
*
|
||||
* URI ::= LDAPString -- limited to characters permitted in
|
||||
* -- URIs
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractResult extends AbstractMessage implements Result {
|
||||
|
||||
/**
|
||||
* Result code.
|
||||
*/
|
||||
private ResultCode resultCode;
|
||||
|
||||
/**
|
||||
* Matched DN.
|
||||
*/
|
||||
private String matchedDN;
|
||||
|
||||
/**
|
||||
* Diagnostic message.
|
||||
*/
|
||||
private String diagnosticMessage;
|
||||
|
||||
/**
|
||||
* Referral URLS.
|
||||
*/
|
||||
private final List<String> referralURLs = new ArrayList<>();
|
||||
|
||||
|
||||
public ResultCode getResultCode() {
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
|
||||
public void setResultCode(final ResultCode code) {
|
||||
resultCode = code;
|
||||
}
|
||||
|
||||
|
||||
public String getMatchedDN() {
|
||||
return matchedDN;
|
||||
}
|
||||
|
||||
|
||||
public void setMatchedDN(final String dn) {
|
||||
matchedDN = dn;
|
||||
}
|
||||
|
||||
|
||||
public String getDiagnosticMessage() {
|
||||
return diagnosticMessage;
|
||||
}
|
||||
|
||||
|
||||
public void setDiagnosticMessage(final String message) {
|
||||
diagnosticMessage = message;
|
||||
}
|
||||
|
||||
|
||||
public String[] getReferralURLs() {
|
||||
return referralURLs != null ? referralURLs.toArray(new String[0]) : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds referral URLs to the result.
|
||||
*
|
||||
* @param urls to add
|
||||
*/
|
||||
public void addReferralURLs(final String... urls) {
|
||||
Collections.addAll(referralURLs, urls);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copies the property values from the supplied result to this result.
|
||||
*
|
||||
* @param <T> type of result
|
||||
* @param result to copy from
|
||||
*/
|
||||
protected <T extends Result> void copyValues(final T result) {
|
||||
super.copyValues(result);
|
||||
setResultCode(result.getResultCode());
|
||||
setMatchedDN(result.getMatchedDN());
|
||||
setDiagnosticMessage(result.getDiagnosticMessage());
|
||||
addReferralURLs(result.getReferralURLs());
|
||||
}
|
||||
|
||||
|
||||
// CheckStyle:EqualsHashCode OFF
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
if (o instanceof AbstractResult v && super.equals(o)) {
|
||||
return LdapUtils.areEqual(getResultCode(), v.getResultCode()) &&
|
||||
LdapUtils.areEqual(getMatchedDN(), v.getMatchedDN()) &&
|
||||
LdapUtils.areEqual(getDiagnosticMessage(), v.getDiagnosticMessage()) &&
|
||||
LdapUtils.areEqual(getReferralURLs(), v.getReferralURLs());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// CheckStyle:EqualsHashCode ON
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ", " +
|
||||
"resultCode=" + resultCode + ", " +
|
||||
"matchedDN=" + matchedDN + ", " +
|
||||
"diagnosticMessage=" + getEncodedDiagnosticMessage() + ", " +
|
||||
"referralURLs=" + referralURLs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse handler implementation for the LDAP result code.
|
||||
*/
|
||||
protected static class ResultCodeHandler extends AbstractParseHandler<AbstractResult> {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new LDAP result code handler.
|
||||
*
|
||||
* @param response to configure
|
||||
*/
|
||||
public ResultCodeHandler(final AbstractResult response) {
|
||||
super(response);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handle(final DERParser parser, final DERBuffer encoded) {
|
||||
getObject().setResultCode(ResultCode.valueOf(IntegerType.decodeUnsignedPrimitive(encoded)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse handler implementation for the LDAP matched DN.
|
||||
*/
|
||||
protected static class MatchedDNHandler extends AbstractParseHandler<AbstractResult> {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new LDAP matched DN handler.
|
||||
*
|
||||
* @param response to configure
|
||||
*/
|
||||
public MatchedDNHandler(final AbstractResult response) {
|
||||
super(response);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handle(final DERParser parser, final DERBuffer encoded) {
|
||||
getObject().setMatchedDN(OctetStringType.decode(encoded));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse handler implementation for the LDAP diagnostic message.
|
||||
*/
|
||||
protected static class DiagnosticMessageHandler extends AbstractParseHandler<AbstractResult> {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new LDAP diagnostic message handler.
|
||||
*
|
||||
* @param response to configure
|
||||
*/
|
||||
public DiagnosticMessageHandler(final AbstractResult response) {
|
||||
super(response);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handle(final DERParser parser, final DERBuffer encoded) {
|
||||
getObject().setDiagnosticMessage(OctetStringType.decode(encoded));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse handler implementation for the LDAP referral.
|
||||
*/
|
||||
protected static class ReferralHandler extends AbstractParseHandler<AbstractResult> {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new LDAP referral handler.
|
||||
*
|
||||
* @param response to configure
|
||||
*/
|
||||
public ReferralHandler(final AbstractResult response) {
|
||||
super(response);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handle(final DERParser parser, final DERBuffer encoded) {
|
||||
getObject().addReferralURLs(OctetStringType.decode(encoded));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// CheckStyle:OFF
|
||||
protected abstract static class AbstractBuilder<B, T extends AbstractResult>
|
||||
extends AbstractMessage.AbstractBuilder<B, T> {
|
||||
|
||||
|
||||
protected AbstractBuilder(final T t) {
|
||||
super(t);
|
||||
}
|
||||
|
||||
|
||||
public B resultCode(final ResultCode code) {
|
||||
object.setResultCode(code);
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
public B matchedDN(final String dn) {
|
||||
object.setMatchedDN(dn);
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
public B diagnosticMessage(final String message) {
|
||||
object.setDiagnosticMessage(message);
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
public B referralURLs(final String... url) {
|
||||
object.addReferralURLs(url);
|
||||
return self();
|
||||
}
|
||||
}
|
||||
// CheckStyle:ON
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Common implementation of retry metadata.
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractRetryMetadata implements RetryMetadata {
|
||||
|
||||
/**
|
||||
* Attempt count.
|
||||
*/
|
||||
private final AtomicInteger attempts = new AtomicInteger();
|
||||
/**
|
||||
* Time at which the last success occurred.
|
||||
*/
|
||||
protected Instant successTime;
|
||||
/**
|
||||
* Time at which the failure occurred.
|
||||
*/
|
||||
protected Instant failureTime;
|
||||
|
||||
@Override
|
||||
public Instant getSuccessTime() {
|
||||
return successTime;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Instant getFailureTime() {
|
||||
return failureTime;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getAttempts() {
|
||||
return attempts.get();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void recordSuccess(final Instant time) {
|
||||
successTime = time;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void recordFailure(final Instant time) {
|
||||
failureTime = time;
|
||||
attempts.incrementAndGet();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getName() + "@" + hashCode() + "::" +
|
||||
"attempts=" + attempts + ", " +
|
||||
"failureTime=" + failureTime;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,377 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import org.xbib.net.ldap.handler.ExceptionHandler;
|
||||
import org.xbib.net.ldap.handler.IntermediateResponseHandler;
|
||||
import org.xbib.net.ldap.handler.LdapEntryHandler;
|
||||
import org.xbib.net.ldap.handler.ReferralHandler;
|
||||
import org.xbib.net.ldap.handler.RequestHandler;
|
||||
import org.xbib.net.ldap.handler.ResponseControlHandler;
|
||||
import org.xbib.net.ldap.handler.ResultHandler;
|
||||
import org.xbib.net.ldap.handler.ResultPredicate;
|
||||
import org.xbib.net.ldap.handler.SearchReferenceHandler;
|
||||
import org.xbib.net.ldap.handler.SearchResultHandler;
|
||||
import org.xbib.net.ldap.handler.UnsolicitedNotificationHandler;
|
||||
|
||||
/**
|
||||
* Base class for classes that perform searches.
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractSearchOperationFactory implements ConnectionFactoryManager {
|
||||
|
||||
/**
|
||||
* Connection factory.
|
||||
*/
|
||||
private ConnectionFactory factory;
|
||||
|
||||
/**
|
||||
* Functions to handle requests.
|
||||
*/
|
||||
private RequestHandler<SearchRequest>[] requestHandlers;
|
||||
|
||||
/**
|
||||
* Functions to handle response results.
|
||||
*/
|
||||
private ResultHandler[] resultHandlers;
|
||||
|
||||
/**
|
||||
* Functions to handle response controls.
|
||||
*/
|
||||
private ResponseControlHandler[] controlHandlers;
|
||||
|
||||
/**
|
||||
* Functions to handle referrals.
|
||||
*/
|
||||
private ReferralHandler[] referralHandlers;
|
||||
|
||||
/**
|
||||
* Functions to handle intermediate responses.
|
||||
*/
|
||||
private IntermediateResponseHandler[] intermediateResponseHandlers;
|
||||
|
||||
/**
|
||||
* Function to handle exceptions.
|
||||
*/
|
||||
private ExceptionHandler exceptionHandler;
|
||||
|
||||
/**
|
||||
* Function to test results.
|
||||
*/
|
||||
private ResultPredicate throwCondition;
|
||||
|
||||
/**
|
||||
* Functions to handle unsolicited notifications.
|
||||
*/
|
||||
private UnsolicitedNotificationHandler[] unsolicitedNotificationHandlers;
|
||||
|
||||
/**
|
||||
* Functions to handle entries.
|
||||
*/
|
||||
private LdapEntryHandler[] entryHandlers;
|
||||
|
||||
/**
|
||||
* Functions to handle response references.
|
||||
*/
|
||||
private SearchReferenceHandler[] referenceHandlers;
|
||||
|
||||
/**
|
||||
* Functions to handle search response results.
|
||||
*/
|
||||
private SearchResultHandler[] searchResultHandlers;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the connection factory.
|
||||
*
|
||||
* @return connection factory
|
||||
*/
|
||||
public ConnectionFactory getConnectionFactory() {
|
||||
return factory;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the connection factory.
|
||||
*
|
||||
* @param cf connection factory
|
||||
*/
|
||||
public void setConnectionFactory(final ConnectionFactory cf) {
|
||||
factory = cf;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the search request handlers.
|
||||
*
|
||||
* @return search request handlers
|
||||
*/
|
||||
public RequestHandler<SearchRequest>[] getRequestHandlers() {
|
||||
return requestHandlers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the search request handlers.
|
||||
*
|
||||
* @param handlers search request handler
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setRequestHandlers(final RequestHandler<SearchRequest>... handlers) {
|
||||
requestHandlers = handlers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the search result handlers.
|
||||
*
|
||||
* @return search result handlers
|
||||
*/
|
||||
public ResultHandler[] getResultHandlers() {
|
||||
return resultHandlers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the search result handlers.
|
||||
*
|
||||
* @param handlers search result handlers
|
||||
*/
|
||||
public void setResultHandlers(final ResultHandler... handlers) {
|
||||
resultHandlers = handlers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the control handlers.
|
||||
*
|
||||
* @return control handlers
|
||||
*/
|
||||
public ResponseControlHandler[] getControlHandlers() {
|
||||
return controlHandlers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the control handlers.
|
||||
*
|
||||
* @param handlers control handlers
|
||||
*/
|
||||
public void setControlHandlers(final ResponseControlHandler... handlers) {
|
||||
controlHandlers = handlers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the referral handlers.
|
||||
*
|
||||
* @return referral handlers
|
||||
*/
|
||||
public ReferralHandler[] getReferralHandlers() {
|
||||
return referralHandlers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the referral handlers.
|
||||
*
|
||||
* @param handlers referral handlers
|
||||
*/
|
||||
public void setReferralHandlers(final ReferralHandler... handlers) {
|
||||
referralHandlers = handlers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the intermediate response handlers.
|
||||
*
|
||||
* @return intermediate response handlers
|
||||
*/
|
||||
public IntermediateResponseHandler[] getIntermediateResponseHandlers() {
|
||||
return intermediateResponseHandlers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the intermediate response handlers.
|
||||
*
|
||||
* @param handlers intermediate response handlers
|
||||
*/
|
||||
public void setIntermediateResponseHandlers(final IntermediateResponseHandler... handlers) {
|
||||
intermediateResponseHandlers = handlers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the search exception handler.
|
||||
*
|
||||
* @return search exception handler
|
||||
*/
|
||||
public ExceptionHandler getExceptionHandler() {
|
||||
return exceptionHandler;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the search exception handler.
|
||||
*
|
||||
* @param handler search exception handler
|
||||
*/
|
||||
public void setExceptionHandler(final ExceptionHandler handler) {
|
||||
exceptionHandler = handler;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the throw condition.
|
||||
*
|
||||
* @return throw condition
|
||||
*/
|
||||
public ResultPredicate getThrowCondition() {
|
||||
return throwCondition;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the throw condition.
|
||||
*
|
||||
* @param function throw condition
|
||||
*/
|
||||
public void setThrowCondition(final ResultPredicate function) {
|
||||
throwCondition = function;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the unsolicited notification handlers.
|
||||
*
|
||||
* @return unsolicited notification handlers
|
||||
*/
|
||||
public UnsolicitedNotificationHandler[] getUnsolicitedNotificationHandlers() {
|
||||
return unsolicitedNotificationHandlers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the unsolicited notification handlers.
|
||||
*
|
||||
* @param handlers unsolicited notification handlers
|
||||
*/
|
||||
public void setUnsolicitedNotificationHandlers(final UnsolicitedNotificationHandler... handlers) {
|
||||
unsolicitedNotificationHandlers = handlers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the search entry handlers.
|
||||
*
|
||||
* @return search entry handlers
|
||||
*/
|
||||
public LdapEntryHandler[] getEntryHandlers() {
|
||||
return entryHandlers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the search entry handlers.
|
||||
*
|
||||
* @param handlers search entry handlers
|
||||
*/
|
||||
public void setEntryHandlers(final LdapEntryHandler... handlers) {
|
||||
entryHandlers = handlers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the search reference handlers.
|
||||
*
|
||||
* @return search reference handlers
|
||||
*/
|
||||
public SearchReferenceHandler[] getReferenceHandlers() {
|
||||
return referenceHandlers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the search reference handlers.
|
||||
*
|
||||
* @param handlers search reference handlers
|
||||
*/
|
||||
public void setReferenceHandlers(final SearchReferenceHandler... handlers) {
|
||||
referenceHandlers = handlers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the search result handlers.
|
||||
*
|
||||
* @return search result handlers
|
||||
*/
|
||||
public SearchResultHandler[] getSearchResultHandlers() {
|
||||
return searchResultHandlers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the search result handlers.
|
||||
*
|
||||
* @param handlers search result handlers
|
||||
*/
|
||||
public void setSearchResultHandlers(final SearchResultHandler... handlers) {
|
||||
searchResultHandlers = handlers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new search operation configured with the properties on this factory.
|
||||
*
|
||||
* @return search operation
|
||||
*/
|
||||
protected SearchOperation createSearchOperation() {
|
||||
return createSearchOperation(factory);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new search operation configured with the properties on this factory.
|
||||
*
|
||||
* @param cf connection factory to set on the search operation
|
||||
* @return search operation
|
||||
*/
|
||||
protected SearchOperation createSearchOperation(final ConnectionFactory cf) {
|
||||
final SearchOperation op = new SearchOperation(cf);
|
||||
if (requestHandlers != null) {
|
||||
op.setRequestHandlers(requestHandlers);
|
||||
}
|
||||
if (resultHandlers != null) {
|
||||
op.setResultHandlers(resultHandlers);
|
||||
}
|
||||
if (controlHandlers != null) {
|
||||
op.setControlHandlers(controlHandlers);
|
||||
}
|
||||
if (referralHandlers != null) {
|
||||
op.setReferralHandlers(referralHandlers);
|
||||
}
|
||||
if (intermediateResponseHandlers != null) {
|
||||
op.setIntermediateResponseHandlers(intermediateResponseHandlers);
|
||||
}
|
||||
if (exceptionHandler != null) {
|
||||
op.setExceptionHandler(exceptionHandler);
|
||||
}
|
||||
if (throwCondition != null) {
|
||||
op.setThrowCondition(throwCondition);
|
||||
}
|
||||
if (unsolicitedNotificationHandlers != null) {
|
||||
op.setUnsolicitedNotificationHandlers(unsolicitedNotificationHandlers);
|
||||
}
|
||||
if (entryHandlers != null) {
|
||||
op.setEntryHandlers(entryHandlers);
|
||||
}
|
||||
if (referenceHandlers != null) {
|
||||
op.setReferenceHandlers(referenceHandlers);
|
||||
}
|
||||
if (searchResultHandlers != null) {
|
||||
op.setSearchResultHandlers(searchResultHandlers);
|
||||
}
|
||||
return op;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Connection strategy that attempts hosts ordered exactly the way they are configured. This means that the first host
|
||||
* will always be attempted first, followed by each host in the list.
|
||||
*
|
||||
*/
|
||||
public class ActivePassiveConnectionStrategy extends AbstractConnectionStrategy {
|
||||
|
||||
/**
|
||||
* Custom iterator function.
|
||||
*/
|
||||
private final Function<List<LdapURL>, Iterator<LdapURL>> iterFunction;
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public ActivePassiveConnectionStrategy() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new active passive connection strategy.
|
||||
*
|
||||
* @param function that produces a custom iterator
|
||||
*/
|
||||
public ActivePassiveConnectionStrategy(final Function<List<LdapURL>, Iterator<LdapURL>> function) {
|
||||
iterFunction = function;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Iterator<LdapURL> iterator() {
|
||||
if (!isInitialized()) {
|
||||
throw new IllegalStateException("Strategy is not initialized");
|
||||
}
|
||||
if (iterFunction != null) {
|
||||
return iterFunction.apply(ldapURLSet.getUrls());
|
||||
}
|
||||
return new DefaultLdapURLIterator(ldapURLSet.getUrls());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ActivePassiveConnectionStrategy newInstance() {
|
||||
final ActivePassiveConnectionStrategy strategy = new ActivePassiveConnectionStrategy(iterFunction);
|
||||
strategy.setRetryCondition(getRetryCondition());
|
||||
return strategy;
|
||||
}
|
||||
}
|
149
net-ldap/src/main/java/org/xbib/net/ldap/AddOperation.java
Normal file
149
net-ldap/src/main/java/org/xbib/net/ldap/AddOperation.java
Normal file
|
@ -0,0 +1,149 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
/**
|
||||
* Executes an ldap add operation.
|
||||
*
|
||||
*/
|
||||
public class AddOperation extends AbstractOperation<AddRequest, AddResponse> {
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public AddOperation() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new add operation.
|
||||
*
|
||||
* @param factory connection factory
|
||||
*/
|
||||
public AddOperation(final ConnectionFactory factory) {
|
||||
super(factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an add request. See {@link OperationHandle#send()}.
|
||||
*
|
||||
* @param factory connection factory
|
||||
* @param request add request
|
||||
* @return operation handle
|
||||
* @throws LdapException if the connection cannot be opened
|
||||
*/
|
||||
public static OperationHandle<AddRequest, AddResponse> send(
|
||||
final ConnectionFactory factory,
|
||||
final AddRequest request)
|
||||
throws LdapException {
|
||||
final Connection conn = factory.getConnection();
|
||||
try {
|
||||
conn.open();
|
||||
} catch (Exception e) {
|
||||
conn.close();
|
||||
throw e;
|
||||
}
|
||||
return conn.operation(request).onComplete(conn::close).send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes an add request. See {@link OperationHandle#execute()}.
|
||||
*
|
||||
* @param factory connection factory
|
||||
* @param request add request
|
||||
* @return add result
|
||||
* @throws LdapException if the connection cannot be opened
|
||||
*/
|
||||
public static AddResponse execute(final ConnectionFactory factory, final AddRequest request)
|
||||
throws LdapException {
|
||||
try (Connection conn = factory.getConnection()) {
|
||||
conn.open();
|
||||
return conn.operation(request).execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new add operation with the same properties as the supplied operation.
|
||||
*
|
||||
* @param operation to copy
|
||||
* @return copy of the supplied add operation
|
||||
*/
|
||||
public static AddOperation copy(final AddOperation operation) {
|
||||
final AddOperation op = new AddOperation();
|
||||
op.setRequestHandlers(operation.getRequestHandlers());
|
||||
op.setResultHandlers(operation.getResultHandlers());
|
||||
op.setControlHandlers(operation.getControlHandlers());
|
||||
op.setReferralHandlers(operation.getReferralHandlers());
|
||||
op.setIntermediateResponseHandlers(operation.getIntermediateResponseHandlers());
|
||||
op.setExceptionHandler(operation.getExceptionHandler());
|
||||
op.setThrowCondition(operation.getThrowCondition());
|
||||
op.setUnsolicitedNotificationHandlers(operation.getUnsolicitedNotificationHandlers());
|
||||
op.setConnectionFactory(operation.getConnectionFactory());
|
||||
return op;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for this class.
|
||||
*
|
||||
* @return new builder
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an add request. See {@link OperationHandle#send()}.
|
||||
*
|
||||
* @param request add request
|
||||
* @return operation handle
|
||||
* @throws LdapException if the connection cannot be opened
|
||||
*/
|
||||
@Override
|
||||
public OperationHandle<AddRequest, AddResponse> send(final AddRequest request)
|
||||
throws LdapException {
|
||||
final Connection conn = getConnectionFactory().getConnection();
|
||||
try {
|
||||
conn.open();
|
||||
} catch (Exception e) {
|
||||
conn.close();
|
||||
throw e;
|
||||
}
|
||||
return configureHandle(conn.operation(configureRequest(request))).onComplete(conn::close).send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes an add request. See {@link OperationHandle#execute()}.
|
||||
*
|
||||
* @param request add request
|
||||
* @return add result
|
||||
* @throws LdapException if the connection cannot be opened
|
||||
*/
|
||||
@Override
|
||||
public AddResponse execute(final AddRequest request)
|
||||
throws LdapException {
|
||||
try (Connection conn = getConnectionFactory().getConnection()) {
|
||||
conn.open();
|
||||
return configureHandle(conn.operation(configureRequest(request))).execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add operation builder.
|
||||
*/
|
||||
public static class Builder extends AbstractOperation.AbstractBuilder<AddOperation.Builder, AddOperation> {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new builder.
|
||||
*/
|
||||
protected Builder() {
|
||||
super(new AddOperation());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Builder self() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
181
net-ldap/src/main/java/org/xbib/net/ldap/AddRequest.java
Normal file
181
net-ldap/src/main/java/org/xbib/net/ldap/AddRequest.java
Normal file
|
@ -0,0 +1,181 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Stream;
|
||||
import org.xbib.asn1.ApplicationDERTag;
|
||||
import org.xbib.asn1.ConstructedDEREncoder;
|
||||
import org.xbib.asn1.DEREncoder;
|
||||
import org.xbib.asn1.IntegerType;
|
||||
import org.xbib.asn1.OctetStringType;
|
||||
import org.xbib.asn1.UniversalDERTag;
|
||||
|
||||
/**
|
||||
* LDAP add request defined as:
|
||||
*
|
||||
* <pre>
|
||||
* AddRequest ::= [APPLICATION 8] SEQUENCE {
|
||||
* entry LDAPDN,
|
||||
* attributes AttributeList }
|
||||
*
|
||||
* AttributeList ::= SEQUENCE OF attribute Attribute
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public class AddRequest extends AbstractRequestMessage {
|
||||
|
||||
/**
|
||||
* BER protocol number.
|
||||
*/
|
||||
public static final int PROTOCOL_OP = 8;
|
||||
|
||||
/**
|
||||
* LDAP DN to add.
|
||||
*/
|
||||
private String ldapDn;
|
||||
|
||||
/**
|
||||
* Attributes to add to the entry.
|
||||
*/
|
||||
private LdapAttribute[] attributes;
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
private AddRequest() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new add request.
|
||||
*
|
||||
* @param dn DN to add
|
||||
* @param attrs to add to the entry
|
||||
*/
|
||||
public AddRequest(final String dn, final LdapAttribute... attrs) {
|
||||
ldapDn = dn;
|
||||
attributes = attrs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new add request.
|
||||
*
|
||||
* @param dn DN to add
|
||||
* @param attrs to add to the entry
|
||||
*/
|
||||
public AddRequest(final String dn, final Collection<LdapAttribute> attrs) {
|
||||
ldapDn = dn;
|
||||
attributes = attrs.toArray(LdapAttribute[]::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for this class.
|
||||
*
|
||||
* @return new builder
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DN.
|
||||
*
|
||||
* @return DN
|
||||
*/
|
||||
public String getDn() {
|
||||
return ldapDn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attributes.
|
||||
*
|
||||
* @return add attributes
|
||||
*/
|
||||
public LdapAttribute[] getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DEREncoder[] getRequestEncoders(final int id) {
|
||||
return new DEREncoder[]{
|
||||
new IntegerType(id),
|
||||
new ConstructedDEREncoder(
|
||||
new ApplicationDERTag(PROTOCOL_OP, true),
|
||||
new OctetStringType(ldapDn),
|
||||
new ConstructedDEREncoder(
|
||||
UniversalDERTag.SEQ,
|
||||
Stream.of(attributes).map(a ->
|
||||
new ConstructedDEREncoder(
|
||||
UniversalDERTag.SEQ,
|
||||
new OctetStringType(a.getName()),
|
||||
new ConstructedDEREncoder(
|
||||
UniversalDERTag.SET,
|
||||
a.getBinaryValues().stream().map(
|
||||
OctetStringType::new).toArray(DEREncoder[]::new)))).toArray(DEREncoder[]::new))),
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ", " + "dn=" + ldapDn + ", " + "attributes=" + Arrays.toString(attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add request builder.
|
||||
*/
|
||||
public static class Builder extends AbstractRequestMessage.AbstractBuilder<AddRequest.Builder, AddRequest> {
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
protected Builder() {
|
||||
super(new AddRequest());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Builder self() {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the ldap DN.
|
||||
*
|
||||
* @param dn ldap DN
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder dn(final String dn) {
|
||||
object.ldapDn = dn;
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the attributes.
|
||||
*
|
||||
* @param attrs attributes
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder attributes(final LdapAttribute... attrs) {
|
||||
object.attributes = attrs;
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the attributes.
|
||||
*
|
||||
* @param attrs attributes
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder attributes(final Collection<LdapAttribute> attrs) {
|
||||
object.attributes = attrs.toArray(LdapAttribute[]::new);
|
||||
return self();
|
||||
}
|
||||
}
|
||||
}
|
117
net-ldap/src/main/java/org/xbib/net/ldap/AddResponse.java
Normal file
117
net-ldap/src/main/java/org/xbib/net/ldap/AddResponse.java
Normal file
|
@ -0,0 +1,117 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import org.xbib.asn1.DERBuffer;
|
||||
import org.xbib.asn1.DERParser;
|
||||
import org.xbib.asn1.DERPath;
|
||||
|
||||
/**
|
||||
* LDAP add response defined as:
|
||||
*
|
||||
* <pre>
|
||||
* AddResponse ::= [APPLICATION 9] LDAPResult
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public class AddResponse extends AbstractResult {
|
||||
|
||||
/**
|
||||
* BER protocol number.
|
||||
*/
|
||||
public static final int PROTOCOL_OP = 9;
|
||||
|
||||
/**
|
||||
* hash code seed.
|
||||
*/
|
||||
private static final int HASH_CODE_SEED = 10211;
|
||||
|
||||
/**
|
||||
* DER path to result code.
|
||||
*/
|
||||
private static final DERPath RESULT_CODE_PATH = new DERPath("/SEQ/APP(9)/ENUM[0]");
|
||||
|
||||
/**
|
||||
* DER path to matched DN.
|
||||
*/
|
||||
private static final DERPath MATCHED_DN_PATH = new DERPath("/SEQ/APP(9)/OCTSTR[1]");
|
||||
|
||||
/**
|
||||
* DER path to diagnostic message.
|
||||
*/
|
||||
private static final DERPath DIAGNOSTIC_MESSAGE_PATH = new DERPath("/SEQ/APP(9)/OCTSTR[2]");
|
||||
|
||||
/**
|
||||
* DER path to referral.
|
||||
*/
|
||||
private static final DERPath REFERRAL_PATH = new DERPath("/SEQ/APP(9)/CTX(3)/OCTSTR[0]");
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
private AddResponse() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new add response.
|
||||
*
|
||||
* @param buffer to decode
|
||||
*/
|
||||
public AddResponse(final DERBuffer buffer) {
|
||||
final DERParser parser = new DERParser();
|
||||
parser.registerHandler(MessageIDHandler.PATH, new MessageIDHandler(this));
|
||||
parser.registerHandler(RESULT_CODE_PATH, new ResultCodeHandler(this));
|
||||
parser.registerHandler(MATCHED_DN_PATH, new MatchedDNHandler(this));
|
||||
parser.registerHandler(DIAGNOSTIC_MESSAGE_PATH, new DiagnosticMessageHandler(this));
|
||||
parser.registerHandler(REFERRAL_PATH, new ReferralHandler(this));
|
||||
parser.registerHandler(ControlsHandler.PATH, new ControlsHandler(this));
|
||||
parser.parse(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for this class.
|
||||
*
|
||||
* @return new builder
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
return o instanceof AddResponse && super.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return
|
||||
LdapUtils.computeHashCode(
|
||||
HASH_CODE_SEED,
|
||||
getMessageID(),
|
||||
getControls(),
|
||||
getResultCode(),
|
||||
getMatchedDN(),
|
||||
getDiagnosticMessage(),
|
||||
getReferralURLs());
|
||||
}
|
||||
|
||||
// CheckStyle:OFF
|
||||
public static class Builder extends AbstractResult.AbstractBuilder<Builder, AddResponse> {
|
||||
|
||||
|
||||
protected Builder() {
|
||||
super(new AddResponse());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Builder self() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
// CheckStyle:ON
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import org.xbib.asn1.ApplicationDERTag;
|
||||
import org.xbib.asn1.ConstructedDEREncoder;
|
||||
import org.xbib.asn1.ContextType;
|
||||
import org.xbib.asn1.DEREncoder;
|
||||
import org.xbib.asn1.IntegerType;
|
||||
import org.xbib.asn1.OctetStringType;
|
||||
|
||||
/**
|
||||
* LDAP anonymous bind request.
|
||||
*
|
||||
*/
|
||||
public class AnonymousBindRequest extends AbstractRequestMessage implements BindRequest {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a builder for this class.
|
||||
*
|
||||
* @return new builder
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DEREncoder[] getRequestEncoders(final int id) {
|
||||
return new DEREncoder[]{
|
||||
new IntegerType(id),
|
||||
new ConstructedDEREncoder(
|
||||
new ApplicationDERTag(PROTOCOL_OP, true),
|
||||
new IntegerType(VERSION),
|
||||
new OctetStringType(""),
|
||||
new ContextType(0, (byte[]) null)),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple bind request builder.
|
||||
*/
|
||||
public static class Builder extends
|
||||
AbstractRequestMessage.AbstractBuilder<AnonymousBindRequest.Builder, AnonymousBindRequest> {
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
protected Builder() {
|
||||
super(new AnonymousBindRequest());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Builder self() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
/**
|
||||
* LDAP modification defined as:
|
||||
*
|
||||
* <pre>
|
||||
* modification PartialAttribute
|
||||
*
|
||||
* PartialAttribute ::= SEQUENCE {
|
||||
* type AttributeDescription,
|
||||
* vals SET OF value AttributeValue }
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public class AttributeModification {
|
||||
/**
|
||||
* Modification type.
|
||||
*/
|
||||
private final Type operation;
|
||||
/**
|
||||
* Attribute to modify.
|
||||
*/
|
||||
private final LdapAttribute attribute;
|
||||
|
||||
/**
|
||||
* Creates a new modification.
|
||||
*
|
||||
* @param type of modification
|
||||
* @param attr attribute to modify
|
||||
*/
|
||||
public AttributeModification(final Type type, final LdapAttribute attr) {
|
||||
operation = type;
|
||||
attribute = attr;
|
||||
}
|
||||
|
||||
public Type getOperation() {
|
||||
return operation;
|
||||
}
|
||||
|
||||
public LdapAttribute getAttribute() {
|
||||
return attribute;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getName() + "@" + hashCode() + "::" + "operation=" + operation + ", " + "attribute=" + attribute;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Modification type.
|
||||
*/
|
||||
public enum Type {
|
||||
|
||||
/**
|
||||
* Add a new attribute.
|
||||
*/
|
||||
ADD,
|
||||
|
||||
/**
|
||||
* Delete an attribute.
|
||||
*/
|
||||
DELETE,
|
||||
|
||||
/**
|
||||
* Replace an attribute.
|
||||
*/
|
||||
REPLACE,
|
||||
|
||||
/**
|
||||
* Increment the value of an attribute.
|
||||
*/
|
||||
INCREMENT,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,279 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import java.util.Arrays;
|
||||
import org.xbib.net.ldap.control.RequestControl;
|
||||
import org.xbib.net.ldap.sasl.CramMD5BindRequest;
|
||||
import org.xbib.net.ldap.sasl.DigestMD5BindRequest;
|
||||
import org.xbib.net.ldap.sasl.GssApiBindRequest;
|
||||
import org.xbib.net.ldap.sasl.Mechanism;
|
||||
import org.xbib.net.ldap.sasl.SaslBindRequest;
|
||||
import org.xbib.net.ldap.sasl.SaslConfig;
|
||||
import org.xbib.net.ldap.sasl.ScramBindRequest;
|
||||
|
||||
/**
|
||||
* Initializes a connection by performing a bind operation. Useful if you need all connections to bind as the same
|
||||
* principal.
|
||||
*
|
||||
*/
|
||||
public class BindConnectionInitializer implements ConnectionInitializer {
|
||||
|
||||
/**
|
||||
* DN to bind as before performing operations.
|
||||
*/
|
||||
private String bindDn;
|
||||
|
||||
/**
|
||||
* Credential for the bind DN.
|
||||
*/
|
||||
private Credential bindCredential;
|
||||
|
||||
/**
|
||||
* Configuration for bind SASL authentication.
|
||||
*/
|
||||
private SaslConfig bindSaslConfig;
|
||||
|
||||
/**
|
||||
* Bind controls.
|
||||
*/
|
||||
private RequestControl[] bindControls;
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public BindConnectionInitializer() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new bind connection initializer.
|
||||
*
|
||||
* @param dn bind dn
|
||||
* @param credential bind credential
|
||||
*/
|
||||
public BindConnectionInitializer(final String dn, final String credential) {
|
||||
setBindDn(dn);
|
||||
setBindCredential(new Credential(credential));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new bind connection initializer.
|
||||
*
|
||||
* @param dn bind dn
|
||||
* @param credential bind credential
|
||||
*/
|
||||
public BindConnectionInitializer(final String dn, final Credential credential) {
|
||||
setBindDn(dn);
|
||||
setBindCredential(credential);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for this class.
|
||||
*
|
||||
* @return new builder
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bind DN.
|
||||
*
|
||||
* @return DN to bind as
|
||||
*/
|
||||
public String getBindDn() {
|
||||
return bindDn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the bind DN to authenticate as before performing operations.
|
||||
*
|
||||
* @param dn to bind as
|
||||
*/
|
||||
public void setBindDn(final String dn) {
|
||||
bindDn = dn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the credential used with the bind DN.
|
||||
*
|
||||
* @return bind DN credential
|
||||
*/
|
||||
public Credential getBindCredential() {
|
||||
return bindCredential;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the credential of the bind DN.
|
||||
*
|
||||
* @param credential to use with bind DN
|
||||
*/
|
||||
public void setBindCredential(final Credential credential) {
|
||||
bindCredential = credential;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bind sasl config.
|
||||
*
|
||||
* @return sasl config
|
||||
*/
|
||||
public SaslConfig getBindSaslConfig() {
|
||||
return bindSaslConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the bind sasl config.
|
||||
*
|
||||
* @param config sasl config
|
||||
*/
|
||||
public void setBindSaslConfig(final SaslConfig config) {
|
||||
bindSaslConfig = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bind controls.
|
||||
*
|
||||
* @return controls
|
||||
*/
|
||||
public RequestControl[] getBindControls() {
|
||||
return bindControls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the bind controls.
|
||||
*
|
||||
* @param cntrls controls to set
|
||||
*/
|
||||
public void setBindControls(final RequestControl... cntrls) {
|
||||
bindControls = cntrls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result initialize(final Connection c)
|
||||
throws LdapException {
|
||||
final Result result;
|
||||
if (bindSaslConfig != null) {
|
||||
switch (bindSaslConfig.getMechanism()) {
|
||||
case EXTERNAL:
|
||||
result = c.operation(SaslBindRequest.builder()
|
||||
.mechanism(Mechanism.EXTERNAL.mechanism())
|
||||
.credentials(bindSaslConfig.getAuthorizationId() != null ? bindSaslConfig.getAuthorizationId() : "")
|
||||
.controls(bindControls).build()).execute();
|
||||
break;
|
||||
case DIGEST_MD5:
|
||||
result = c.operation(new DigestMD5BindRequest(
|
||||
bindDn,
|
||||
bindSaslConfig.getAuthorizationId(),
|
||||
bindCredential != null ? bindCredential.getString() : null,
|
||||
bindSaslConfig.getRealm(),
|
||||
DigestMD5BindRequest.createProperties(bindSaslConfig)));
|
||||
break;
|
||||
case CRAM_MD5:
|
||||
result = c.operation(new CramMD5BindRequest(
|
||||
bindDn,
|
||||
bindCredential != null ? bindCredential.getString() : null));
|
||||
break;
|
||||
case GSSAPI:
|
||||
result = c.operation(new GssApiBindRequest(
|
||||
bindDn,
|
||||
bindSaslConfig.getAuthorizationId(),
|
||||
bindCredential != null ? bindCredential.getString() : null,
|
||||
bindSaslConfig.getRealm(),
|
||||
GssApiBindRequest.createProperties(bindSaslConfig)));
|
||||
break;
|
||||
case SCRAM_SHA_1:
|
||||
result = c.operation(new ScramBindRequest(Mechanism.SCRAM_SHA_1, bindDn, bindCredential.getString()));
|
||||
break;
|
||||
case SCRAM_SHA_256:
|
||||
result = c.operation(new ScramBindRequest(Mechanism.SCRAM_SHA_256, bindDn, bindCredential.getString()));
|
||||
break;
|
||||
case SCRAM_SHA_512:
|
||||
result = c.operation(new ScramBindRequest(Mechanism.SCRAM_SHA_512, bindDn, bindCredential.getString()));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unknown SASL mechanism: " + bindSaslConfig.getMechanism());
|
||||
}
|
||||
} else if (bindDn == null && bindCredential == null) {
|
||||
result = c.operation(AnonymousBindRequest.builder()
|
||||
.controls(bindControls).build()).execute();
|
||||
} else {
|
||||
result = c.operation(SimpleBindRequest.builder()
|
||||
.dn(bindDn)
|
||||
.password(bindCredential.getString())
|
||||
.controls(bindControls).build()).execute();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this connection initializer contains any configuration data.
|
||||
*
|
||||
* @return whether all properties are null
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return bindDn == null && bindCredential == null && bindSaslConfig == null && bindControls == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getName() + "@" + hashCode() + "::" +
|
||||
"bindDn=" + bindDn + ", " +
|
||||
"bindSaslConfig=" + bindSaslConfig + ", " +
|
||||
"bindControls=" + Arrays.toString(bindControls);
|
||||
}
|
||||
|
||||
// CheckStyle:OFF
|
||||
public static class Builder {
|
||||
|
||||
|
||||
private final BindConnectionInitializer object = new BindConnectionInitializer();
|
||||
|
||||
|
||||
protected Builder() {
|
||||
}
|
||||
|
||||
|
||||
public Builder dn(final String dn) {
|
||||
object.setBindDn(dn);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder credential(final Credential credential) {
|
||||
object.setBindCredential(credential);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder credential(final String credential) {
|
||||
object.setBindCredential(new Credential(credential));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder credential(final byte[] credential) {
|
||||
object.setBindCredential(new Credential(credential));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder saslConfig(final SaslConfig config) {
|
||||
object.setBindSaslConfig(config);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder controls(final RequestControl... controls) {
|
||||
object.setBindControls(controls);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public BindConnectionInitializer build() {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
// CheckStyle:ON
|
||||
}
|
149
net-ldap/src/main/java/org/xbib/net/ldap/BindOperation.java
Normal file
149
net-ldap/src/main/java/org/xbib/net/ldap/BindOperation.java
Normal file
|
@ -0,0 +1,149 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
/**
|
||||
* Executes an ldap bind operation.
|
||||
*
|
||||
*/
|
||||
public class BindOperation extends AbstractOperation<BindRequest, BindResponse> {
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public BindOperation() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new bind operation.
|
||||
*
|
||||
* @param factory connection factory
|
||||
*/
|
||||
public BindOperation(final ConnectionFactory factory) {
|
||||
super(factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a bind request. See {@link OperationHandle#send()}.
|
||||
*
|
||||
* @param factory connection factory
|
||||
* @param request bind request
|
||||
* @return operation handle
|
||||
* @throws LdapException if the connection cannot be opened
|
||||
*/
|
||||
public static OperationHandle<BindRequest, BindResponse> send(
|
||||
final ConnectionFactory factory,
|
||||
final BindRequest request)
|
||||
throws LdapException {
|
||||
final Connection conn = factory.getConnection();
|
||||
try {
|
||||
conn.open();
|
||||
} catch (Exception e) {
|
||||
conn.close();
|
||||
throw e;
|
||||
}
|
||||
return conn.operation(request).onComplete(conn::close).send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a bind request. See {@link OperationHandle#execute()}.
|
||||
*
|
||||
* @param factory connection factory
|
||||
* @param request bind request
|
||||
* @return bind result
|
||||
* @throws LdapException if the connection cannot be opened
|
||||
*/
|
||||
public static BindResponse execute(final ConnectionFactory factory, final BindRequest request)
|
||||
throws LdapException {
|
||||
try (Connection conn = factory.getConnection()) {
|
||||
conn.open();
|
||||
return conn.operation(request).execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new bind operation with the same properties as the supplied operation.
|
||||
*
|
||||
* @param operation to copy
|
||||
* @return copy of the supplied bind operation
|
||||
*/
|
||||
public static BindOperation copy(final BindOperation operation) {
|
||||
final BindOperation op = new BindOperation();
|
||||
op.setRequestHandlers(operation.getRequestHandlers());
|
||||
op.setResultHandlers(operation.getResultHandlers());
|
||||
op.setControlHandlers(operation.getControlHandlers());
|
||||
op.setReferralHandlers(operation.getReferralHandlers());
|
||||
op.setIntermediateResponseHandlers(operation.getIntermediateResponseHandlers());
|
||||
op.setExceptionHandler(operation.getExceptionHandler());
|
||||
op.setThrowCondition(operation.getThrowCondition());
|
||||
op.setUnsolicitedNotificationHandlers(operation.getUnsolicitedNotificationHandlers());
|
||||
op.setConnectionFactory(operation.getConnectionFactory());
|
||||
return op;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for this class.
|
||||
*
|
||||
* @return new builder
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a bind request. See {@link OperationHandle#send()}.
|
||||
*
|
||||
* @param request bind request
|
||||
* @return operation handle
|
||||
* @throws LdapException if the connection cannot be opened
|
||||
*/
|
||||
@Override
|
||||
public OperationHandle<BindRequest, BindResponse> send(final BindRequest request)
|
||||
throws LdapException {
|
||||
final Connection conn = getConnectionFactory().getConnection();
|
||||
try {
|
||||
conn.open();
|
||||
} catch (Exception e) {
|
||||
conn.close();
|
||||
throw e;
|
||||
}
|
||||
return configureHandle(conn.operation(configureRequest(request))).onComplete(conn::close).send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a bind request. See {@link OperationHandle#execute()}.
|
||||
*
|
||||
* @param request bind request
|
||||
* @return bind result
|
||||
* @throws LdapException if the connection cannot be opened
|
||||
*/
|
||||
@Override
|
||||
public BindResponse execute(final BindRequest request)
|
||||
throws LdapException {
|
||||
try (Connection conn = getConnectionFactory().getConnection()) {
|
||||
conn.open();
|
||||
return configureHandle(conn.operation(configureRequest(request))).execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind operation builder.
|
||||
*/
|
||||
public static class Builder extends AbstractOperation.AbstractBuilder<BindOperation.Builder, BindOperation> {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new builder.
|
||||
*/
|
||||
protected Builder() {
|
||||
super(new BindOperation());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Builder self() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
38
net-ldap/src/main/java/org/xbib/net/ldap/BindRequest.java
Normal file
38
net-ldap/src/main/java/org/xbib/net/ldap/BindRequest.java
Normal file
|
@ -0,0 +1,38 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
/**
|
||||
* LDAP bind request defined as:
|
||||
*
|
||||
* <pre>
|
||||
* BindRequest ::= [APPLICATION 0] SEQUENCE {
|
||||
* version INTEGER (1 .. 127),
|
||||
* name LDAPDN,
|
||||
* authentication AuthenticationChoice }
|
||||
*
|
||||
* AuthenticationChoice ::= CHOICE {
|
||||
* simple [0] OCTET STRING,
|
||||
* -- 1 and 2 reserved
|
||||
* sasl [3] SaslCredentials,
|
||||
* ... }
|
||||
*
|
||||
* SaslCredentials ::= SEQUENCE {
|
||||
* mechanism LDAPString,
|
||||
* credentials OCTET STRING OPTIONAL }
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
// CheckStyle:InterfaceIsType OFF
|
||||
public interface BindRequest extends Request {
|
||||
|
||||
/**
|
||||
* BER protocol number.
|
||||
*/
|
||||
int PROTOCOL_OP = 0;
|
||||
|
||||
/**
|
||||
* bind protocol version.
|
||||
*/
|
||||
int VERSION = 3;
|
||||
}
|
||||
// CheckStyle:InterfaceIsType ON
|
173
net-ldap/src/main/java/org/xbib/net/ldap/BindResponse.java
Normal file
173
net-ldap/src/main/java/org/xbib/net/ldap/BindResponse.java
Normal file
|
@ -0,0 +1,173 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import org.xbib.asn1.AbstractParseHandler;
|
||||
import org.xbib.asn1.DERBuffer;
|
||||
import org.xbib.asn1.DERParser;
|
||||
import org.xbib.asn1.DERPath;
|
||||
|
||||
/**
|
||||
* LDAP bind response defined as:
|
||||
*
|
||||
* <pre>
|
||||
* BindResponse ::= [APPLICATION 1] SEQUENCE {
|
||||
* COMPONENTS OF LDAPResult,
|
||||
* serverSaslCreds [7] OCTET STRING OPTIONAL }
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public class BindResponse extends AbstractResult {
|
||||
|
||||
/**
|
||||
* BER protocol number.
|
||||
*/
|
||||
public static final int PROTOCOL_OP = 1;
|
||||
|
||||
/**
|
||||
* hash code seed.
|
||||
*/
|
||||
private static final int HASH_CODE_SEED = 10243;
|
||||
|
||||
/**
|
||||
* DER path to result code.
|
||||
*/
|
||||
private static final DERPath RESULT_CODE_PATH = new DERPath("/SEQ/APP(1)/ENUM[0]");
|
||||
|
||||
/**
|
||||
* DER path to matched DN.
|
||||
*/
|
||||
private static final DERPath MATCHED_DN_PATH = new DERPath("/SEQ/APP(1)/OCTSTR[1]");
|
||||
|
||||
/**
|
||||
* DER path to diagnostic message.
|
||||
*/
|
||||
private static final DERPath DIAGNOSTIC_MESSAGE_PATH = new DERPath("/SEQ/APP(1)/OCTSTR[2]");
|
||||
|
||||
/**
|
||||
* DER path to referral.
|
||||
*/
|
||||
private static final DERPath REFERRAL_PATH = new DERPath("/SEQ/APP(1)/CTX(3)/OCTSTR[0]");
|
||||
|
||||
/**
|
||||
* DER path to SASL credentials.
|
||||
*/
|
||||
private static final DERPath SASL_CREDENTIALS_PATH = new DERPath("/SEQ/APP(1)/CTX(7)");
|
||||
|
||||
/**
|
||||
* Server SASL credentials.
|
||||
*/
|
||||
private byte[] serverSaslCreds;
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
private BindResponse() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new bind response.
|
||||
*
|
||||
* @param buffer to decode
|
||||
*/
|
||||
public BindResponse(final DERBuffer buffer) {
|
||||
final DERParser parser = new DERParser();
|
||||
parser.registerHandler(MessageIDHandler.PATH, new MessageIDHandler(this));
|
||||
parser.registerHandler(RESULT_CODE_PATH, new ResultCodeHandler(this));
|
||||
parser.registerHandler(MATCHED_DN_PATH, new MatchedDNHandler(this));
|
||||
parser.registerHandler(DIAGNOSTIC_MESSAGE_PATH, new DiagnosticMessageHandler(this));
|
||||
parser.registerHandler(REFERRAL_PATH, new ReferralHandler(this));
|
||||
parser.registerHandler(SASL_CREDENTIALS_PATH, new SASLCredsHandler(this));
|
||||
parser.registerHandler(ControlsHandler.PATH, new ControlsHandler(this));
|
||||
parser.parse(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for this class.
|
||||
*
|
||||
* @return new builder
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public byte[] getServerSaslCreds() {
|
||||
return serverSaslCreds;
|
||||
}
|
||||
|
||||
public void setServerSaslCreds(final byte[] creds) {
|
||||
serverSaslCreds = creds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
if (o instanceof BindResponse v && super.equals(o)) {
|
||||
return LdapUtils.areEqual(serverSaslCreds, v.serverSaslCreds);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return
|
||||
LdapUtils.computeHashCode(
|
||||
HASH_CODE_SEED,
|
||||
getMessageID(),
|
||||
getControls(),
|
||||
getResultCode(),
|
||||
getMatchedDN(),
|
||||
getDiagnosticMessage(),
|
||||
getReferralURLs(),
|
||||
serverSaslCreds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse handler implementation for the server SASL creds.
|
||||
*/
|
||||
protected static class SASLCredsHandler extends AbstractParseHandler<BindResponse> {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new server SASL creds handler.
|
||||
*
|
||||
* @param response to configure
|
||||
*/
|
||||
SASLCredsHandler(final BindResponse response) {
|
||||
super(response);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handle(final DERParser parser, final DERBuffer encoded) {
|
||||
if (encoded.remaining() > 0) {
|
||||
getObject().setServerSaslCreds(encoded.getRemainingBytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CheckStyle:OFF
|
||||
public static class Builder extends AbstractResult.AbstractBuilder<Builder, BindResponse> {
|
||||
|
||||
|
||||
protected Builder() {
|
||||
super(new BindResponse());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Builder self() {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder serverSaslCreds(final byte[] creds) {
|
||||
object.setServerSaslCreds(creds);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
// CheckStyle:ON
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* Retry metadata used when a connection is unexpectedly closed.
|
||||
*
|
||||
*/
|
||||
public class ClosedRetryMetadata extends AbstractRetryMetadata {
|
||||
|
||||
/**
|
||||
* Last thrown exception.
|
||||
*/
|
||||
protected final Throwable failureException;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new closed retry metadata.
|
||||
*
|
||||
* @param time of last successful connection
|
||||
* @param ex exception that caused the connection to close
|
||||
*/
|
||||
public ClosedRetryMetadata(final Instant time, final Throwable ex) {
|
||||
successTime = time;
|
||||
failureException = ex;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the exception that caused the closed connection.
|
||||
*
|
||||
* @return failure exception
|
||||
*/
|
||||
public Throwable getFailureException() {
|
||||
return failureException;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ", " + "failureException=" + failureException;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Validates a connection is healthy by performing a compare operation. Unless {@link
|
||||
* #setValidResultCodes(ResultCode...)} is set, validation is considered successful if the compare result contains any
|
||||
* result code.
|
||||
*
|
||||
*/
|
||||
public class CompareConnectionValidator extends AbstractOperationConnectionValidator<CompareRequest, CompareResponse> {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new compare validator.
|
||||
*/
|
||||
public CompareConnectionValidator() {
|
||||
this(CompareRequest.builder().dn("").name("objectClass").value("top").build());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new compare validator.
|
||||
*
|
||||
* @param cr to use for compares
|
||||
*/
|
||||
public CompareConnectionValidator(final CompareRequest cr) {
|
||||
this(DEFAULT_VALIDATE_PERIOD, DEFAULT_VALIDATE_TIMEOUT, cr);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new compare validator.
|
||||
*
|
||||
* @param period execution period
|
||||
* @param timeout execution timeout
|
||||
* @param request to use for searches
|
||||
*/
|
||||
public CompareConnectionValidator(final Duration period, final Duration timeout, final CompareRequest request) {
|
||||
setValidatePeriod(period);
|
||||
setValidateTimeout(timeout);
|
||||
setRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for this class.
|
||||
*
|
||||
* @return new builder
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the compare request.
|
||||
*
|
||||
* @return compare request
|
||||
* @deprecated use {@link AbstractOperationConnectionValidator#getRequest()}
|
||||
*/
|
||||
@Deprecated
|
||||
public CompareRequest getCompareRequest() {
|
||||
return getRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the compare request.
|
||||
*
|
||||
* @param cr compare request
|
||||
* @deprecated use {@link AbstractOperationConnectionValidator#setRequest(Request)}
|
||||
*/
|
||||
@Deprecated
|
||||
public void setCompareRequest(final CompareRequest cr) {
|
||||
setRequest(cr);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OperationHandle<CompareRequest, CompareResponse> performOperation(final Connection conn) {
|
||||
return conn.operation(getRequest());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + super.toString() + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare validator builder.
|
||||
*/
|
||||
public static class Builder extends
|
||||
AbstractOperationConnectionValidator.AbstractBuilder<
|
||||
CompareRequest, CompareResponse, CompareConnectionValidator.Builder, CompareConnectionValidator> {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new builder.
|
||||
*/
|
||||
protected Builder() {
|
||||
super(new CompareConnectionValidator());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Builder self() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
199
net-ldap/src/main/java/org/xbib/net/ldap/CompareOperation.java
Normal file
199
net-ldap/src/main/java/org/xbib/net/ldap/CompareOperation.java
Normal file
|
@ -0,0 +1,199 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import java.util.Arrays;
|
||||
import org.xbib.net.ldap.handler.CompareValueHandler;
|
||||
|
||||
/**
|
||||
* Executes an ldap compare operation.
|
||||
*
|
||||
*/
|
||||
public class CompareOperation extends AbstractOperation<CompareRequest, CompareResponse> {
|
||||
|
||||
/**
|
||||
* Functions to handle the compare result.
|
||||
*/
|
||||
private CompareValueHandler[] compareValueHandlers;
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public CompareOperation() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new compare operation.
|
||||
*
|
||||
* @param factory connection factory
|
||||
*/
|
||||
public CompareOperation(final ConnectionFactory factory) {
|
||||
super(factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a compare request. See {@link OperationHandle#send()}.
|
||||
*
|
||||
* @param factory connection factory
|
||||
* @param request compare request
|
||||
* @return operation handle
|
||||
* @throws LdapException if the connection cannot be opened
|
||||
*/
|
||||
public static CompareOperationHandle send(final ConnectionFactory factory, final CompareRequest request)
|
||||
throws LdapException {
|
||||
final Connection conn = factory.getConnection();
|
||||
try {
|
||||
conn.open();
|
||||
} catch (Exception e) {
|
||||
conn.close();
|
||||
throw e;
|
||||
}
|
||||
return conn.operation(request).onComplete(conn::close).send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a compare request. See {@link OperationHandle#execute()}.
|
||||
*
|
||||
* @param factory connection factory
|
||||
* @param request compare request
|
||||
* @return compare result
|
||||
* @throws LdapException if the connection cannot be opened
|
||||
*/
|
||||
public static CompareResponse execute(final ConnectionFactory factory, final CompareRequest request)
|
||||
throws LdapException {
|
||||
try (Connection conn = factory.getConnection()) {
|
||||
conn.open();
|
||||
return conn.operation(request).execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new compare operation with the same properties as the supplied operation.
|
||||
*
|
||||
* @param operation to copy
|
||||
* @return copy of the supplied compare operation
|
||||
*/
|
||||
public static CompareOperation copy(final CompareOperation operation) {
|
||||
final CompareOperation op = new CompareOperation();
|
||||
op.setRequestHandlers(operation.getRequestHandlers());
|
||||
op.setResultHandlers(operation.getResultHandlers());
|
||||
op.setControlHandlers(operation.getControlHandlers());
|
||||
op.setReferralHandlers(operation.getReferralHandlers());
|
||||
op.setIntermediateResponseHandlers(operation.getIntermediateResponseHandlers());
|
||||
op.setExceptionHandler(operation.getExceptionHandler());
|
||||
op.setThrowCondition(operation.getThrowCondition());
|
||||
op.setUnsolicitedNotificationHandlers(operation.getUnsolicitedNotificationHandlers());
|
||||
op.setConnectionFactory(operation.getConnectionFactory());
|
||||
op.setCompareValueHandlers(operation.getCompareValueHandlers());
|
||||
return op;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for this class.
|
||||
*
|
||||
* @return new builder
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public CompareValueHandler[] getCompareValueHandlers() {
|
||||
return compareValueHandlers;
|
||||
}
|
||||
|
||||
public void setCompareValueHandlers(final CompareValueHandler... handlers) {
|
||||
compareValueHandlers = handlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a compare request. See {@link OperationHandle#send()}.
|
||||
*
|
||||
* @param request compare request
|
||||
* @return operation handle
|
||||
* @throws LdapException if the connection cannot be opened
|
||||
*/
|
||||
@Override
|
||||
public CompareOperationHandle send(final CompareRequest request)
|
||||
throws LdapException {
|
||||
final Connection conn = getConnectionFactory().getConnection();
|
||||
try {
|
||||
conn.open();
|
||||
} catch (Exception e) {
|
||||
conn.close();
|
||||
throw e;
|
||||
}
|
||||
return configureHandle(conn.operation(configureRequest(request))).onComplete(conn::close).send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a compare request. See {@link OperationHandle#execute()}.
|
||||
*
|
||||
* @param request compare request
|
||||
* @return compare result
|
||||
* @throws LdapException if the connection cannot be opened
|
||||
*/
|
||||
@Override
|
||||
public CompareResponse execute(final CompareRequest request)
|
||||
throws LdapException {
|
||||
try (Connection conn = getConnectionFactory().getConnection()) {
|
||||
conn.open();
|
||||
return configureHandle(conn.operation(configureRequest(request))).execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds configured functions to the supplied handle.
|
||||
*
|
||||
* @param handle to configure
|
||||
* @return configured handle
|
||||
*/
|
||||
protected CompareOperationHandle configureHandle(final CompareOperationHandle handle) {
|
||||
return handle
|
||||
.onCompare(getCompareValueHandlers())
|
||||
.onControl(getControlHandlers())
|
||||
.onReferral(getReferralHandlers())
|
||||
.onIntermediate(getIntermediateResponseHandlers())
|
||||
.onException(getExceptionHandler())
|
||||
.throwIf(getThrowCondition())
|
||||
.onUnsolicitedNotification(getUnsolicitedNotificationHandlers())
|
||||
.onResult(getResultHandlers());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ", " + "compareValueHandlers=" + Arrays.toString(compareValueHandlers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare operation builder.
|
||||
*/
|
||||
public static class Builder extends AbstractOperation.AbstractBuilder<CompareOperation.Builder, CompareOperation> {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new builder.
|
||||
*/
|
||||
protected Builder() {
|
||||
super(new CompareOperation());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Builder self() {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the functions to execute when a compare result is complete.
|
||||
*
|
||||
* @param handlers to execute on a compare result
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder onCompare(final CompareValueHandler... handlers) {
|
||||
object.setCompareValueHandlers(handlers);
|
||||
return self();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import org.xbib.net.ldap.handler.CompareValueHandler;
|
||||
import org.xbib.net.ldap.handler.CompleteHandler;
|
||||
import org.xbib.net.ldap.handler.ExceptionHandler;
|
||||
import org.xbib.net.ldap.handler.IntermediateResponseHandler;
|
||||
import org.xbib.net.ldap.handler.ReferralHandler;
|
||||
import org.xbib.net.ldap.handler.ResponseControlHandler;
|
||||
import org.xbib.net.ldap.handler.ResultHandler;
|
||||
import org.xbib.net.ldap.handler.ResultPredicate;
|
||||
import org.xbib.net.ldap.handler.UnsolicitedNotificationHandler;
|
||||
|
||||
/**
|
||||
* Handle that notifies on the components of a compare request.
|
||||
*
|
||||
*/
|
||||
public interface CompareOperationHandle extends OperationHandle<CompareRequest, CompareResponse> {
|
||||
|
||||
|
||||
@Override
|
||||
CompareOperationHandle send();
|
||||
|
||||
|
||||
@Override
|
||||
CompareResponse await() throws LdapException;
|
||||
|
||||
|
||||
@Override
|
||||
default CompareResponse execute()
|
||||
throws LdapException {
|
||||
return send().await();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
CompareOperationHandle onResult(ResultHandler... function);
|
||||
|
||||
|
||||
@Override
|
||||
CompareOperationHandle onControl(ResponseControlHandler... function);
|
||||
|
||||
|
||||
@Override
|
||||
CompareOperationHandle onReferral(ReferralHandler... function);
|
||||
|
||||
|
||||
@Override
|
||||
CompareOperationHandle onIntermediate(IntermediateResponseHandler... function);
|
||||
|
||||
|
||||
@Override
|
||||
CompareOperationHandle onUnsolicitedNotification(UnsolicitedNotificationHandler... function);
|
||||
|
||||
|
||||
@Override
|
||||
CompareOperationHandle onException(ExceptionHandler function);
|
||||
|
||||
|
||||
@Override
|
||||
CompareOperationHandle throwIf(ResultPredicate function);
|
||||
|
||||
|
||||
@Override
|
||||
CompareOperationHandle onComplete(CompleteHandler function);
|
||||
|
||||
|
||||
/**
|
||||
* Sets the function to execute when a compare result is received.
|
||||
*
|
||||
* @param function to execute on a compare result
|
||||
* @return this handle
|
||||
*/
|
||||
CompareOperationHandle onCompare(CompareValueHandler... function);
|
||||
}
|
204
net-ldap/src/main/java/org/xbib/net/ldap/CompareRequest.java
Normal file
204
net-ldap/src/main/java/org/xbib/net/ldap/CompareRequest.java
Normal file
|
@ -0,0 +1,204 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import org.xbib.asn1.ApplicationDERTag;
|
||||
import org.xbib.asn1.ConstructedDEREncoder;
|
||||
import org.xbib.asn1.DEREncoder;
|
||||
import org.xbib.asn1.IntegerType;
|
||||
import org.xbib.asn1.OctetStringType;
|
||||
import org.xbib.asn1.UniversalDERTag;
|
||||
|
||||
/**
|
||||
* LDAP compare request defined as:
|
||||
*
|
||||
* <pre>
|
||||
* CompareRequest ::= [APPLICATION 14] SEQUENCE {
|
||||
* entry LDAPDN,
|
||||
* ava AttributeValueAssertion }
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public class CompareRequest extends AbstractRequestMessage {
|
||||
|
||||
/**
|
||||
* BER protocol number.
|
||||
*/
|
||||
public static final int PROTOCOL_OP = 14;
|
||||
|
||||
/**
|
||||
* LDAP DN to compare.
|
||||
*/
|
||||
private String ldapDn;
|
||||
|
||||
/**
|
||||
* Attribute description
|
||||
*/
|
||||
private String attributeDesc;
|
||||
|
||||
/**
|
||||
* Assertion value.
|
||||
*/
|
||||
private String assertionValue;
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public CompareRequest() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new compare request.
|
||||
*
|
||||
* @param dn to compare
|
||||
* @param name attribute description
|
||||
* @param value assertion value
|
||||
*/
|
||||
public CompareRequest(final String dn, final String name, final String value) {
|
||||
ldapDn = dn;
|
||||
attributeDesc = name;
|
||||
assertionValue = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for this class.
|
||||
*
|
||||
* @return new builder
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DN.
|
||||
*
|
||||
* @return DN
|
||||
*/
|
||||
public String getDn() {
|
||||
return ldapDn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the DN.
|
||||
*
|
||||
* @param dn ldapDn to set
|
||||
*/
|
||||
public void setDn(final String dn) {
|
||||
ldapDn = dn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name.
|
||||
*
|
||||
* @return name
|
||||
*/
|
||||
public String getName() {
|
||||
return attributeDesc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name.
|
||||
*
|
||||
* @param name attributeDesc to set
|
||||
*/
|
||||
public void setName(final String name) {
|
||||
attributeDesc = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value.
|
||||
*
|
||||
* @return value
|
||||
*/
|
||||
public String getValue() {
|
||||
return assertionValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value.
|
||||
*
|
||||
* @param value assertionValue to set
|
||||
*/
|
||||
public void setValue(final String value) {
|
||||
assertionValue = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DEREncoder[] getRequestEncoders(final int id) {
|
||||
return new DEREncoder[]{
|
||||
new IntegerType(id),
|
||||
new ConstructedDEREncoder(
|
||||
new ApplicationDERTag(PROTOCOL_OP, true),
|
||||
new OctetStringType(ldapDn),
|
||||
new ConstructedDEREncoder(
|
||||
UniversalDERTag.SEQ,
|
||||
new OctetStringType(attributeDesc),
|
||||
new OctetStringType(assertionValue))),
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ", " +
|
||||
"dn=" + ldapDn + ", " +
|
||||
"attributeDesc=" + attributeDesc + ", " +
|
||||
"assertionValue=" + ("userPassword".equalsIgnoreCase(attributeDesc) ? "<suppressed>" : assertionValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare request builder.
|
||||
*/
|
||||
public static class Builder extends AbstractRequestMessage.AbstractBuilder<CompareRequest.Builder, CompareRequest> {
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
protected Builder() {
|
||||
super(new CompareRequest());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Builder self() {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the ldap DN.
|
||||
*
|
||||
* @param dn ldap DN
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder dn(final String dn) {
|
||||
object.ldapDn = dn;
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the attribute description.
|
||||
*
|
||||
* @param name attribute description
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder name(final String name) {
|
||||
object.attributeDesc = name;
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the assertion value.
|
||||
*
|
||||
* @param value assertion value
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder value(final String value) {
|
||||
object.assertionValue = value;
|
||||
return self();
|
||||
}
|
||||
}
|
||||
}
|
135
net-ldap/src/main/java/org/xbib/net/ldap/CompareResponse.java
Normal file
135
net-ldap/src/main/java/org/xbib/net/ldap/CompareResponse.java
Normal file
|
@ -0,0 +1,135 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import org.xbib.asn1.DERBuffer;
|
||||
import org.xbib.asn1.DERParser;
|
||||
import org.xbib.asn1.DERPath;
|
||||
|
||||
/**
|
||||
* LDAP compare response defined as:
|
||||
*
|
||||
* <pre>
|
||||
* CompareResponse ::= [APPLICATION 15] LDAPResult
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public class CompareResponse extends AbstractResult {
|
||||
|
||||
/**
|
||||
* BER protocol number.
|
||||
*/
|
||||
public static final int PROTOCOL_OP = 15;
|
||||
|
||||
/**
|
||||
* hash code seed.
|
||||
*/
|
||||
private static final int HASH_CODE_SEED = 10247;
|
||||
|
||||
/**
|
||||
* DER path to result code.
|
||||
*/
|
||||
private static final DERPath RESULT_CODE_PATH = new DERPath("/SEQ/APP(15)/ENUM[0]");
|
||||
|
||||
/**
|
||||
* DER path to matched DN.
|
||||
*/
|
||||
private static final DERPath MATCHED_DN_PATH = new DERPath("/SEQ/APP(15)/OCTSTR[1]");
|
||||
|
||||
/**
|
||||
* DER path to diagnostic message.
|
||||
*/
|
||||
private static final DERPath DIAGNOSTIC_MESSAGE_PATH = new DERPath("/SEQ/APP(15)/OCTSTR[2]");
|
||||
|
||||
/**
|
||||
* DER path to referral.
|
||||
*/
|
||||
private static final DERPath REFERRAL_PATH = new DERPath("/SEQ/APP(15)/CTX(3)/OCTSTR[0]");
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
private CompareResponse() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new compare response.
|
||||
*
|
||||
* @param buffer to decode
|
||||
*/
|
||||
public CompareResponse(final DERBuffer buffer) {
|
||||
final DERParser parser = new DERParser();
|
||||
parser.registerHandler(MessageIDHandler.PATH, new MessageIDHandler(this));
|
||||
parser.registerHandler(RESULT_CODE_PATH, new ResultCodeHandler(this));
|
||||
parser.registerHandler(MATCHED_DN_PATH, new MatchedDNHandler(this));
|
||||
parser.registerHandler(DIAGNOSTIC_MESSAGE_PATH, new DiagnosticMessageHandler(this));
|
||||
parser.registerHandler(REFERRAL_PATH, new ReferralHandler(this));
|
||||
parser.registerHandler(ControlsHandler.PATH, new ControlsHandler(this));
|
||||
parser.parse(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for this class.
|
||||
*
|
||||
* @return new builder
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the result code in this result is {@link ResultCode#COMPARE_TRUE}.
|
||||
*
|
||||
* @return whether this result is compare true
|
||||
*/
|
||||
public boolean isTrue() {
|
||||
return ResultCode.COMPARE_TRUE == getResultCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the result code in this result is {@link ResultCode#COMPARE_FALSE}.
|
||||
*
|
||||
* @return whether this result is compare false
|
||||
*/
|
||||
public boolean isFalse() {
|
||||
return ResultCode.COMPARE_FALSE == getResultCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
return o instanceof CompareResponse && super.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return
|
||||
LdapUtils.computeHashCode(
|
||||
HASH_CODE_SEED,
|
||||
getMessageID(),
|
||||
getControls(),
|
||||
getResultCode(),
|
||||
getMatchedDN(),
|
||||
getDiagnosticMessage(),
|
||||
getReferralURLs());
|
||||
}
|
||||
|
||||
// CheckStyle:OFF
|
||||
public static class Builder extends AbstractResult.AbstractBuilder<Builder, CompareResponse> {
|
||||
|
||||
|
||||
protected Builder() {
|
||||
super(new CompareResponse());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Builder self() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
// CheckStyle:ON
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
/**
|
||||
* Exception that indicates a connection attempt failed.
|
||||
*
|
||||
*/
|
||||
public class ConnectException extends LdapException {
|
||||
|
||||
/**
|
||||
* serialVersionUID.
|
||||
*/
|
||||
private static final long serialVersionUID = -5935483002226156942L;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new connect exception.
|
||||
*
|
||||
* @param code result code describing this exception
|
||||
* @param msg describing this exception
|
||||
*/
|
||||
public ConnectException(final ResultCode code, final String msg) {
|
||||
super(code, msg);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new connect exception.
|
||||
*
|
||||
* @param code result code describing this exception
|
||||
* @param e underlying exception
|
||||
*/
|
||||
public ConnectException(final ResultCode code, final Throwable e) {
|
||||
super(code, e);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new connect exception.
|
||||
*
|
||||
* @param code result code describing this exception
|
||||
* @param msg describing this exception
|
||||
* @param e underlying exception
|
||||
*/
|
||||
public ConnectException(final ResultCode code, final String msg, final Throwable e) {
|
||||
super(code, msg, e);
|
||||
}
|
||||
}
|
156
net-ldap/src/main/java/org/xbib/net/ldap/Connection.java
Normal file
156
net-ldap/src/main/java/org/xbib/net/ldap/Connection.java
Normal file
|
@ -0,0 +1,156 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import org.xbib.net.ldap.control.RequestControl;
|
||||
import org.xbib.net.ldap.extended.ExtendedOperationHandle;
|
||||
import org.xbib.net.ldap.extended.ExtendedRequest;
|
||||
import org.xbib.net.ldap.sasl.DefaultSaslClientRequest;
|
||||
import org.xbib.net.ldap.sasl.SaslClientRequest;
|
||||
|
||||
/**
|
||||
* Interface for connection implementations.
|
||||
*
|
||||
*/
|
||||
public interface Connection extends AutoCloseable {
|
||||
|
||||
|
||||
/**
|
||||
* Executes an abandon operation. Clients should execute abandons using {@link OperationHandle#abandon()}.
|
||||
*
|
||||
* @param request abandon request
|
||||
*/
|
||||
void operation(AbandonRequest request);
|
||||
|
||||
|
||||
/**
|
||||
* Creates a handle for an add operation.
|
||||
*
|
||||
* @param request add request
|
||||
* @return operation handle
|
||||
*/
|
||||
OperationHandle<AddRequest, AddResponse> operation(AddRequest request);
|
||||
|
||||
|
||||
/**
|
||||
* Creates a handle for a bind operation. Since clients must not send requests while a bind is in progress, some
|
||||
* methods may not be supported on the operation handle.
|
||||
*
|
||||
* @param request bind request
|
||||
* @return operation handle
|
||||
*/
|
||||
OperationHandle<BindRequest, BindResponse> operation(BindRequest request);
|
||||
|
||||
|
||||
/**
|
||||
* Creates a handle for a compare operation.
|
||||
*
|
||||
* @param request compare request
|
||||
* @return compare operation handle
|
||||
*/
|
||||
CompareOperationHandle operation(CompareRequest request);
|
||||
|
||||
|
||||
/**
|
||||
* Creates a handle for a delete operation.
|
||||
*
|
||||
* @param request delete request
|
||||
* @return operation handle
|
||||
*/
|
||||
OperationHandle<DeleteRequest, DeleteResponse> operation(DeleteRequest request);
|
||||
|
||||
|
||||
/**
|
||||
* Creates a handle for an extended operation.
|
||||
*
|
||||
* @param request extended request
|
||||
* @return extended operation handle
|
||||
*/
|
||||
ExtendedOperationHandle operation(ExtendedRequest request);
|
||||
|
||||
|
||||
/**
|
||||
* Creates a handle for a modify operation.
|
||||
*
|
||||
* @param request modify request
|
||||
* @return operation handle
|
||||
*/
|
||||
OperationHandle<ModifyRequest, ModifyResponse> operation(ModifyRequest request);
|
||||
|
||||
|
||||
/**
|
||||
* Creates a handle for a modify dn operation.
|
||||
*
|
||||
* @param request modify dn request
|
||||
* @return operation handle
|
||||
*/
|
||||
OperationHandle<ModifyDnRequest, ModifyDnResponse> operation(ModifyDnRequest request);
|
||||
|
||||
|
||||
/**
|
||||
* Creates a handle for a search operation.
|
||||
*
|
||||
* @param request search request
|
||||
* @return search operation handle
|
||||
*/
|
||||
SearchOperationHandle operation(SearchRequest request);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the result of a SASL request that requires use of a generic SASL client.
|
||||
*
|
||||
* @param request SASL client request
|
||||
* @return operation result
|
||||
* @throws LdapException if the operation fails or another bind is in progress
|
||||
*/
|
||||
BindResponse operation(SaslClientRequest request) throws LdapException;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the result of a SASL request that requires use of the default SASL client. This includes CRAM-MD5,
|
||||
* DIGEST-MD5, and GSS-API.
|
||||
*
|
||||
* @param request default SASL client request
|
||||
* @return operation result
|
||||
* @throws LdapException if the operation fails or another bind is in progress
|
||||
*/
|
||||
BindResponse operation(DefaultSaslClientRequest request) throws LdapException;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the URL that was selected for this connection. The existence of this value does not indicate a current
|
||||
* established connection.
|
||||
*
|
||||
* @return LDAP URL
|
||||
*/
|
||||
LdapURL getLdapURL();
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this connection is open.
|
||||
*
|
||||
* @return whether this connection is open
|
||||
*/
|
||||
boolean isOpen();
|
||||
|
||||
|
||||
/**
|
||||
* Opens the connection.
|
||||
*
|
||||
* @throws LdapException if an error occurs opening the connection
|
||||
*/
|
||||
void open() throws LdapException;
|
||||
|
||||
|
||||
@Override
|
||||
default void close() {
|
||||
close((RequestControl[]) null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Closes the connection.
|
||||
*
|
||||
* @param controls to send when closing the connection
|
||||
*/
|
||||
void close(RequestControl... controls);
|
||||
}
|
601
net-ldap/src/main/java/org/xbib/net/ldap/ConnectionConfig.java
Normal file
601
net-ldap/src/main/java/org/xbib/net/ldap/ConnectionConfig.java
Normal file
|
@ -0,0 +1,601 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
import org.xbib.net.ldap.ssl.SslConfig;
|
||||
|
||||
/**
|
||||
* Contains all the configuration data needed to control connections.
|
||||
*
|
||||
*/
|
||||
public class ConnectionConfig extends AbstractConfig {
|
||||
|
||||
/**
|
||||
* Predicate that attempts a single reconnect.
|
||||
*/
|
||||
public static final Predicate<RetryMetadata> ONE_RECONNECT_ATTEMPT =
|
||||
new Predicate<>() {
|
||||
@Override
|
||||
public boolean test(final RetryMetadata metadata) {
|
||||
return metadata instanceof ClosedRetryMetadata && metadata.getAttempts() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ONE_RECONNECT_ATTEMPT";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Predicate that attempts to reconnect forever, waiting for 5 seconds after the first attempt.
|
||||
*/
|
||||
public static final Predicate<RetryMetadata> INFINITE_RECONNECT_ATTEMPTS =
|
||||
new Predicate<>() {
|
||||
@Override
|
||||
public boolean test(final RetryMetadata metadata) {
|
||||
if (metadata instanceof ClosedRetryMetadata) {
|
||||
if (metadata.getAttempts() > 0) {
|
||||
try {
|
||||
// CheckStyle:MagicNumber OFF
|
||||
Thread.sleep(Duration.ofSeconds(5).toMillis());
|
||||
// CheckStyle:MagicNumber ON
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "INFINITE_RECONNECT_ATTEMPTS";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Predicate that attempts to reconnect forever, backing off in 5 second intervals after the first attempt.
|
||||
*/
|
||||
public static final Predicate<RetryMetadata> INFINITE_RECONNECT_ATTEMPTS_WITH_BACKOFF =
|
||||
new Predicate<>() {
|
||||
@Override
|
||||
public boolean test(final RetryMetadata metadata) {
|
||||
if (metadata instanceof ClosedRetryMetadata) {
|
||||
if (metadata.getAttempts() > 0) {
|
||||
try {
|
||||
// CheckStyle:MagicNumber OFF
|
||||
Thread.sleep(Duration.ofSeconds(5).multipliedBy(metadata.getAttempts()).toMillis());
|
||||
// CheckStyle:MagicNumber ON
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "INFINITE_RECONNECT_ATTEMPTS_WITH_BACKOFF";
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Transport options.
|
||||
*/
|
||||
private final Map<String, Object> transportOptions = new HashMap<>();
|
||||
/**
|
||||
* URL to the LDAP(s).
|
||||
*/
|
||||
private String ldapUrl;
|
||||
/**
|
||||
* Duration of time that connects will block.
|
||||
*/
|
||||
private Duration connectTimeout = Duration.ofMinutes(1);
|
||||
/**
|
||||
* Duration of time to wait for startTLS responses.
|
||||
*/
|
||||
private Duration startTLSTimeout = Duration.ofMinutes(1);
|
||||
/**
|
||||
* Duration of time to wait for responses.
|
||||
*/
|
||||
private Duration responseTimeout = Duration.ofMinutes(1);
|
||||
/**
|
||||
* Duration of time that operations will block on reconnects, should generally be longer than {@link
|
||||
* #connectTimeout}.
|
||||
*/
|
||||
private Duration reconnectTimeout = Duration.ofMinutes(2);
|
||||
/**
|
||||
* Whether to automatically reconnect to the server when a connection is lost. Default is true.
|
||||
*/
|
||||
private boolean autoReconnect = true;
|
||||
/**
|
||||
* Condition used to determine whether another reconnect attempt should be made. Default makes a single attempt only
|
||||
* if the connection was previously opened.
|
||||
*/
|
||||
private Predicate<RetryMetadata> autoReconnectCondition = ONE_RECONNECT_ATTEMPT;
|
||||
/**
|
||||
* Whether pending operations should be replayed after a reconnect. Default is false.
|
||||
*/
|
||||
private boolean autoReplay;
|
||||
/**
|
||||
* Configuration for SSL and startTLS connections.
|
||||
*/
|
||||
private SslConfig sslConfig;
|
||||
/**
|
||||
* Connect to LDAP using startTLS.
|
||||
*/
|
||||
private boolean useStartTLS;
|
||||
/**
|
||||
* Connection initializers to execute on {@link Connection#open()}.
|
||||
*/
|
||||
private ConnectionInitializer[] connectionInitializers;
|
||||
/**
|
||||
* Connection strategy.
|
||||
*/
|
||||
private ConnectionStrategy connectionStrategy = new ActivePassiveConnectionStrategy();
|
||||
/**
|
||||
* Connection validator.
|
||||
*/
|
||||
private ConnectionValidator connectionValidator;
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public ConnectionConfig() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new connection config.
|
||||
*
|
||||
* @param url to connect to
|
||||
*/
|
||||
public ConnectionConfig(final String url) {
|
||||
setLdapUrl(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new connection config initialized with the supplied config.
|
||||
*
|
||||
* @param config connection config to read properties from
|
||||
* @return connection config
|
||||
*/
|
||||
public static ConnectionConfig copy(final ConnectionConfig config) {
|
||||
final ConnectionConfig cc = new ConnectionConfig();
|
||||
cc.setLdapUrl(config.getLdapUrl());
|
||||
cc.setConnectTimeout(config.getConnectTimeout());
|
||||
cc.setStartTLSTimeout(config.getStartTLSTimeout());
|
||||
cc.setResponseTimeout(config.getResponseTimeout());
|
||||
cc.setReconnectTimeout(config.getReconnectTimeout());
|
||||
cc.setAutoReconnect(config.getAutoReconnect());
|
||||
cc.setAutoReconnectCondition(config.getAutoReconnectCondition());
|
||||
cc.setAutoReplay(config.getAutoReplay());
|
||||
cc.setSslConfig(config.getSslConfig() != null ? SslConfig.copy(config.getSslConfig()) : null);
|
||||
cc.setUseStartTLS(config.getUseStartTLS());
|
||||
cc.setConnectionInitializers(config.getConnectionInitializers());
|
||||
cc.setConnectionStrategy(
|
||||
config.getConnectionStrategy() != null ? config.getConnectionStrategy().newInstance() : null);
|
||||
cc.setConnectionValidator(config.getConnectionValidator());
|
||||
cc.setTransportOptions(config.getTransportOptions());
|
||||
return cc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for this class.
|
||||
*
|
||||
* @return new builder
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ldap url.
|
||||
*
|
||||
* @return ldap url
|
||||
*/
|
||||
public String getLdapUrl() {
|
||||
return ldapUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ldap url.
|
||||
*
|
||||
* @param url of the ldap
|
||||
*/
|
||||
public void setLdapUrl(final String url) {
|
||||
checkStringInput(url, true);
|
||||
ldapUrl = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connect timeout.
|
||||
*
|
||||
* @return timeout
|
||||
*/
|
||||
public Duration getConnectTimeout() {
|
||||
return connectTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum amount of time that connects will block.
|
||||
*
|
||||
* @param time timeout for connects
|
||||
*/
|
||||
public void setConnectTimeout(final Duration time) {
|
||||
if (time == null || time.isNegative()) {
|
||||
throw new IllegalArgumentException("Connect timeout cannot be null or negative");
|
||||
}
|
||||
connectTimeout = time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the startTLS timeout.
|
||||
*
|
||||
* @return timeout
|
||||
*/
|
||||
public Duration getStartTLSTimeout() {
|
||||
return startTLSTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum amount of time that startTLS operations will wait for a response.
|
||||
*
|
||||
* @param time timeout for responses
|
||||
*/
|
||||
public void setStartTLSTimeout(final Duration time) {
|
||||
if (time == null || time.isNegative()) {
|
||||
throw new IllegalArgumentException("StartTLS timeout cannot be null or negative");
|
||||
}
|
||||
startTLSTimeout = time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the response timeout.
|
||||
*
|
||||
* @return timeout
|
||||
*/
|
||||
public Duration getResponseTimeout() {
|
||||
return responseTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum amount of time that operations will wait for a response.
|
||||
*
|
||||
* @param time timeout for responses
|
||||
*/
|
||||
public void setResponseTimeout(final Duration time) {
|
||||
if (time == null || time.isNegative()) {
|
||||
throw new IllegalArgumentException("Response timeout cannot be null or negative");
|
||||
}
|
||||
responseTimeout = time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reconnect timeout.
|
||||
*
|
||||
* @return timeout
|
||||
*/
|
||||
public Duration getReconnectTimeout() {
|
||||
return reconnectTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum amount of time that operations will block waiting for a reconnect.
|
||||
*
|
||||
* @param time timeout for reconnects
|
||||
*/
|
||||
public void setReconnectTimeout(final Duration time) {
|
||||
if (time == null || time.isNegative()) {
|
||||
throw new IllegalArgumentException("Reconnect timeout cannot be null or negative");
|
||||
}
|
||||
reconnectTimeout = time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether connections will attempt to reconnect.
|
||||
*
|
||||
* @return whether to automatically reconnect when a connection is lost
|
||||
*/
|
||||
public boolean getAutoReconnect() {
|
||||
return autoReconnect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether connections will attempt to reconnect when unexpectedly closed.
|
||||
*
|
||||
* @param b whether to automatically reconnect when a connection is lost
|
||||
*/
|
||||
public void setAutoReconnect(final boolean b) {
|
||||
autoReconnect = b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the auto reconnect condition.
|
||||
*
|
||||
* @return auto reconnect condition
|
||||
*/
|
||||
public Predicate<RetryMetadata> getAutoReconnectCondition() {
|
||||
return autoReconnectCondition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the auto reconnect condition.
|
||||
*
|
||||
* @param predicate to determine whether to attempt a reconnect
|
||||
*/
|
||||
public void setAutoReconnectCondition(final Predicate<RetryMetadata> predicate) {
|
||||
autoReconnectCondition = predicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether operations should be replayed after a reconnect.
|
||||
*
|
||||
* @return whether to auto replay
|
||||
*/
|
||||
public boolean getAutoReplay() {
|
||||
return autoReplay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether operations will be replayed after a reconnect.
|
||||
*
|
||||
* @param b whether to replay operations
|
||||
*/
|
||||
public void setAutoReplay(final boolean b) {
|
||||
autoReplay = b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ssl config.
|
||||
*
|
||||
* @return ssl config
|
||||
*/
|
||||
public SslConfig getSslConfig() {
|
||||
return sslConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ssl config.
|
||||
*
|
||||
* @param config ssl config
|
||||
*/
|
||||
public void setSslConfig(final SslConfig config) {
|
||||
sslConfig = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether startTLS will be used for connections.
|
||||
*
|
||||
* @return whether startTLS will be used
|
||||
*/
|
||||
public boolean getUseStartTLS() {
|
||||
return useStartTLS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether startTLS will be used for connections.
|
||||
*
|
||||
* @param b whether startTLS will be used
|
||||
*/
|
||||
public void setUseStartTLS(final boolean b) {
|
||||
useStartTLS = b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connection initializers.
|
||||
*
|
||||
* @return connection initializers
|
||||
*/
|
||||
public ConnectionInitializer[] getConnectionInitializers() {
|
||||
return connectionInitializers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the connection initializers.
|
||||
*
|
||||
* @param initializers connection initializers
|
||||
*/
|
||||
public void setConnectionInitializers(final ConnectionInitializer... initializers) {
|
||||
checkArrayContainsNull(initializers);
|
||||
connectionInitializers = initializers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connection strategy.
|
||||
*
|
||||
* @return strategy for making connections
|
||||
*/
|
||||
public ConnectionStrategy getConnectionStrategy() {
|
||||
return connectionStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the connection strategy.
|
||||
*
|
||||
* @param strategy for making new connections
|
||||
*/
|
||||
public void setConnectionStrategy(final ConnectionStrategy strategy) {
|
||||
connectionStrategy = strategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connection validator.
|
||||
*
|
||||
* @return connection validator
|
||||
*/
|
||||
public ConnectionValidator getConnectionValidator() {
|
||||
return connectionValidator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the connection validator.
|
||||
*
|
||||
* @param validator for validating connections
|
||||
*/
|
||||
public void setConnectionValidator(final ConnectionValidator validator) {
|
||||
connectionValidator = validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns transport options.
|
||||
*
|
||||
* @return transport options
|
||||
*/
|
||||
public Map<String, ?> getTransportOptions() {
|
||||
return transportOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets transport options.
|
||||
*
|
||||
* @param options to set
|
||||
*/
|
||||
public void setTransportOptions(final Map<String, ?> options) {
|
||||
transportOptions.putAll(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a transport option.
|
||||
*
|
||||
* @param id transport option id
|
||||
* @return transport option
|
||||
*/
|
||||
public Object getTransportOption(final String id) {
|
||||
return transportOptions.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a transport option.
|
||||
*
|
||||
* @param id of the transport option
|
||||
* @param value of the transport option
|
||||
*/
|
||||
public void setTransportOption(final String id, final Object value) {
|
||||
transportOptions.put(id, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" +
|
||||
getClass().getName() + "@" + hashCode() + "::" +
|
||||
"ldapUrl=" + ldapUrl + ", " +
|
||||
"connectTimeout=" + connectTimeout + ", " +
|
||||
"startTLSTimeout=" + startTLSTimeout + ", " +
|
||||
"responseTimeout=" + responseTimeout + ", " +
|
||||
"reconnectTimeout=" + reconnectTimeout + ", " +
|
||||
"autoReconnect=" + autoReconnect + ", " +
|
||||
"autoReconnectCondition=" + autoReconnectCondition + ", " +
|
||||
"autoReplay=" + autoReplay + ", " +
|
||||
"sslConfig=" + sslConfig + ", " +
|
||||
"useStartTLS=" + useStartTLS + ", " +
|
||||
"connectionInitializers=" + Arrays.toString(connectionInitializers) + ", " +
|
||||
"connectionStrategy=" + connectionStrategy + ", " +
|
||||
"connectionValidator=" + connectionValidator + ", " +
|
||||
"transportOptions=" + transportOptions + "]";
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final ConnectionConfig object = new ConnectionConfig();
|
||||
|
||||
|
||||
protected Builder() {
|
||||
}
|
||||
|
||||
|
||||
public Builder url(final String url) {
|
||||
object.setLdapUrl(url);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder connectTimeout(final Duration timeout) {
|
||||
object.setConnectTimeout(timeout);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder responseTimeout(final Duration timeout) {
|
||||
object.setResponseTimeout(timeout);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder startTLSTimeout(final Duration timeout) {
|
||||
object.setStartTLSTimeout(timeout);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder reconnectTimeout(final Duration timeout) {
|
||||
object.setReconnectTimeout(timeout);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder autoReconnect(final boolean b) {
|
||||
object.setAutoReconnect(b);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder autoReconnectCondition(final Predicate<RetryMetadata> predicate) {
|
||||
object.setAutoReconnectCondition(predicate);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder autoReplay(final boolean b) {
|
||||
object.setAutoReplay(b);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder sslConfig(final SslConfig config) {
|
||||
object.setSslConfig(config);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder useStartTLS(final boolean b) {
|
||||
object.setUseStartTLS(b);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder connectionInitializers(final ConnectionInitializer... initializers) {
|
||||
object.setConnectionInitializers(initializers);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder connectionStrategy(final ConnectionStrategy strategy) {
|
||||
object.setConnectionStrategy(strategy);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder connectionValidator(final ConnectionValidator validator) {
|
||||
object.setConnectionValidator(validator);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder transportOption(final String id, final Object value) {
|
||||
object.setTransportOption(id, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public ConnectionConfig build() {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
// CheckStyle:ON
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
/**
|
||||
* Interface for connection factories.
|
||||
*
|
||||
*/
|
||||
public interface ConnectionFactory {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new connection.
|
||||
*
|
||||
* @return connection
|
||||
* @throws LdapException if a connection cannot be returned
|
||||
*/
|
||||
Connection getConnection()
|
||||
throws LdapException;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the connection configuration used to create connections.
|
||||
*
|
||||
* @return connection config
|
||||
*/
|
||||
ConnectionConfig getConnectionConfig();
|
||||
|
||||
|
||||
/**
|
||||
* Free any resources associated with this factory.
|
||||
*/
|
||||
void close();
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
/**
|
||||
* Interface for objects that manage an instance of connection factory.
|
||||
*
|
||||
*/
|
||||
public interface ConnectionFactoryManager {
|
||||
|
||||
|
||||
/**
|
||||
* Returns the connection factory.
|
||||
*
|
||||
* @return connection factory
|
||||
*/
|
||||
ConnectionFactory getConnectionFactory();
|
||||
|
||||
|
||||
/**
|
||||
* Sets the connection factory.
|
||||
*
|
||||
* @param cf connection factory
|
||||
*/
|
||||
void setConnectionFactory(ConnectionFactory cf);
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
/**
|
||||
* Interface to describe the state of the connection factory. Used by {@link ConnectionStrategy} to produce LDAP URLs.
|
||||
*
|
||||
*/
|
||||
public interface ConnectionFactoryMetadata {
|
||||
|
||||
|
||||
/**
|
||||
* Returns the LDAP URL the connection factory is using. May be space delimited for multiple URLs.
|
||||
*
|
||||
* @return ldap url
|
||||
*/
|
||||
String getLdapUrl();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of times the connection factory has created a connection.
|
||||
*
|
||||
* @return connection count
|
||||
*/
|
||||
int getConnectionCount();
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
/**
|
||||
* Provides an interface for initializing connections after they are opened.
|
||||
*
|
||||
*/
|
||||
public interface ConnectionInitializer {
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the supplied connection.
|
||||
*
|
||||
* @param conn connection to initialize
|
||||
* @return result associated with the initialization or an empty result
|
||||
* @throws LdapException if initialization fails
|
||||
*/
|
||||
Result initialize(Connection conn)
|
||||
throws LdapException;
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Interface to describe various connection strategies. Each strategy returns an ordered list of LDAP URLs to attempt
|
||||
* when opening a connection.
|
||||
*
|
||||
*/
|
||||
public interface ConnectionStrategy extends Iterable<LdapURL> {
|
||||
|
||||
|
||||
/**
|
||||
* Populates a {@link LdapURLSet} from the URL string provided at configuration time.
|
||||
*
|
||||
* @param urls Space-delimited string of URLs describing the LDAP hosts to connect to. The URLs in the string
|
||||
* are commonly {@code ldap://} or {@code ldaps://} URLs that directly describe the hosts to connect to,
|
||||
* but may also describe a resource from which to obtain LDAP connection URLs as is the case for
|
||||
* {@link DnsSrvConnectionStrategy} that use URLs with the scheme {@code dns:}.
|
||||
* @param urlSet LDAP URL set to populate.
|
||||
*/
|
||||
void populate(String urls, LdapURLSet urlSet);
|
||||
|
||||
|
||||
/**
|
||||
* Prepare this strategy for use.
|
||||
*
|
||||
* @param urls LDAP URLs for this strategy
|
||||
* @param activateCondition predicate to determine whether a connection is active
|
||||
*/
|
||||
void initialize(String urls, Predicate<LdapURL> activateCondition);
|
||||
|
||||
|
||||
/**
|
||||
* Whether this strategy is ready for use.
|
||||
*
|
||||
* @return whether this strategy is ready for use
|
||||
*/
|
||||
boolean isInitialized();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the condition used to activate connections.
|
||||
*
|
||||
* @return activate condition
|
||||
*/
|
||||
Predicate<LdapURL> getActivateCondition();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the condition used to determine whether to attempt to activate a connection.
|
||||
*
|
||||
* @return retry condition
|
||||
*/
|
||||
Predicate<LdapURL> getRetryCondition();
|
||||
|
||||
|
||||
/**
|
||||
* Indicates the supplied URL was successfully connected to.
|
||||
*
|
||||
* @param url which was successfully connected to
|
||||
*/
|
||||
void success(LdapURL url);
|
||||
|
||||
|
||||
/**
|
||||
* Indicates the supplied URL could not be connected to.
|
||||
*
|
||||
* @param url which was could not be connected to
|
||||
*/
|
||||
void failure(LdapURL url);
|
||||
|
||||
|
||||
/**
|
||||
* Create a deep copy of this strategy.
|
||||
*
|
||||
* @return new instance of this connection strategy
|
||||
*/
|
||||
ConnectionStrategy newInstance();
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Provides an interface for defining connection validation.
|
||||
*
|
||||
*/
|
||||
public interface ConnectionValidator extends Function<Connection, Boolean> {
|
||||
|
||||
|
||||
/**
|
||||
* Provides an asynchronous implementation of {@link #apply(Object)}. The supplied consumer will be invoked with the
|
||||
* validation result. {@link #getValidateTimeout()} must be enforced by the caller.
|
||||
*
|
||||
* @param conn to validate
|
||||
* @param function to consume the validation result
|
||||
*/
|
||||
void applyAsync(Connection conn, Consumer<Boolean> function);
|
||||
|
||||
|
||||
/**
|
||||
* Provides an asynchronous implementation of {@link #apply(Object)}. The returned supplier will block until a
|
||||
* validation result is received respecting {@link #getValidateTimeout()}.
|
||||
*
|
||||
* @param conn to validate
|
||||
* @return supplier to retrieve the validation result
|
||||
*/
|
||||
Supplier<Boolean> applyAsync(Connection conn);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the interval at which the validation task will be executed.
|
||||
*
|
||||
* @return validation period
|
||||
*/
|
||||
Duration getValidatePeriod();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the duration at which a validate operation should be abandoned.
|
||||
*
|
||||
* @return validation timeout
|
||||
*/
|
||||
Duration getValidateTimeout();
|
||||
}
|
89
net-ldap/src/main/java/org/xbib/net/ldap/Credential.java
Normal file
89
net-ldap/src/main/java/org/xbib/net/ldap/Credential.java
Normal file
|
@ -0,0 +1,89 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
/**
|
||||
* Provides convenience methods for converting the various types of passwords into a byte array.
|
||||
*
|
||||
*/
|
||||
public class Credential {
|
||||
|
||||
/**
|
||||
* Credential stored as a byte array.
|
||||
*/
|
||||
private final byte[] bytes;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new credential.
|
||||
*
|
||||
* @param password converted from UTF-8 to a byte array
|
||||
*/
|
||||
public Credential(final String password) {
|
||||
if (password == null) {
|
||||
throw new NullPointerException("Password cannot be null");
|
||||
}
|
||||
bytes = LdapUtils.utf8Encode(password, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new credential.
|
||||
*
|
||||
* @param password converted from UTF-8 to a byte array
|
||||
*/
|
||||
public Credential(final char[] password) {
|
||||
if (password == null) {
|
||||
throw new NullPointerException("Password cannot be null");
|
||||
}
|
||||
bytes = LdapUtils.utf8Encode(new String(password), false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new credential.
|
||||
*
|
||||
* @param password to store
|
||||
*/
|
||||
public Credential(final byte[] password) {
|
||||
if (password == null) {
|
||||
throw new NullPointerException("Password cannot be null");
|
||||
}
|
||||
bytes = password;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns this credential as a byte array.
|
||||
*
|
||||
* @return credential bytes
|
||||
*/
|
||||
public byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns this credential as a string.
|
||||
*
|
||||
* @return credential string
|
||||
*/
|
||||
public String getString() {
|
||||
return LdapUtils.utf8Encode(bytes, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns this credential as a character array.
|
||||
*
|
||||
* @return credential characters
|
||||
*/
|
||||
public char[] getChars() {
|
||||
return getString().toCharArray();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + getClass().getName() + "@" + hashCode() + "::" + "bytes=" + LdapUtils.utf8Encode(bytes) + "]";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import org.xbib.net.ldap.transport.Transport;
|
||||
import org.xbib.net.ldap.transport.TransportFactory;
|
||||
|
||||
/**
|
||||
* Creates connections for performing ldap operations.
|
||||
*
|
||||
*/
|
||||
public class DefaultConnectionFactory implements ConnectionFactory {
|
||||
|
||||
/**
|
||||
* Transport used by this factory.
|
||||
*/
|
||||
private final Transport transport;
|
||||
|
||||
/**
|
||||
* Connection configuration used by this factory.
|
||||
*/
|
||||
private ConnectionConfig config;
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public DefaultConnectionFactory() {
|
||||
this(TransportFactory.getTransport(DefaultConnectionFactory.class));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new default connection factory. Be sure to invoke {@link #close()} if the supplied transport has
|
||||
* resources to cleanup.
|
||||
*
|
||||
* @param t transport
|
||||
*/
|
||||
public DefaultConnectionFactory(final Transport t) {
|
||||
transport = t;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new default connection factory.
|
||||
*
|
||||
* @param ldapUrl to connect to
|
||||
*/
|
||||
public DefaultConnectionFactory(final String ldapUrl) {
|
||||
this(new ConnectionConfig(ldapUrl));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new default connection factory. Be sure to invoke {@link #close()} if the supplied transport has
|
||||
* resources to cleanup.
|
||||
*
|
||||
* @param ldapUrl to connect to
|
||||
* @param t transport
|
||||
*/
|
||||
public DefaultConnectionFactory(final String ldapUrl, final Transport t) {
|
||||
this(new ConnectionConfig(ldapUrl), t);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new default connection factory.
|
||||
*
|
||||
* @param cc connection configuration
|
||||
*/
|
||||
public DefaultConnectionFactory(final ConnectionConfig cc) {
|
||||
this(cc, TransportFactory.getTransport(DefaultConnectionFactory.class));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new default connection factory. Be sure to invoke {@link #close()} if the supplied transport has
|
||||
* resources to cleanup.
|
||||
*
|
||||
* @param cc connection configuration
|
||||
* @param t transport
|
||||
*/
|
||||
public DefaultConnectionFactory(final ConnectionConfig cc, final Transport t) {
|
||||
transport = t;
|
||||
setConnectionConfig(cc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for this class.
|
||||
*
|
||||
* @return new builder
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for this class.
|
||||
*
|
||||
* @param t transport
|
||||
* @return new builder
|
||||
*/
|
||||
public static Builder builder(final Transport t) {
|
||||
return new Builder(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConnectionConfig getConnectionConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the connection config.
|
||||
*
|
||||
* @param cc connection config
|
||||
*/
|
||||
public void setConnectionConfig(final ConnectionConfig cc) {
|
||||
config = cc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ldap transport.
|
||||
*
|
||||
* @return ldap transport
|
||||
*/
|
||||
public Transport getTransport() {
|
||||
return transport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new connection. Connections returned from this method must be opened before they can perform ldap
|
||||
* operations.
|
||||
*
|
||||
* @return connection
|
||||
*/
|
||||
@Override
|
||||
public Connection getConnection() {
|
||||
return transport.create(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
transport.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" +
|
||||
getClass().getName() + "@" + hashCode() + "::" +
|
||||
"transport=" + transport + ", " +
|
||||
"config=" + config + "]";
|
||||
}
|
||||
|
||||
// CheckStyle:OFF
|
||||
public static class Builder {
|
||||
|
||||
|
||||
private final DefaultConnectionFactory object;
|
||||
|
||||
|
||||
protected Builder() {
|
||||
object = new DefaultConnectionFactory();
|
||||
}
|
||||
|
||||
|
||||
protected Builder(final Transport t) {
|
||||
object = new DefaultConnectionFactory(t);
|
||||
}
|
||||
|
||||
|
||||
public Builder config(final ConnectionConfig cc) {
|
||||
object.setConnectionConfig(cc);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public DefaultConnectionFactory build() {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
// CheckStyle:ON
|
||||
}
|
149
net-ldap/src/main/java/org/xbib/net/ldap/DeleteOperation.java
Normal file
149
net-ldap/src/main/java/org/xbib/net/ldap/DeleteOperation.java
Normal file
|
@ -0,0 +1,149 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
/**
|
||||
* Executes an ldap delete operation.
|
||||
*
|
||||
*/
|
||||
public class DeleteOperation extends AbstractOperation<DeleteRequest, DeleteResponse> {
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public DeleteOperation() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new delete operation.
|
||||
*
|
||||
* @param factory connection factory
|
||||
*/
|
||||
public DeleteOperation(final ConnectionFactory factory) {
|
||||
super(factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a delete request. See {@link OperationHandle#send()}.
|
||||
*
|
||||
* @param factory connection factory
|
||||
* @param request delete request
|
||||
* @return operation handle
|
||||
* @throws LdapException if the connection cannot be opened
|
||||
*/
|
||||
public static OperationHandle<DeleteRequest, DeleteResponse> send(
|
||||
final ConnectionFactory factory,
|
||||
final DeleteRequest request)
|
||||
throws LdapException {
|
||||
final Connection conn = factory.getConnection();
|
||||
try {
|
||||
conn.open();
|
||||
} catch (Exception e) {
|
||||
conn.close();
|
||||
throw e;
|
||||
}
|
||||
return conn.operation(request).onComplete(conn::close).send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a delete request. See {@link OperationHandle#execute()}.
|
||||
*
|
||||
* @param factory connection factory
|
||||
* @param request delete request
|
||||
* @return delete result
|
||||
* @throws LdapException if the connection cannot be opened
|
||||
*/
|
||||
public static DeleteResponse execute(final ConnectionFactory factory, final DeleteRequest request)
|
||||
throws LdapException {
|
||||
try (Connection conn = factory.getConnection()) {
|
||||
conn.open();
|
||||
return conn.operation(request).execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new delete operation with the same properties as the supplied operation.
|
||||
*
|
||||
* @param operation to copy
|
||||
* @return copy of the supplied delete operation
|
||||
*/
|
||||
public static DeleteOperation copy(final DeleteOperation operation) {
|
||||
final DeleteOperation op = new DeleteOperation();
|
||||
op.setRequestHandlers(operation.getRequestHandlers());
|
||||
op.setResultHandlers(operation.getResultHandlers());
|
||||
op.setControlHandlers(operation.getControlHandlers());
|
||||
op.setReferralHandlers(operation.getReferralHandlers());
|
||||
op.setIntermediateResponseHandlers(operation.getIntermediateResponseHandlers());
|
||||
op.setExceptionHandler(operation.getExceptionHandler());
|
||||
op.setThrowCondition(operation.getThrowCondition());
|
||||
op.setUnsolicitedNotificationHandlers(operation.getUnsolicitedNotificationHandlers());
|
||||
op.setConnectionFactory(operation.getConnectionFactory());
|
||||
return op;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for this class.
|
||||
*
|
||||
* @return new builder
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a delete request. See {@link OperationHandle#send()}.
|
||||
*
|
||||
* @param request delete request
|
||||
* @return operation handle
|
||||
* @throws LdapException if the connection cannot be opened
|
||||
*/
|
||||
@Override
|
||||
public OperationHandle<DeleteRequest, DeleteResponse> send(final DeleteRequest request)
|
||||
throws LdapException {
|
||||
final Connection conn = getConnectionFactory().getConnection();
|
||||
try {
|
||||
conn.open();
|
||||
} catch (Exception e) {
|
||||
conn.close();
|
||||
throw e;
|
||||
}
|
||||
return configureHandle(conn.operation(configureRequest(request))).onComplete(conn::close).send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a delete request. See {@link OperationHandle#execute()}.
|
||||
*
|
||||
* @param request delete request
|
||||
* @return delete result
|
||||
* @throws LdapException if the connection cannot be opened
|
||||
*/
|
||||
@Override
|
||||
public DeleteResponse execute(final DeleteRequest request)
|
||||
throws LdapException {
|
||||
try (Connection conn = getConnectionFactory().getConnection()) {
|
||||
conn.open();
|
||||
return configureHandle(conn.operation(configureRequest(request))).execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete operation builder.
|
||||
*/
|
||||
public static class Builder extends AbstractOperation.AbstractBuilder<DeleteOperation.Builder, DeleteOperation> {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new builder.
|
||||
*/
|
||||
protected Builder() {
|
||||
super(new DeleteOperation());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Builder self() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
108
net-ldap/src/main/java/org/xbib/net/ldap/DeleteRequest.java
Normal file
108
net-ldap/src/main/java/org/xbib/net/ldap/DeleteRequest.java
Normal file
|
@ -0,0 +1,108 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import org.xbib.asn1.ApplicationDERTag;
|
||||
import org.xbib.asn1.DEREncoder;
|
||||
import org.xbib.asn1.IntegerType;
|
||||
import org.xbib.asn1.OctetStringType;
|
||||
|
||||
/**
|
||||
* LDAP delete request defined as:
|
||||
*
|
||||
* <pre>
|
||||
* DelRequest ::= [APPLICATION 10] LDAPDN
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public class DeleteRequest extends AbstractRequestMessage {
|
||||
|
||||
/**
|
||||
* BER protocol number.
|
||||
*/
|
||||
public static final int PROTOCOL_OP = 10;
|
||||
|
||||
/**
|
||||
* LDAP DN to delete.
|
||||
*/
|
||||
private String ldapDn;
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
private DeleteRequest() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new delete request.
|
||||
*
|
||||
* @param dn DN to delete
|
||||
*/
|
||||
public DeleteRequest(final String dn) {
|
||||
ldapDn = dn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for this class.
|
||||
*
|
||||
* @return new builder
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DN.
|
||||
*
|
||||
* @return DN
|
||||
*/
|
||||
public String getDn() {
|
||||
return ldapDn;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DEREncoder[] getRequestEncoders(final int id) {
|
||||
return new DEREncoder[]{
|
||||
new IntegerType(id),
|
||||
new OctetStringType(new ApplicationDERTag(PROTOCOL_OP, false), ldapDn),
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ", " + "dn=" + ldapDn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete request builder.
|
||||
*/
|
||||
public static class Builder extends AbstractRequestMessage.AbstractBuilder<DeleteRequest.Builder, DeleteRequest> {
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
protected Builder() {
|
||||
super(new DeleteRequest());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Builder self() {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the ldap DN.
|
||||
*
|
||||
* @param dn ldap DN
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder dn(final String dn) {
|
||||
object.ldapDn = dn;
|
||||
return self();
|
||||
}
|
||||
}
|
||||
}
|
117
net-ldap/src/main/java/org/xbib/net/ldap/DeleteResponse.java
Normal file
117
net-ldap/src/main/java/org/xbib/net/ldap/DeleteResponse.java
Normal file
|
@ -0,0 +1,117 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import org.xbib.asn1.DERBuffer;
|
||||
import org.xbib.asn1.DERParser;
|
||||
import org.xbib.asn1.DERPath;
|
||||
|
||||
/**
|
||||
* LDAP delete response defined as:
|
||||
*
|
||||
* <pre>
|
||||
* DelResponse ::= [APPLICATION 11] LDAPResult
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public class DeleteResponse extends AbstractResult {
|
||||
|
||||
/**
|
||||
* BER protocol number.
|
||||
*/
|
||||
public static final int PROTOCOL_OP = 11;
|
||||
|
||||
/**
|
||||
* hash code seed.
|
||||
*/
|
||||
private static final int HASH_CODE_SEED = 10253;
|
||||
|
||||
/**
|
||||
* DER path to result code.
|
||||
*/
|
||||
private static final DERPath RESULT_CODE_PATH = new DERPath("/SEQ/APP(11)/ENUM[0]");
|
||||
|
||||
/**
|
||||
* DER path to matched DN.
|
||||
*/
|
||||
private static final DERPath MATCHED_DN_PATH = new DERPath("/SEQ/APP(11)/OCTSTR[1]");
|
||||
|
||||
/**
|
||||
* DER path to diagnostic message.
|
||||
*/
|
||||
private static final DERPath DIAGNOSTIC_MESSAGE_PATH = new DERPath("/SEQ/APP(11)/OCTSTR[2]");
|
||||
|
||||
/**
|
||||
* DER path to referral.
|
||||
*/
|
||||
private static final DERPath REFERRAL_PATH = new DERPath("/SEQ/APP(11)/CTX(3)/OCTSTR[0]");
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
private DeleteResponse() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new delete response.
|
||||
*
|
||||
* @param buffer to decode
|
||||
*/
|
||||
public DeleteResponse(final DERBuffer buffer) {
|
||||
final DERParser parser = new DERParser();
|
||||
parser.registerHandler(MessageIDHandler.PATH, new MessageIDHandler(this));
|
||||
parser.registerHandler(RESULT_CODE_PATH, new ResultCodeHandler(this));
|
||||
parser.registerHandler(MATCHED_DN_PATH, new MatchedDNHandler(this));
|
||||
parser.registerHandler(DIAGNOSTIC_MESSAGE_PATH, new DiagnosticMessageHandler(this));
|
||||
parser.registerHandler(REFERRAL_PATH, new ReferralHandler(this));
|
||||
parser.registerHandler(ControlsHandler.PATH, new ControlsHandler(this));
|
||||
parser.parse(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for this class.
|
||||
*
|
||||
* @return new builder
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
return o instanceof DeleteResponse && super.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return
|
||||
LdapUtils.computeHashCode(
|
||||
HASH_CODE_SEED,
|
||||
getMessageID(),
|
||||
getControls(),
|
||||
getResultCode(),
|
||||
getMatchedDN(),
|
||||
getDiagnosticMessage(),
|
||||
getReferralURLs());
|
||||
}
|
||||
|
||||
// CheckStyle:OFF
|
||||
public static class Builder extends AbstractResult.AbstractBuilder<Builder, DeleteResponse> {
|
||||
|
||||
|
||||
protected Builder() {
|
||||
super(new DeleteResponse());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Builder self() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
// CheckStyle:ON
|
||||
}
|
29
net-ldap/src/main/java/org/xbib/net/ldap/DerefAliases.java
Normal file
29
net-ldap/src/main/java/org/xbib/net/ldap/DerefAliases.java
Normal file
|
@ -0,0 +1,29 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
/**
|
||||
* Enum to define how aliases are dereferenced.
|
||||
*
|
||||
*/
|
||||
public enum DerefAliases {
|
||||
|
||||
/**
|
||||
* never dereference aliases.
|
||||
*/
|
||||
NEVER,
|
||||
|
||||
/**
|
||||
* dereference when searching the entries beneath the starting point but not when searching for the starting entry.
|
||||
*/
|
||||
SEARCHING,
|
||||
|
||||
/**
|
||||
* dereference when searching for the starting entry but not when searching the entries beneath the starting point.
|
||||
*/
|
||||
FINDING,
|
||||
|
||||
/**
|
||||
* dereference when searching for the starting entry and when searching the entries beneath the starting point.
|
||||
*/
|
||||
ALWAYS
|
||||
}
|
|
@ -0,0 +1,175 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Connection strategy that tries all IP addresses resolved from DNS. The order of IP addressees returned can be
|
||||
* controlled via the java.net.preferIPv4Stack or java.net.preferIPv6Addresses system property flags. This strategy
|
||||
* operates in an active/passive fashion.
|
||||
*
|
||||
*/
|
||||
public class DnsResolverConnectionStrategy extends AbstractConnectionStrategy {
|
||||
|
||||
/**
|
||||
* Default time to live for DNS results.
|
||||
*/
|
||||
protected static final Duration DEFAULT_TTL = Duration.ofHours(6);
|
||||
|
||||
/**
|
||||
* Custom iterator function.
|
||||
*/
|
||||
private final Function<List<LdapURL>, Iterator<LdapURL>> iterFunction;
|
||||
|
||||
/**
|
||||
* Time to live for DNS records.
|
||||
*/
|
||||
private final Duration dnsTtl;
|
||||
|
||||
/**
|
||||
* Name resolver function.
|
||||
*/
|
||||
private Function<String, InetAddress[]> resolverFunction = name -> {
|
||||
try {
|
||||
return InetAddress.getAllByName(name);
|
||||
} catch (UnknownHostException e) {
|
||||
throw new IllegalStateException("Could not resolve IP address for " + name, e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* LDAP URL string used to initialize this strategy.
|
||||
*/
|
||||
private String ldapUrls;
|
||||
|
||||
/**
|
||||
* DNS expiration time.
|
||||
*/
|
||||
private Instant expirationTime;
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public DnsResolverConnectionStrategy() {
|
||||
this(DEFAULT_TTL);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new DNS resolver connection strategy.
|
||||
*
|
||||
* @param ttl time to live for DNS records
|
||||
*/
|
||||
public DnsResolverConnectionStrategy(final Duration ttl) {
|
||||
this(null, ttl);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new DNS connection strategy.
|
||||
*
|
||||
* @param function that produces a custom iterator
|
||||
*/
|
||||
public DnsResolverConnectionStrategy(final Function<List<LdapURL>, Iterator<LdapURL>> function) {
|
||||
this(function, DEFAULT_TTL);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new DNS resolver connection strategy.
|
||||
*
|
||||
* @param function that produces a custom iterator
|
||||
* @param ttl time to live for DNS records
|
||||
*/
|
||||
public DnsResolverConnectionStrategy(final Function<List<LdapURL>, Iterator<LdapURL>> function, final Duration ttl) {
|
||||
iterFunction = function;
|
||||
dnsTtl = ttl;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the name resolution function.
|
||||
*
|
||||
* @return name resolution function
|
||||
*/
|
||||
public Function<String, InetAddress[]> getResolverFunction() {
|
||||
return resolverFunction;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the function used to resolve names.
|
||||
*
|
||||
* @param func to set
|
||||
*/
|
||||
public void setResolverFunction(final Function<String, InetAddress[]> func) {
|
||||
resolverFunction = func;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Iterator<LdapURL> iterator() {
|
||||
if (!isInitialized()) {
|
||||
throw new IllegalStateException("Strategy is not initialized");
|
||||
}
|
||||
if (Instant.now().isAfter(expirationTime)) {
|
||||
populate(ldapUrls, ldapURLSet);
|
||||
}
|
||||
if (iterFunction != null) {
|
||||
return iterFunction.apply(ldapURLSet.getUrls());
|
||||
}
|
||||
return new DefaultLdapURLIterator(ldapURLSet.getUrls());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void populate(final String urls, final LdapURLSet urlSet) {
|
||||
if (urls == null || urls.isEmpty()) {
|
||||
throw new IllegalArgumentException("urls cannot be empty or null");
|
||||
}
|
||||
ldapUrls = urls;
|
||||
if (urls.contains(" ")) {
|
||||
urlSet.populate(Stream.of(urls.split(" "))
|
||||
.flatMap(s -> {
|
||||
final List<LdapURL> l = new ArrayList<>(2);
|
||||
final LdapURL parsedUrl = new LdapURL(s);
|
||||
for (InetAddress address : resolverFunction.apply(parsedUrl.getHostname())) {
|
||||
final LdapURL url = LdapURL.copy(parsedUrl);
|
||||
url.setRetryMetadata(new LdapURLRetryMetadata(this));
|
||||
url.setInetAddress(address);
|
||||
l.add(url);
|
||||
}
|
||||
return l.stream();
|
||||
}).collect(Collectors.toList()));
|
||||
} else {
|
||||
final LdapURL parsedUrl = new LdapURL(urls);
|
||||
urlSet.populate(Stream.of(resolverFunction.apply(parsedUrl.getHostname()))
|
||||
.map(ip -> {
|
||||
final LdapURL url = LdapURL.copy(parsedUrl);
|
||||
url.setRetryMetadata(new LdapURLRetryMetadata(this));
|
||||
url.setInetAddress(ip);
|
||||
return url;
|
||||
}).collect(Collectors.toList()));
|
||||
}
|
||||
expirationTime = Instant.now().plus(dnsTtl);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DnsResolverConnectionStrategy newInstance() {
|
||||
final DnsResolverConnectionStrategy strategy = new DnsResolverConnectionStrategy(iterFunction, dnsTtl);
|
||||
strategy.setResolverFunction(resolverFunction);
|
||||
strategy.setRetryCondition(getRetryCondition());
|
||||
return strategy;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,253 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.xbib.net.ldap.dn.Dn;
|
||||
import org.xbib.net.ldap.dns.DNSContextFactory;
|
||||
import org.xbib.net.ldap.dns.DNSDomainFunction;
|
||||
import org.xbib.net.ldap.dns.DefaultDNSContextFactory;
|
||||
import org.xbib.net.ldap.dns.SRVDNSResolver;
|
||||
import org.xbib.net.ldap.dns.SRVRecord;
|
||||
|
||||
/**
|
||||
* DNS SRV connection strategy. Queries a DNS server for SRV records and uses those records to construct a list of URLs.
|
||||
* A time to live can be set to control how often the DNS server is consulted. See http://www.ietf.org/rfc/rfc2782.txt.
|
||||
*
|
||||
*/
|
||||
public class DnsSrvConnectionStrategy extends AbstractConnectionStrategy {
|
||||
|
||||
/**
|
||||
* Default time to live for DNS results.
|
||||
*/
|
||||
protected static final Duration DEFAULT_TTL = Duration.ofHours(6);
|
||||
|
||||
/**
|
||||
* DNS context factory to override initialization parameters.
|
||||
*/
|
||||
private final DNSContextFactory dnsContextFactory;
|
||||
|
||||
/**
|
||||
* Time to live for SRV records.
|
||||
*/
|
||||
private final Duration srvTtl;
|
||||
|
||||
/**
|
||||
* Connect to LDAP using LDAPS.
|
||||
*/
|
||||
private final boolean useSSL;
|
||||
|
||||
/**
|
||||
* LDAP URL string used to initialize this strategy.
|
||||
*/
|
||||
private String ldapUrls;
|
||||
|
||||
/**
|
||||
* Resolver(s) for SRV DNS records.
|
||||
*/
|
||||
private Map<SRVDNSResolver, String> dnsResolvers;
|
||||
|
||||
/**
|
||||
* SRV records expiration time.
|
||||
*/
|
||||
private Instant expirationTime;
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public DnsSrvConnectionStrategy() {
|
||||
this(DEFAULT_TTL);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new DNS SRV connection strategy.
|
||||
*
|
||||
* @param ttl time to live for SRV records
|
||||
*/
|
||||
public DnsSrvConnectionStrategy(final Duration ttl) {
|
||||
this(null, ttl);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new DNS SRV connection strategy.
|
||||
*
|
||||
* @param factory DNS context factory
|
||||
*/
|
||||
public DnsSrvConnectionStrategy(final DNSContextFactory factory) {
|
||||
this(factory, DEFAULT_TTL);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new DNS SRV connection strategy.
|
||||
*
|
||||
* @param factory DNS context factory
|
||||
* @param ttl time to live for SRV records
|
||||
*/
|
||||
public DnsSrvConnectionStrategy(final DNSContextFactory factory, final Duration ttl) {
|
||||
this(factory, ttl, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new DNS SRV connection strategy.
|
||||
*
|
||||
* @param factory DNS context factory
|
||||
* @param ttl time to live for SRV records
|
||||
* @param ssl whether SRV records should produce LDAPS URLs
|
||||
*/
|
||||
public DnsSrvConnectionStrategy(final DNSContextFactory factory, final Duration ttl, final boolean ssl) {
|
||||
dnsContextFactory = factory;
|
||||
srvTtl = ttl;
|
||||
useSSL = ssl;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void populate(final String urls, final LdapURLSet urlSet) {
|
||||
ldapUrls = urls;
|
||||
// SRV records are ordered by priority then weight.
|
||||
// Thus LdapURLSet will be organized by decreasing precedence.
|
||||
final List<LdapURL> list = readSrvRecords(ldapUrls)
|
||||
.stream()
|
||||
.map(srv -> {
|
||||
final LdapURL url = srv.getLdapURL();
|
||||
url.setRetryMetadata(new LdapURLRetryMetadata(this));
|
||||
return url;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
urlSet.populate(list);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses the supplied DNS URL string and reads SRV records from DNS.
|
||||
*
|
||||
* @param urls to parse
|
||||
* @return Set of DNS SRV records ordered first by priority and then by weight.
|
||||
*/
|
||||
protected Set<SRVRecord> readSrvRecords(final String urls) {
|
||||
if (urls == null) {
|
||||
dnsResolvers = Collections.singletonMap(
|
||||
new SRVDNSResolver(
|
||||
Objects.requireNonNullElseGet(dnsContextFactory, DefaultDNSContextFactory::new), useSSL), null);
|
||||
} else if (urls.contains(" ")) {
|
||||
dnsResolvers = new HashMap<>();
|
||||
for (String url : urls.split(" ")) {
|
||||
final String[] dnsUrl = parseUrl(url);
|
||||
dnsResolvers.put(
|
||||
new SRVDNSResolver(
|
||||
Objects.requireNonNullElseGet(dnsContextFactory, () -> new DefaultDNSContextFactory(dnsUrl[0])), useSSL),
|
||||
dnsUrl[1]);
|
||||
}
|
||||
} else {
|
||||
final String[] dnsUrl = parseUrl(urls);
|
||||
dnsResolvers = Collections.singletonMap(
|
||||
new SRVDNSResolver(
|
||||
Objects.requireNonNullElseGet(
|
||||
dnsContextFactory, () -> new DefaultDNSContextFactory(dnsUrl[0])), useSSL), dnsUrl[1]);
|
||||
}
|
||||
final Set<SRVRecord> srvRecords = retrieveDNSRecords();
|
||||
if (srvRecords.isEmpty()) {
|
||||
expirationTime = Instant.now();
|
||||
} else {
|
||||
expirationTime = Instant.now().plus(srvTtl);
|
||||
}
|
||||
return srvRecords;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses the supplied URL. If the URL has an ldap scheme, it is inspected for a baseDN which will be used as the
|
||||
* domain. Otherwise, the URL is assumed to have a dns scheme.
|
||||
*
|
||||
* @param url to parse
|
||||
* @return array containing the DNS URL and the record name in that order
|
||||
*/
|
||||
protected String[] parseUrl(final String url) {
|
||||
final LdapURL ldapURL;
|
||||
try {
|
||||
ldapURL = new LdapURL(url);
|
||||
} catch (Exception e) {
|
||||
return parseDnsUrl(url);
|
||||
}
|
||||
if (ldapURL.getBaseDn() == null || ldapURL.getBaseDn().isEmpty()) {
|
||||
throw new IllegalArgumentException("LDAP URL " + url + " must contain a base DN");
|
||||
}
|
||||
final String domain = new DNSDomainFunction().apply(new Dn(ldapURL.getBaseDn()));
|
||||
if (domain.isEmpty()) {
|
||||
throw new IllegalArgumentException("Base DN " + ldapURL.getBaseDn() + " could not be converted to a domain");
|
||||
}
|
||||
return new String[]{null, "_ldap._tcp.".concat(domain)};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses a DNS URL of the form dns://hostname/domain?record. Where record is the DNS record to retrieve.
|
||||
*
|
||||
* @param url to parse
|
||||
* @return array containing the DNS URL and the record name in that order
|
||||
*/
|
||||
protected String[] parseDnsUrl(final String url) {
|
||||
if (!url.contains("?")) {
|
||||
return new String[]{url, null};
|
||||
}
|
||||
return url.split("\\?");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a list of URLs retrieved from DNS SRV records.
|
||||
*
|
||||
* @return list of URLs to attempt connections to
|
||||
*/
|
||||
@Override
|
||||
public synchronized Iterator<LdapURL> iterator() {
|
||||
if (!isInitialized()) {
|
||||
throw new IllegalStateException("Strategy is not initialized");
|
||||
}
|
||||
if (Instant.now().isAfter(expirationTime)) {
|
||||
populate(ldapUrls, ldapURLSet);
|
||||
}
|
||||
return new DefaultLdapURLIterator(ldapURLSet.getUrls());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invoke {@link org.xbib.net.ldap.dns.DNSResolver#resolve(String)} for each resolver until results are found.
|
||||
*
|
||||
* @return set of LDAP URLs
|
||||
*/
|
||||
protected Set<SRVRecord> retrieveDNSRecords() {
|
||||
for (Map.Entry<SRVDNSResolver, String> entry : dnsResolvers.entrySet()) {
|
||||
try {
|
||||
final Set<SRVRecord> records = entry.getKey().resolve(entry.getValue());
|
||||
if (records != null && !records.isEmpty()) {
|
||||
return records;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DnsSrvConnectionStrategy newInstance() {
|
||||
final DnsSrvConnectionStrategy strategy = new DnsSrvConnectionStrategy(dnsContextFactory, srvTtl, useSSL);
|
||||
strategy.setRetryCondition(getRetryCondition());
|
||||
return strategy;
|
||||
}
|
||||
}
|
277
net-ldap/src/main/java/org/xbib/net/ldap/FilterTemplate.java
Normal file
277
net-ldap/src/main/java/org/xbib/net/ldap/FilterTemplate.java
Normal file
|
@ -0,0 +1,277 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.xbib.net.ldap.filter.FilterUtils;
|
||||
|
||||
/**
|
||||
* Class for producing an LDAP search filter from a filter template. Templates can use either index based parameters or
|
||||
* name based parameters for substitutions. Parameters are encoded according to RFC 4515.
|
||||
*
|
||||
*/
|
||||
public class FilterTemplate {
|
||||
|
||||
/**
|
||||
* hash code seed.
|
||||
*/
|
||||
private static final int HASH_CODE_SEED = 311;
|
||||
/**
|
||||
* filter parameters.
|
||||
*/
|
||||
private final Map<String, Object> parameters = new HashMap<>();
|
||||
/**
|
||||
* filter.
|
||||
*/
|
||||
private String searchFilter;
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public FilterTemplate() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new search filter with the supplied filter.
|
||||
*
|
||||
* @param filter to set
|
||||
*/
|
||||
public FilterTemplate(final String filter) {
|
||||
setFilter(filter);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new search filter with the supplied filter and parameters.
|
||||
*
|
||||
* @param filter to set
|
||||
* @param params to set
|
||||
*/
|
||||
public FilterTemplate(final String filter, final Object[] params) {
|
||||
setFilter(filter);
|
||||
setParameters(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hex encodes the supplied byte array for use in a search filter.
|
||||
*
|
||||
* @param value to encode
|
||||
* @return encoded value or null if supplied value is null
|
||||
*/
|
||||
public static String encodeValue(final byte[] value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final char[] c = LdapUtils.hexEncode(value);
|
||||
final StringBuilder sb = new StringBuilder(c.length + c.length / 2);
|
||||
for (int i = 0; i < c.length; i += 2) {
|
||||
sb.append('\\').append(c[i]).append(c[i + 1]);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the supplied attribute value for use in a search filter. See {@link FilterUtils#escape(String)}.
|
||||
*
|
||||
* @param value to encode
|
||||
* @return encoded value or null if supplied value is null
|
||||
*/
|
||||
public static String encodeValue(final String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return FilterUtils.escape(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hex encodes the supplied object if it is of type byte[], otherwise the string format of the object is escaped. See
|
||||
* {@link FilterUtils#escape(String)}.
|
||||
*
|
||||
* @param obj to encode
|
||||
* @return encoded object
|
||||
*/
|
||||
protected static String encode(final Object obj) {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String str;
|
||||
if (obj instanceof String) {
|
||||
str = encodeValue((String) obj);
|
||||
} else if (obj instanceof byte[]) {
|
||||
str = encodeValue((byte[]) obj);
|
||||
} else {
|
||||
str = encodeValue(obj.toString());
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for this class.
|
||||
*
|
||||
* @return new builder
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the filter.
|
||||
*
|
||||
* @return filter
|
||||
*/
|
||||
public String getFilter() {
|
||||
return searchFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the filter.
|
||||
*
|
||||
* @param filter to set
|
||||
*/
|
||||
public void setFilter(final String filter) {
|
||||
searchFilter = filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the filter parameters.
|
||||
*
|
||||
* @return unmodifiable map of filter parameters
|
||||
*/
|
||||
public Map<String, Object> getParameters() {
|
||||
return Collections.unmodifiableMap(parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets positional filter parameters.
|
||||
*
|
||||
* @param values to set
|
||||
*/
|
||||
public void setParameters(final Object[] values) {
|
||||
int i = 0;
|
||||
for (Object o : values) {
|
||||
parameters.put(Integer.toString(i++), o);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a positional filter parameter.
|
||||
*
|
||||
* @param position of the parameter in the filter
|
||||
* @param value to set
|
||||
*/
|
||||
public void setParameter(final int position, final Object value) {
|
||||
parameters.put(Integer.toString(position), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a named filter parameter.
|
||||
*
|
||||
* @param name of the parameter in the filter
|
||||
* @param value to set
|
||||
*/
|
||||
public void setParameter(final String name, final Object value) {
|
||||
parameters.put(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this filter with its parameters encoded and replaced. See {@link #encode(Object)}.
|
||||
*
|
||||
* @return formatted and encoded filter
|
||||
*/
|
||||
public String format() {
|
||||
String s = searchFilter;
|
||||
if (!parameters.isEmpty()) {
|
||||
for (Map.Entry<String, Object> e : parameters.entrySet()) {
|
||||
final String encoded = encode(e.getValue());
|
||||
if (encoded != null) {
|
||||
s = s.replace("{" + e.getKey() + "}", encoded);
|
||||
}
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
if (o instanceof FilterTemplate v) {
|
||||
return LdapUtils.areEqual(searchFilter, v.searchFilter) &&
|
||||
LdapUtils.areEqual(parameters, v.parameters);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return LdapUtils.computeHashCode(HASH_CODE_SEED, searchFilter, parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" +
|
||||
getClass().getName() + "@" + hashCode() + "::" +
|
||||
"filter=" + searchFilter + ", " +
|
||||
"parameters=" + parameters + "]";
|
||||
}
|
||||
|
||||
// CheckStyle:OFF
|
||||
public static class Builder {
|
||||
|
||||
|
||||
private final FilterTemplate object = new FilterTemplate();
|
||||
|
||||
|
||||
protected Builder() {
|
||||
}
|
||||
|
||||
|
||||
public Builder filter(final String filter) {
|
||||
object.setFilter(filter);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder parameter(final String name, final String value) {
|
||||
object.setParameter(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder parameter(final String name, final Object value) {
|
||||
object.setParameter(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder parameter(final int pos, final String value) {
|
||||
object.setParameter(pos, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder parameter(final int pos, final Object value) {
|
||||
object.setParameter(pos, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder parameters(final Object... values) {
|
||||
object.setParameters(values);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public FilterTemplate build() {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
// CheckStyle:ON
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* Retry metadata used when a connection is opened.
|
||||
*
|
||||
*/
|
||||
public class InitialRetryMetadata extends AbstractRetryMetadata {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new initial retry metadata.
|
||||
*
|
||||
* @param time of last successful connection
|
||||
*/
|
||||
public InitialRetryMetadata(final Instant time) {
|
||||
successTime = time;
|
||||
}
|
||||
}
|
654
net-ldap/src/main/java/org/xbib/net/ldap/LdapAttribute.java
Normal file
654
net-ldap/src/main/java/org/xbib/net/ldap/LdapAttribute.java
Normal file
|
@ -0,0 +1,654 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* LDAP attribute defined as:
|
||||
*
|
||||
* <pre>
|
||||
* Attribute ::= PartialAttribute(WITH COMPONENTS {
|
||||
* ...,
|
||||
* vals (SIZE(1..MAX))})
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public class LdapAttribute {
|
||||
|
||||
/**
|
||||
* hash code seed.
|
||||
*/
|
||||
private static final int HASH_CODE_SEED = 10223;
|
||||
|
||||
/**
|
||||
* List of attribute names known to use binary syntax.
|
||||
*/
|
||||
private static final String[] DEFAULT_BINARY_ATTRIBUTES = new String[]{
|
||||
"photo",
|
||||
"personalSignature",
|
||||
"audio",
|
||||
"jpegPhoto",
|
||||
"javaSerializedData",
|
||||
"thumbnailPhoto",
|
||||
"thumbnailLogo",
|
||||
"userCertificate",
|
||||
"cACertificate",
|
||||
"authorityRevocationList",
|
||||
"certificateRevocationList",
|
||||
"crossCertificatePair",
|
||||
"x500UniqueIdentifier",
|
||||
};
|
||||
|
||||
/**
|
||||
* List of custom binary attribute names.
|
||||
*/
|
||||
private static final String[] BINARY_ATTRIBUTES;
|
||||
|
||||
static {
|
||||
// Configure custom binary attribute names
|
||||
final String[] split = System.getProperty("org.xbib.net.ldap.attribute.binary", "").split(",");
|
||||
BINARY_ATTRIBUTES = LdapUtils.concatArrays(DEFAULT_BINARY_ATTRIBUTES, split);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute name.
|
||||
*/
|
||||
private String attributeName;
|
||||
/**
|
||||
* Attribute values.
|
||||
*/
|
||||
private Set<ByteBuffer> attributeValues = new LinkedHashSet<>();
|
||||
/**
|
||||
* Whether this attribute is binary and string representations should be base64 encoded.
|
||||
*/
|
||||
private boolean binary;
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public LdapAttribute() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new attribute.
|
||||
*
|
||||
* @param type attribute description
|
||||
*/
|
||||
public LdapAttribute(final String type) {
|
||||
setName(type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new attribute.
|
||||
*
|
||||
* @param type attribute description
|
||||
* @param value attribute values
|
||||
*/
|
||||
public LdapAttribute(final String type, final byte[]... value) {
|
||||
setName(type);
|
||||
addBinaryValues(value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new attribute.
|
||||
*
|
||||
* @param type attribute description
|
||||
* @param value attribute values
|
||||
*/
|
||||
public LdapAttribute(final String type, final String... value) {
|
||||
setName(type);
|
||||
addStringValues(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new attribute whose values are sorted. String values are sorted naturally. Binary values are sorted using
|
||||
* {@link ByteBuffer#compareTo(ByteBuffer)}.
|
||||
*
|
||||
* @param la attribute to sort
|
||||
* @return sorted attribute
|
||||
*/
|
||||
public static LdapAttribute sort(final LdapAttribute la) {
|
||||
final LdapAttribute sorted = new LdapAttribute(la.getName());
|
||||
if (la.isBinary()) {
|
||||
sorted.setBinary(true);
|
||||
final Set<byte[]> newValues = la.getBinaryValues().stream().sorted(
|
||||
(o1, o2) -> {
|
||||
final ByteBuffer bb1 = ByteBuffer.wrap(o1);
|
||||
final ByteBuffer bb2 = ByteBuffer.wrap(o2);
|
||||
return bb1.compareTo(bb2);
|
||||
}).collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
sorted.addBinaryValues(newValues);
|
||||
} else {
|
||||
final Set<String> newValues = la.getStringValues().stream()
|
||||
.sorted(Comparator.comparing(String::toString)).collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
sorted.addStringValues(newValues);
|
||||
}
|
||||
return sorted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for this class.
|
||||
*
|
||||
* @return new builder
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this ldap attribute is binary.
|
||||
*
|
||||
* @return whether this ldap attribute is binary
|
||||
*/
|
||||
public boolean isBinary() {
|
||||
return binary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this ldap attribute is binary.
|
||||
*
|
||||
* @param b whether this ldap attribute is binary
|
||||
*/
|
||||
public void setBinary(final boolean b) {
|
||||
binary = b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether attrNames matches the name of this attribute. If a match is found this attribute is set as binary.
|
||||
*
|
||||
* @param attrNames custom binary attribute names
|
||||
*/
|
||||
public void configureBinary(final String... attrNames) {
|
||||
if (binary) {
|
||||
return;
|
||||
}
|
||||
if (attrNames != null && attrNames.length > 0) {
|
||||
for (String s : attrNames) {
|
||||
if (attributeName.equals(s)) {
|
||||
binary = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attribute description with options.
|
||||
*
|
||||
* @return attribute description
|
||||
*/
|
||||
public String getName() {
|
||||
return attributeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name. This method has the side effect of setting this attribute as binary if the name has an option of
|
||||
* 'binary' or the name matches one of {@link #BINARY_ATTRIBUTES}.
|
||||
*
|
||||
* @param type attribute name
|
||||
*/
|
||||
public void setName(final String type) {
|
||||
attributeName = type;
|
||||
if (getOptions().contains("binary") || Stream.of(BINARY_ATTRIBUTES).anyMatch(attributeName::equals)) {
|
||||
setBinary(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attribute description with or without options.
|
||||
*
|
||||
* @param withOptions whether the attribute description should include options
|
||||
* @return attribute description
|
||||
*/
|
||||
public String getName(final boolean withOptions) {
|
||||
if (withOptions) {
|
||||
return attributeName;
|
||||
} else {
|
||||
final int optionIndex = attributeName.indexOf(";");
|
||||
return optionIndex > 0 ? attributeName.substring(0, optionIndex) : attributeName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns any options that may exist on the attribute description.
|
||||
*
|
||||
* @return attribute description options
|
||||
*/
|
||||
public List<String> getOptions() {
|
||||
if (attributeName.indexOf(";") > 0) {
|
||||
final String[] split = attributeName.split(";");
|
||||
if (split.length > 1) {
|
||||
return IntStream.range(1, split.length).mapToObj(i -> split[i]).collect(Collectors.toUnmodifiableList());
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single byte array value of this attribute.
|
||||
*
|
||||
* @return single byte array attribute value or null if this attribute is empty
|
||||
*/
|
||||
public byte[] getBinaryValue() {
|
||||
return attributeValues.isEmpty() ? null : attributeValues.iterator().next().array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the values of this attribute as byte arrays. The return collection cannot be modified.
|
||||
*
|
||||
* @return collection of string attribute values
|
||||
*/
|
||||
public Collection<byte[]> getBinaryValues() {
|
||||
if (attributeValues.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
return attributeValues.stream().map(ByteBuffer::array).collect(Collectors.toUnmodifiableList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single string value of this attribute.
|
||||
*
|
||||
* @return single string attribute value or null if this attribute is empty
|
||||
*/
|
||||
public String getStringValue() {
|
||||
if (attributeValues.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
final ByteBuffer val = attributeValues.iterator().next();
|
||||
return binary ? LdapUtils.base64Encode(val.array()) : LdapUtils.utf8Encode(val.array());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the values of this attribute as strings. Binary data is base64 encoded. The return collection cannot be
|
||||
* modified.
|
||||
*
|
||||
* @return collection of string attribute values
|
||||
*/
|
||||
public Collection<String> getStringValues() {
|
||||
if (attributeValues.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
return attributeValues.stream().map(v -> {
|
||||
if (binary) {
|
||||
return LdapUtils.base64Encode(v.array());
|
||||
}
|
||||
return LdapUtils.utf8Encode(v.array(), false);
|
||||
}).collect(Collectors.toUnmodifiableList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single decoded value of this attribute.
|
||||
*
|
||||
* @param <T> type of decoded attribute
|
||||
* @param func to decode attribute value with
|
||||
* @return single decoded attribute value or null if this attribute is empty
|
||||
*/
|
||||
public <T> T getValue(final Function<byte[], T> func) {
|
||||
return attributeValues.isEmpty() ? null : func.apply(attributeValues.iterator().next().array());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the values of this attribute decoded by the supplied function.
|
||||
*
|
||||
* @param <T> type of decoded attributes
|
||||
* @param func to decode attribute values with
|
||||
* @return collection of decoded attribute values, null values are discarded
|
||||
*/
|
||||
public <T> Collection<T> getValues(final Function<byte[], T> func) {
|
||||
return attributeValues.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(ByteBuffer::array)
|
||||
.map(func).collect(Collectors.toUnmodifiableList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the supplied byte array as a value for this attribute.
|
||||
*
|
||||
* @param value to add, null values are discarded
|
||||
*/
|
||||
public void addBinaryValues(final byte[]... value) {
|
||||
Stream.of(value).filter(Objects::nonNull).map(ByteBuffer::wrap).forEach(attributeValues::add);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all the byte arrays in the supplied collection as values for this attribute.
|
||||
*
|
||||
* @param values to add, null values are discarded
|
||||
*/
|
||||
public void addBinaryValues(final Collection<byte[]> values) {
|
||||
values.stream().filter(Objects::nonNull).map(ByteBuffer::wrap).forEach(attributeValues::add);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the supplied string as a value for this attribute.
|
||||
*
|
||||
* @param value to add, null values are discarded
|
||||
*/
|
||||
public void addStringValues(final String... value) {
|
||||
Stream.of(value)
|
||||
.filter(Objects::nonNull)
|
||||
.map(s -> toByteArray(s, true))
|
||||
.filter(Objects::nonNull)
|
||||
.map(ByteBuffer::wrap)
|
||||
.forEach(attributeValues::add);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all the strings in the supplied collection as values for this attribute.
|
||||
*
|
||||
* @param values to add, null values are discarded
|
||||
*/
|
||||
public void addStringValues(final Collection<String> values) {
|
||||
values.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(s -> toByteArray(s, true))
|
||||
.filter(Objects::nonNull)
|
||||
.map(ByteBuffer::wrap)
|
||||
.forEach(attributeValues::add);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all the buffers in the supplied collection as values for this attribute.
|
||||
*
|
||||
* @param values to add, null values are discarded
|
||||
*/
|
||||
public void addBufferValues(final ByteBuffer... values) {
|
||||
Stream.of(values).filter(Objects::nonNull).forEach(attributeValues::add);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all the buffers in the supplied collection as values for this attribute.
|
||||
*
|
||||
* @param values to add, null values are discarded
|
||||
*/
|
||||
public void addBufferValues(final Collection<ByteBuffer> values) {
|
||||
values.stream().filter(Objects::nonNull).forEach(attributeValues::add);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the supplied values for this attribute by encoding them with the supplied function.
|
||||
*
|
||||
* @param <T> type attribute to encode
|
||||
* @param func to encode value with
|
||||
* @param value to encode and add, null values are discarded
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> void addValues(final Function<T, byte[]> func, final T... value) {
|
||||
Stream.of(value)
|
||||
.filter(Objects::nonNull)
|
||||
.map(func)
|
||||
.filter(Objects::nonNull)
|
||||
.map(ByteBuffer::wrap)
|
||||
.forEach(attributeValues::add);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all the values in the supplied collection for this attribute by encoding them with the supplied function.
|
||||
* See {@link #addValues(Function, Object...)}.
|
||||
*
|
||||
* @param <T> type attribute to encode
|
||||
* @param func to encode value with
|
||||
* @param values to encode and add, null values are discarded
|
||||
*/
|
||||
public <T> void addValues(final Function<T, byte[]> func, final Collection<T> values) {
|
||||
values.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(func)
|
||||
.filter(Objects::nonNull)
|
||||
.map(ByteBuffer::wrap)
|
||||
.forEach(attributeValues::add);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the supplied byte array as a value from this attribute.
|
||||
*
|
||||
* @param value to remove, null values are discarded
|
||||
*/
|
||||
public void removeBinaryValues(final byte[]... value) {
|
||||
Stream.of(value).filter(Objects::nonNull).map(ByteBuffer::wrap).forEach(attributeValues::remove);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all the byte arrays in the supplied collection as values from this attribute.
|
||||
*
|
||||
* @param values to remove, null values are discarded
|
||||
*/
|
||||
public void removeBinaryValues(final Collection<byte[]> values) {
|
||||
values.stream().filter(Objects::nonNull).map(ByteBuffer::wrap).forEach(attributeValues::remove);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the supplied string as a value from this attribute.
|
||||
*
|
||||
* @param value to remove, null values are discarded
|
||||
*/
|
||||
public void removeStringValues(final String... value) {
|
||||
Stream.of(value)
|
||||
.filter(Objects::nonNull)
|
||||
.map(s -> toByteArray(s, true))
|
||||
.filter(Objects::nonNull)
|
||||
.map(ByteBuffer::wrap)
|
||||
.forEach(attributeValues::remove);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all the strings in the supplied collection as values from this attribute.
|
||||
*
|
||||
* @param values to remove, null values are discarded
|
||||
*/
|
||||
public void removeStringValues(final Collection<String> values) {
|
||||
values.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(s -> toByteArray(s, true))
|
||||
.filter(Objects::nonNull)
|
||||
.map(ByteBuffer::wrap)
|
||||
.forEach(attributeValues::remove);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all the buffers in the supplied collection as values from this attribute.
|
||||
*
|
||||
* @param values to remove, null values are discarded
|
||||
*/
|
||||
public void removeBufferValues(final ByteBuffer... values) {
|
||||
Stream.of(values).filter(Objects::nonNull).forEach(attributeValues::remove);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all the buffers in the supplied collection as values from this attribute.
|
||||
*
|
||||
* @param values to remove, null values are discarded
|
||||
*/
|
||||
public void removeBufferValues(final Collection<ByteBuffer> values) {
|
||||
values.stream().filter(Objects::nonNull).forEach(attributeValues::remove);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the supplied value exists in this attribute.
|
||||
*
|
||||
* @param value to find
|
||||
* @return whether value exists
|
||||
*/
|
||||
public boolean hasValue(final byte[] value) {
|
||||
return attributeValues.stream().anyMatch(bb -> Arrays.equals(bb.array(), value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the supplied value exists in this attribute.
|
||||
*
|
||||
* @param value to find
|
||||
* @return whether value exists
|
||||
*/
|
||||
public boolean hasValue(final String value) {
|
||||
return attributeValues.stream().anyMatch(bb -> Arrays.equals(bb.array(), toByteArray(value, false)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the supplied value exists in this attribute.
|
||||
*
|
||||
* @param <T> type attribute to encode
|
||||
* @param func to encode value with
|
||||
* @param value to find
|
||||
* @return whether value exists
|
||||
*/
|
||||
public <T> boolean hasValue(final Function<T, byte[]> func, final T value) {
|
||||
return attributeValues.stream().anyMatch(bb -> Arrays.equals(bb.array(), func.apply(value)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of values in this ldap attribute.
|
||||
*
|
||||
* @return number of values in this ldap attribute
|
||||
*/
|
||||
public int size() {
|
||||
return attributeValues.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all the values in this ldap attribute.
|
||||
*/
|
||||
public void clear() {
|
||||
attributeValues.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
if (o instanceof LdapAttribute) {
|
||||
final LdapAttribute v = (LdapAttribute) o;
|
||||
return LdapUtils.areEqual(LdapUtils.toLowerCase(attributeName), LdapUtils.toLowerCase(v.attributeName)) &&
|
||||
LdapUtils.areEqual(attributeValues, v.attributeValues);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return
|
||||
LdapUtils.computeHashCode(
|
||||
HASH_CODE_SEED,
|
||||
LdapUtils.toLowerCase(attributeName),
|
||||
attributeValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getName() + "@" + hashCode() + "::" +
|
||||
"name=" + attributeName + ", " +
|
||||
"values=" + getStringValues() + ", " +
|
||||
"binary=" + binary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the supplied string value to a byte array respecting the {@link #binary} flag. If this attribute is
|
||||
* binary, value is expected to be in base64 format. Otherwise, value is UTF-8 encoded.
|
||||
*
|
||||
* @param value to convert
|
||||
* @param throwOnError whether to throw if a base64 decode error occurs
|
||||
* @return binary value
|
||||
* @throws IllegalArgumentException if attribute is binary, value cannot be base64 decoded and throwOnError is true
|
||||
*/
|
||||
private byte[] toByteArray(final String value, final boolean throwOnError) {
|
||||
if (binary) {
|
||||
try {
|
||||
return LdapUtils.base64Decode(value);
|
||||
} catch (IllegalArgumentException e) {
|
||||
if (throwOnError) {
|
||||
throw new IllegalArgumentException("Error decoding " + value + " for " + attributeName, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return LdapUtils.utf8Encode(value, false);
|
||||
}
|
||||
|
||||
// CheckStyle:OFF
|
||||
public static class Builder {
|
||||
|
||||
|
||||
private final LdapAttribute object = new LdapAttribute();
|
||||
|
||||
|
||||
protected Builder() {
|
||||
}
|
||||
|
||||
|
||||
public Builder name(final String name) {
|
||||
object.setName(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Builder values(final Function<T, byte[]> func, final T... value) {
|
||||
object.addValues(func, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder values(final byte[]... values) {
|
||||
object.addBinaryValues(values);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder binaryValues(final Collection<byte[]> values) {
|
||||
object.addBinaryValues(values);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder values(final String... values) {
|
||||
object.addStringValues(values);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder stringValues(final Collection<String> values) {
|
||||
object.addStringValues(values);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder values(final ByteBuffer... values) {
|
||||
object.addBufferValues(values);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder bufferValues(final Collection<ByteBuffer> values) {
|
||||
object.addBufferValues(values);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder binary(final boolean b) {
|
||||
object.setBinary(b);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public LdapAttribute build() {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
// CheckStyle:ON
|
||||
}
|
542
net-ldap/src/main/java/org/xbib/net/ldap/LdapEntry.java
Normal file
542
net-ldap/src/main/java/org/xbib/net/ldap/LdapEntry.java
Normal file
|
@ -0,0 +1,542 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import org.xbib.asn1.AbstractParseHandler;
|
||||
import org.xbib.asn1.DERBuffer;
|
||||
import org.xbib.asn1.DERParser;
|
||||
import org.xbib.asn1.DERPath;
|
||||
import org.xbib.asn1.OctetStringType;
|
||||
import org.xbib.net.ldap.dn.Dn;
|
||||
|
||||
/**
|
||||
* LDAP search result entry defined as:
|
||||
*
|
||||
* <pre>
|
||||
* SearchResultEntry ::= [APPLICATION 4] SEQUENCE {
|
||||
* objectName LDAPDN,
|
||||
* attributes PartialAttributeList }
|
||||
*
|
||||
* PartialAttributeList ::= SEQUENCE OF
|
||||
* partialAttribute PartialAttribute
|
||||
*
|
||||
* PartialAttribute ::= SEQUENCE {
|
||||
* type AttributeDescription,
|
||||
* vals SET OF value AttributeValue }
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public class LdapEntry extends AbstractMessage {
|
||||
|
||||
/**
|
||||
* BER protocol number.
|
||||
*/
|
||||
public static final int PROTOCOL_OP = 4;
|
||||
|
||||
/**
|
||||
* hash code seed.
|
||||
*/
|
||||
private static final int HASH_CODE_SEED = 10303;
|
||||
|
||||
/**
|
||||
* DER path to LDAP DN.
|
||||
*/
|
||||
private static final DERPath LDAP_DN_PATH = new DERPath("/SEQ/APP(4)/OCTSTR[0]");
|
||||
|
||||
/**
|
||||
* DER path to attributes.
|
||||
*/
|
||||
private static final DERPath ATTRIBUTES_PATH = new DERPath("/SEQ/APP(4)/SEQ/SEQ");
|
||||
|
||||
/**
|
||||
* LDAP DN of the entry.
|
||||
*/
|
||||
private String ldapDn;
|
||||
|
||||
/**
|
||||
* Parsed LDAP DN.
|
||||
*/
|
||||
private Dn parsedDn;
|
||||
|
||||
/**
|
||||
* Normalized LDAP DN.
|
||||
*/
|
||||
private String normalizedDn;
|
||||
|
||||
/**
|
||||
* LDAP attributes on the entry.
|
||||
*/
|
||||
private Map<String, LdapAttribute> attributes = new LinkedHashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public LdapEntry() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new search result entry.
|
||||
*
|
||||
* @param buffer to decode
|
||||
*/
|
||||
public LdapEntry(final DERBuffer buffer) {
|
||||
final DERParser parser = new DERParser();
|
||||
parser.registerHandler(MessageIDHandler.PATH, new MessageIDHandler(this));
|
||||
parser.registerHandler(LDAP_DN_PATH, new LdapDnHandler(this));
|
||||
parser.registerHandler(ATTRIBUTES_PATH, new AttributesHandler(this));
|
||||
parser.registerHandler(ControlsHandler.PATH, new ControlsHandler(this));
|
||||
parser.parse(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new entry whose attributes are sorted naturally by name without options.
|
||||
*
|
||||
* @param le entry to sort
|
||||
* @return sorted entry
|
||||
*/
|
||||
public static LdapEntry sort(final LdapEntry le) {
|
||||
final LdapEntry sorted = new LdapEntry();
|
||||
sorted.copyValues(le);
|
||||
sorted.setDn(le.getDn());
|
||||
sorted.addAttributes(
|
||||
le.getAttributes().stream()
|
||||
.map(LdapAttribute::sort)
|
||||
.sorted(Comparator.comparing(o -> o.getName(false))).collect(Collectors.toCollection(LinkedHashSet::new)));
|
||||
return sorted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of attribute modifications needed to change the supplied target entry into the supplied source
|
||||
* entry. See {@link #computeModifications(LdapEntry, LdapEntry, boolean)}.
|
||||
*
|
||||
* @param source ldap entry containing new data
|
||||
* @param target ldap entry containing existing data
|
||||
* @return attribute modifications needed to change target into source or an empty array
|
||||
*/
|
||||
public static AttributeModification[] computeModifications(final LdapEntry source, final LdapEntry target) {
|
||||
return computeModifications(source, target, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of attribute modifications needed to change the supplied target entry into the supplied source
|
||||
* entry. This implementation performs a byte comparison on the attribute values to determine changes.
|
||||
*
|
||||
* @param source ldap entry containing new data
|
||||
* @param target ldap entry containing existing data
|
||||
* @param useReplace whether to use a single REPLACE modification or individual ADD/DELETE for attribute values
|
||||
* @return attribute modifications needed to change target into source or an empty array
|
||||
*/
|
||||
public static AttributeModification[] computeModifications(
|
||||
final LdapEntry source, final LdapEntry target, final boolean useReplace) {
|
||||
final List<AttributeModification> mods = new ArrayList<>();
|
||||
for (LdapAttribute sourceAttr : source.getAttributes()) {
|
||||
final LdapAttribute targetAttr = target.getAttribute(sourceAttr.getName());
|
||||
if (targetAttr == null) {
|
||||
if (sourceAttr.size() > 0) {
|
||||
mods.add(new AttributeModification(AttributeModification.Type.ADD, sourceAttr));
|
||||
} else {
|
||||
// perform a replace if attribute has no values to avoid potential schema issues
|
||||
mods.add(new AttributeModification(AttributeModification.Type.REPLACE, sourceAttr));
|
||||
}
|
||||
} else if (!targetAttr.equals(sourceAttr)) {
|
||||
if (useReplace) {
|
||||
mods.add(new AttributeModification(AttributeModification.Type.REPLACE, sourceAttr));
|
||||
} else {
|
||||
final LdapAttribute toAdd = new LdapAttribute(sourceAttr.getName());
|
||||
sourceAttr.getBinaryValues().stream()
|
||||
.filter(sv -> !targetAttr.hasValue(sv))
|
||||
.forEach(toAdd::addBinaryValues);
|
||||
if (toAdd.size() > 0) {
|
||||
mods.add(new AttributeModification(AttributeModification.Type.ADD, toAdd));
|
||||
}
|
||||
|
||||
final LdapAttribute toDelete = new LdapAttribute(sourceAttr.getName());
|
||||
targetAttr.getBinaryValues().stream()
|
||||
.filter(tv -> !sourceAttr.hasValue(tv))
|
||||
.forEach(toDelete::addBinaryValues);
|
||||
if (toDelete.size() > 0) {
|
||||
mods.add(new AttributeModification(AttributeModification.Type.DELETE, toDelete));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (LdapAttribute targetAttr : target.getAttributes()) {
|
||||
final LdapAttribute sourceAttr = source.getAttribute(targetAttr.getName());
|
||||
if (sourceAttr == null) {
|
||||
mods.add(
|
||||
new AttributeModification(
|
||||
AttributeModification.Type.DELETE,
|
||||
LdapAttribute.builder().name(targetAttr.getName()).build()));
|
||||
}
|
||||
}
|
||||
return mods.toArray(AttributeModification[]::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for this class.
|
||||
*
|
||||
* @return new builder
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ldap DN.
|
||||
*
|
||||
* @return ldap DN
|
||||
*/
|
||||
public String getDn() {
|
||||
return ldapDn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ldap DN.
|
||||
*
|
||||
* @param dn ldap DN
|
||||
*/
|
||||
public void setDn(final String dn) {
|
||||
ldapDn = dn;
|
||||
if (ldapDn != null) {
|
||||
try {
|
||||
parsedDn = new Dn(ldapDn);
|
||||
} catch (Exception e) {
|
||||
parsedDn = null;
|
||||
}
|
||||
if (parsedDn != null) {
|
||||
normalizedDn = parsedDn.format();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parsed ldap DN. Parsing is performed using {@link org.xbib.net.ldap.dn.DefaultDnParser}.
|
||||
*
|
||||
* @return parsed ldap DN or null if {@link #ldapDn} is null or could not be parsed
|
||||
*/
|
||||
public Dn getParsedDn() {
|
||||
return parsedDn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the normalized ldap DN. Normalization is performed using {@link org.xbib.net.ldap.dn.DefaultRDnNormalizer}.
|
||||
*
|
||||
* @return normalized ldap DN or null if {@link #ldapDn} is null or could not be parsed
|
||||
*/
|
||||
public String getNormalizedDn() {
|
||||
return normalizedDn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ldap attributes.
|
||||
*
|
||||
* @return ldap attributes
|
||||
*/
|
||||
public Collection<LdapAttribute> getAttributes() {
|
||||
return attributes.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single attribute of this entry. If multiple attributes exist the first attribute returned by the
|
||||
* underlying iterator is used. If no attributes exist null is returned.
|
||||
*
|
||||
* @return single attribute
|
||||
*/
|
||||
public LdapAttribute getAttribute() {
|
||||
if (attributes.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return attributes.values().iterator().next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attribute with the supplied name.
|
||||
*
|
||||
* @param name of the attribute to return
|
||||
* @return ldap attribute
|
||||
*/
|
||||
public LdapAttribute getAttribute(final String name) {
|
||||
if (name != null) {
|
||||
return attributes.get(LdapUtils.toLowerCase(name));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attribute names in this entry.
|
||||
*
|
||||
* @return string array of attribute names
|
||||
*/
|
||||
public String[] getAttributeNames() {
|
||||
return attributes.values().stream().map(LdapAttribute::getName).toArray(String[]::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds attributes to the entry.
|
||||
*
|
||||
* @param attrs attributes to add
|
||||
*/
|
||||
public void addAttributes(final LdapAttribute... attrs) {
|
||||
for (LdapAttribute a : attrs) {
|
||||
attributes.put(LdapUtils.toLowerCase(a.getName()), a);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds attributes to the entry.
|
||||
*
|
||||
* @param attrs attributes to add
|
||||
*/
|
||||
public void addAttributes(final Collection<LdapAttribute> attrs) {
|
||||
attrs.forEach(a -> attributes.put(LdapUtils.toLowerCase(a.getName()), a));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the attribute with the supplied name.
|
||||
*
|
||||
* @param name of attribute to remove
|
||||
*/
|
||||
public void removeAttribute(final String name) {
|
||||
attributes.remove(LdapUtils.toLowerCase(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an attribute from this ldap attributes.
|
||||
*
|
||||
* @param attrs attribute to remove
|
||||
*/
|
||||
public void removeAttributes(final LdapAttribute... attrs) {
|
||||
for (LdapAttribute a : attrs) {
|
||||
attributes.remove(LdapUtils.toLowerCase(a.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the attribute(s) from this ldap attributes.
|
||||
*
|
||||
* @param attrs collection of ldap attributes to remove
|
||||
*/
|
||||
public void removeAttributes(final Collection<LdapAttribute> attrs) {
|
||||
attrs.forEach(a -> attributes.remove(LdapUtils.toLowerCase(a.getName())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of attributes.
|
||||
*
|
||||
* @return number of attributes
|
||||
*/
|
||||
public int size() {
|
||||
return attributes.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all the attributes.
|
||||
*/
|
||||
public void clear() {
|
||||
attributes.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
if (o instanceof LdapEntry && super.equals(o)) {
|
||||
final LdapEntry v = (LdapEntry) o;
|
||||
// compare normalizedDn if not null, else compare Dn
|
||||
return LdapUtils.areEqual(
|
||||
normalizedDn != null ? normalizedDn : ldapDn,
|
||||
normalizedDn != null ? v.normalizedDn : v.normalizedDn != null ? v.normalizedDn : v.ldapDn) &&
|
||||
LdapUtils.areEqual(attributes, v.attributes);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return
|
||||
LdapUtils.computeHashCode(
|
||||
HASH_CODE_SEED,
|
||||
getMessageID(),
|
||||
getControls(),
|
||||
normalizedDn != null ? normalizedDn : ldapDn,
|
||||
attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ", " +
|
||||
"dn=" + ldapDn + ", " +
|
||||
"attributes=" + (attributes != null ? attributes.values() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse handler implementation for the LDAP DN.
|
||||
*/
|
||||
protected static class LdapDnHandler extends AbstractParseHandler<LdapEntry> {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new ldap dn handler.
|
||||
*
|
||||
* @param response to configure
|
||||
*/
|
||||
LdapDnHandler(final LdapEntry response) {
|
||||
super(response);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handle(final DERParser parser, final DERBuffer encoded) {
|
||||
getObject().setDn(OctetStringType.decode(encoded));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse handler implementation for the attributes.
|
||||
*/
|
||||
protected static class AttributesHandler extends AbstractParseHandler<LdapEntry> {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new attributes handler.
|
||||
*
|
||||
* @param response to configure
|
||||
*/
|
||||
AttributesHandler(final LdapEntry response) {
|
||||
super(response);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handle(final DERParser parser, final DERBuffer encoded) {
|
||||
final AttributeParser p = new AttributeParser();
|
||||
p.parse(encoded);
|
||||
|
||||
if (p.getName().isEmpty()) {
|
||||
throw new IllegalArgumentException("Could not parse attribute");
|
||||
}
|
||||
if (p.getValues().isEmpty()) {
|
||||
getObject().addAttributes(LdapAttribute.builder().name(p.getName().get()).build());
|
||||
} else {
|
||||
getObject().addAttributes(
|
||||
LdapAttribute.builder().name(p.getName().get()).bufferValues(p.getValues().get()).build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a buffer containing an attribute name and its values.
|
||||
*/
|
||||
protected static class AttributeParser {
|
||||
|
||||
/**
|
||||
* DER path to name.
|
||||
*/
|
||||
private static final DERPath NAME_PATH = new DERPath("/OCTSTR");
|
||||
|
||||
/**
|
||||
* DER path to values.
|
||||
*/
|
||||
private static final DERPath VALUES_PATH = new DERPath("/SET/OCTSTR");
|
||||
|
||||
/**
|
||||
* Parser for decoding LDAP attributes.
|
||||
*/
|
||||
private final DERParser parser = new DERParser();
|
||||
|
||||
/**
|
||||
* Attribute name.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* Attribute values.
|
||||
*/
|
||||
private final List<ByteBuffer> values = new ArrayList<>();
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new attribute parser.
|
||||
*/
|
||||
public AttributeParser() {
|
||||
parser.registerHandler(NAME_PATH, (p, e) -> name = OctetStringType.decode(e));
|
||||
parser.registerHandler(VALUES_PATH, (p, e) -> values.add(ByteBuffer.wrap(e.getRemainingBytes())));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Examines the supplied buffer and parses an LDAP attribute if one is found.
|
||||
*
|
||||
* @param buffer to parse
|
||||
*/
|
||||
public void parse(final DERBuffer buffer) {
|
||||
parser.parse(buffer);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the attribute name.
|
||||
*
|
||||
* @return attribute name or empty
|
||||
*/
|
||||
public Optional<String> getName() {
|
||||
return Optional.ofNullable(name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the attribute values.
|
||||
*
|
||||
* @return attribute values or empty
|
||||
*/
|
||||
public Optional<List<ByteBuffer>> getValues() {
|
||||
return values.isEmpty() ? Optional.empty() : Optional.of(values);
|
||||
}
|
||||
}
|
||||
|
||||
// CheckStyle:OFF
|
||||
public static class Builder extends AbstractMessage.AbstractBuilder<Builder, LdapEntry> {
|
||||
|
||||
|
||||
protected Builder() {
|
||||
super(new LdapEntry());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Builder self() {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder dn(final String dn) {
|
||||
object.setDn(dn);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder attributes(final LdapAttribute... attrs) {
|
||||
object.addAttributes(attrs);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder attributes(final Collection<LdapAttribute> attrs) {
|
||||
object.addAttributes(attrs);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
// CheckStyle:ON
|
||||
}
|
116
net-ldap/src/main/java/org/xbib/net/ldap/LdapException.java
Normal file
116
net-ldap/src/main/java/org/xbib/net/ldap/LdapException.java
Normal file
|
@ -0,0 +1,116 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
/**
|
||||
* Base exception for all ldap related exceptions.
|
||||
*
|
||||
*/
|
||||
public class LdapException extends Exception {
|
||||
|
||||
/**
|
||||
* serialVersionUID.
|
||||
*/
|
||||
private static final long serialVersionUID = 6812614366508784841L;
|
||||
|
||||
/**
|
||||
* Optional result code associated with this exception.
|
||||
*/
|
||||
private final ResultCode resultCode;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new ldap exception based on the supplied result.
|
||||
*
|
||||
* @param result that produced this exception
|
||||
*/
|
||||
public LdapException(final Result result) {
|
||||
this(result.getResultCode(), formatResult(result));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new ldap exception.
|
||||
*
|
||||
* @param msg describing this exception
|
||||
*/
|
||||
public LdapException(final String msg) {
|
||||
this(null, msg);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new ldap exception.
|
||||
*
|
||||
* @param code result code describing this exception
|
||||
* @param msg describing this exception
|
||||
*/
|
||||
public LdapException(final ResultCode code, final String msg) {
|
||||
super(msg);
|
||||
resultCode = code;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new ldap exception.
|
||||
*
|
||||
* @param e underlying exception
|
||||
*/
|
||||
public LdapException(final Throwable e) {
|
||||
this((ResultCode) null, e);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new ldap exception.
|
||||
*
|
||||
* @param code result code describing this exception
|
||||
* @param e underlying exception
|
||||
*/
|
||||
public LdapException(final ResultCode code, final Throwable e) {
|
||||
super(e);
|
||||
resultCode = code;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new ldap exception.
|
||||
*
|
||||
* @param msg describing this exception
|
||||
* @param e underlying exception
|
||||
*/
|
||||
public LdapException(final String msg, final Throwable e) {
|
||||
this(null, msg, e);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new ldap exception.
|
||||
*
|
||||
* @param code result code describing this exception
|
||||
* @param msg describing this exception
|
||||
* @param e underlying exception
|
||||
*/
|
||||
public LdapException(final ResultCode code, final String msg, final Throwable e) {
|
||||
super(msg, e);
|
||||
resultCode = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the supplied result for use as an exception message.
|
||||
*
|
||||
* @param result to format
|
||||
* @return formatted result
|
||||
*/
|
||||
protected static String formatResult(final Result result) {
|
||||
return "resultCode=" + result.getResultCode() + ", " + "diagnosticMessage=" + result.getEncodedDiagnosticMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result code.
|
||||
*
|
||||
* @return result code or null
|
||||
*/
|
||||
public ResultCode getResultCode() {
|
||||
return resultCode;
|
||||
}
|
||||
}
|
516
net-ldap/src/main/java/org/xbib/net/ldap/LdapURL.java
Normal file
516
net-ldap/src/main/java/org/xbib/net/ldap/LdapURL.java
Normal file
|
@ -0,0 +1,516 @@
|
|||
|
||||
package org.xbib.net.ldap;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Class for parsing LDAP URLs. See RFC 4516. Expects URLs of the form scheme://hostname:port/baseDn?attrs?scope?filter.
|
||||
* This implementation does not support URL extensions.
|
||||
*
|
||||
*/
|
||||
public class LdapURL {
|
||||
|
||||
/**
|
||||
* Pattern to match LDAP URL.
|
||||
*/
|
||||
protected static final Pattern URL_PATTERN = Pattern.compile(
|
||||
"([lL][dD][aA][pP][sSiI]?)://(\\[[0-9A-Fa-f:]+\\]|[^:/]+)?" +
|
||||
"(?::(\\d+))?" +
|
||||
"(?:/(?:([^?]+))?" +
|
||||
"(?:\\?([^?]*))?" +
|
||||
"(?:\\?([^?]*))?" +
|
||||
"(?:\\?(.*))?)?");
|
||||
|
||||
/**
|
||||
* Default LDAP port, value is {@value}.
|
||||
*/
|
||||
protected static final int DEFAULT_LDAP_PORT = 389;
|
||||
|
||||
/**
|
||||
* Default LDAPS port, value is {@value}.
|
||||
*/
|
||||
protected static final int DEFAULT_LDAPS_PORT = 636;
|
||||
|
||||
/**
|
||||
* Default base DN, value is {@value}.
|
||||
*/
|
||||
protected static final String DEFAULT_BASE_DN = "";
|
||||
|
||||
/**
|
||||
* Default search filter value is '(objectClass=*)'.
|
||||
*/
|
||||
protected static final String DEFAULT_FILTER = "(objectClass=*)";
|
||||
|
||||
/**
|
||||
* Default scope, value is {@link SearchScope#OBJECT}.
|
||||
*/
|
||||
protected static final SearchScope DEFAULT_SCOPE = SearchScope.OBJECT;
|
||||
|
||||
/**
|
||||
* Default return attributes, value is all user attributes.
|
||||
*/
|
||||
protected static final String[] DEFAULT_ATTRIBUTES = ReturnAttributes.ALL_USER.value();
|
||||
|
||||
/**
|
||||
* hash code seed.
|
||||
*/
|
||||
private static final int HASH_CODE_SEED = 10333;
|
||||
|
||||
/**
|
||||
* Scheme of the ldap url.
|
||||
*/
|
||||
private String scheme;
|
||||
|
||||
/**
|
||||
* Hostname of the ldap url.
|
||||
*/
|
||||
private String hostname;
|
||||
|
||||
/**
|
||||
* Port of the ldap url.
|
||||
*/
|
||||
private int port;
|
||||
|
||||
/**
|
||||
* Base DN of the ldap url.
|
||||
*/
|
||||
private String baseDn;
|
||||
|
||||
/**
|
||||
* Attributes of the ldap url.
|
||||
*/
|
||||
private String[] attributes;
|
||||
|
||||
/**
|
||||
* Search scope of the ldap url.
|
||||
*/
|
||||
private SearchScope scope;
|
||||
|
||||
/**
|
||||
* Search filter of the ldap url.
|
||||
*/
|
||||
private String filter;
|
||||
|
||||
/**
|
||||
* Metadata that describes connection failures on this URL.
|
||||
*/
|
||||
private LdapURLRetryMetadata retryMetadata;
|
||||
|
||||
/**
|
||||
* False if the last connection attempt to this URL failed, which should result in updating {@link #retryMetadata},
|
||||
* otherwise true.
|
||||
*/
|
||||
private boolean active = true;
|
||||
|
||||
/**
|
||||
* IP address resolved for this URL.
|
||||
*/
|
||||
private InetAddress inetAddress;
|
||||
|
||||
|
||||
/**
|
||||
* Private constructor.
|
||||
*/
|
||||
private LdapURL() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new ldap url.
|
||||
*
|
||||
* @param hostname LDAP server hostname
|
||||
* @param port TCP port the LDAP server is listening on
|
||||
*/
|
||||
public LdapURL(final String hostname, final int port) {
|
||||
this("ldap://" + hostname + ":" + port);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new ldap url.
|
||||
*
|
||||
* @param url LDAP url
|
||||
*/
|
||||
public LdapURL(final String url) {
|
||||
parseURL(url);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new ldap url.
|
||||
*
|
||||
* @param scheme url scheme
|
||||
* @param hostname url hostname
|
||||
* @param port url port
|
||||
* @param baseDn base DN
|
||||
* @param attributes attributes
|
||||
* @param scope search scope
|
||||
* @param filter search filter
|
||||
*/
|
||||
protected LdapURL(
|
||||
final String scheme,
|
||||
final String hostname,
|
||||
final int port,
|
||||
final String baseDn,
|
||||
final String[] attributes,
|
||||
final SearchScope scope,
|
||||
final String filter) {
|
||||
if (scheme == null) {
|
||||
throw new IllegalArgumentException("Scheme cannot be null");
|
||||
}
|
||||
this.scheme = scheme;
|
||||
this.hostname = hostname;
|
||||
this.port = port;
|
||||
this.baseDn = baseDn;
|
||||
this.attributes = attributes;
|
||||
this.scope = scope;
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new ldap URL initialized with the supplied URL.
|
||||
*
|
||||
* @param ldapURL ldap URL to read properties from
|
||||
* @return ldap URL
|
||||
*/
|
||||
public static LdapURL copy(final LdapURL ldapURL) {
|
||||
final LdapURL url = new LdapURL();
|
||||
url.scheme = ldapURL.scheme;
|
||||
url.hostname = ldapURL.hostname;
|
||||
url.port = ldapURL.port;
|
||||
url.baseDn = ldapURL.baseDn;
|
||||
url.attributes = ldapURL.attributes;
|
||||
url.scope = ldapURL.scope;
|
||||
url.filter = ldapURL.filter;
|
||||
url.retryMetadata = ldapURL.retryMetadata;
|
||||
url.active = ldapURL.active;
|
||||
url.inetAddress = ldapURL.inetAddress;
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the scheme.
|
||||
*
|
||||
* @return scheme
|
||||
*/
|
||||
public String getScheme() {
|
||||
return scheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hostname.
|
||||
*
|
||||
* @return hostname
|
||||
*/
|
||||
public String getHostname() {
|
||||
return hostname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the port. If no port was supplied, returns the default port for the scheme.
|
||||
*
|
||||
* @return port
|
||||
*/
|
||||
public int getPort() {
|
||||
if (port == -1) {
|
||||
return "ldaps".equals(scheme) ? DEFAULT_LDAPS_PORT : DEFAULT_LDAP_PORT;
|
||||
}
|
||||
return port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false if a port was supplied in this url.
|
||||
*
|
||||
* @return false if a port was supplied in this url
|
||||
*/
|
||||
public boolean isDefaultPort() {
|
||||
return port == -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base DN.
|
||||
*
|
||||
* @return baseDn
|
||||
*/
|
||||
public String getBaseDn() {
|
||||
return baseDn == null ? DEFAULT_BASE_DN : baseDn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a base DN was supplied in this url.
|
||||
*
|
||||
* @return whether a base DN was supplied in this url
|
||||
*/
|
||||
public boolean isDefaultBaseDn() {
|
||||
return baseDn == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attributes.
|
||||
*
|
||||
* @return attributes
|
||||
*/
|
||||
public String[] getAttributes() {
|
||||
return attributes == null ? DEFAULT_ATTRIBUTES : attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether attributes were supplied in this url.
|
||||
*
|
||||
* @return whether an attributes were supplied in this url
|
||||
*/
|
||||
public boolean isDefaultAttributes() {
|
||||
return attributes == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the scope.
|
||||
*
|
||||
* @return scope
|
||||
*/
|
||||
public SearchScope getScope() {
|
||||
return scope == null ? DEFAULT_SCOPE : scope;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a scope was supplied in this url.
|
||||
*
|
||||
* @return whether a scope was supplied in this url
|
||||
*/
|
||||
public boolean isDefaultScope() {
|
||||
return scope == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the filter.
|
||||
*
|
||||
* @return filter
|
||||
*/
|
||||
public String getFilter() {
|
||||
return filter == null ? DEFAULT_FILTER : filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a filter was supplied in this url.
|
||||
*
|
||||
* @return whether a filter was supplied in this url
|
||||
*/
|
||||
public boolean isDefaultFilter() {
|
||||
return filter == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the formatted URL as scheme://hostname:port/baseDn?attrs?scope?filter.
|
||||
*
|
||||
* @return url
|
||||
*/
|
||||
public String getUrl() {
|
||||
|
||||
final StringBuilder sb = new StringBuilder(scheme).append("://");
|
||||
final String hostname = getHostname();
|
||||
if (hostname != null) {
|
||||
// ipv6 address
|
||||
if (hostname.contains(":")) {
|
||||
sb.append("[").append(hostname).append("]");
|
||||
} else {
|
||||
sb.append(hostname);
|
||||
}
|
||||
}
|
||||
sb.append(":").append(getPort());
|
||||
sb.append("/").append(LdapUtils.percentEncode(getBaseDn()));
|
||||
sb.append("?");
|
||||
|
||||
final String[] attrs = getAttributes();
|
||||
for (int i = 0; i < attrs.length; i++) {
|
||||
sb.append(attrs[i]);
|
||||
if (i + 1 < attrs.length) {
|
||||
sb.append(",");
|
||||
}
|
||||
}
|
||||
sb.append("?");
|
||||
|
||||
final SearchScope scope = getScope();
|
||||
if (SearchScope.OBJECT == scope) {
|
||||
sb.append("base");
|
||||
} else if (SearchScope.ONELEVEL == scope) {
|
||||
sb.append("one");
|
||||
} else if (SearchScope.SUBTREE == scope) {
|
||||
sb.append("sub");
|
||||
}
|
||||
sb.append("?").append(LdapUtils.percentEncode(getFilter()));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hostname:port.
|
||||
*
|
||||
* @return hostname:port
|
||||
*/
|
||||
public String getHostnameWithPort() {
|
||||
return (getHostname() != null ? getHostname() : "null") + ":" + getPort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the scheme://hostname:port.
|
||||
*
|
||||
* @return scheme://hostname:port
|
||||
*/
|
||||
public String getHostnameWithSchemeAndPort() {
|
||||
return getScheme() + "://" + (getHostname() != null ? getHostname() : "null") + ":" + getPort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the retry metadata.
|
||||
*
|
||||
* @return metadata describing retry attempts for connections made this URL.
|
||||
*/
|
||||
LdapURLRetryMetadata getRetryMetadata() {
|
||||
return retryMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the retry metadata.
|
||||
*
|
||||
* @param metadata retry metadata
|
||||
*/
|
||||
void setRetryMetadata(final LdapURLRetryMetadata metadata) {
|
||||
retryMetadata = metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this URL is currently active.
|
||||
*
|
||||
* @return true if this URL can be connected to, false otherwise.
|
||||
*/
|
||||
boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks this URL as active.
|
||||
*/
|
||||
void activate() {
|
||||
active = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks this URL as inactive.
|
||||
*/
|
||||
void deactivate() {
|
||||
active = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resolved IP address.
|
||||
*
|
||||
* @return resolved IP address for this URL.
|
||||
*/
|
||||
public InetAddress getInetAddress() {
|
||||
return inetAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the resolved IP address.
|
||||
*
|
||||
* @param address IP address for this URL
|
||||
*/
|
||||
void setInetAddress(final InetAddress address) {
|
||||
inetAddress = address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
if (o instanceof LdapURL) {
|
||||
final LdapURL v = (LdapURL) o;
|
||||
return LdapUtils.areEqual(scheme, v.scheme) &&
|
||||
LdapUtils.areEqual(hostname, v.hostname) &&
|
||||
LdapUtils.areEqual(port, v.port) &&
|
||||
LdapUtils.areEqual(baseDn, v.baseDn) &&
|
||||
LdapUtils.areEqual(attributes, v.attributes) &&
|
||||
LdapUtils.areEqual(scope, v.scope) &&
|
||||
LdapUtils.areEqual(filter, v.filter) &&
|
||||
LdapUtils.areEqual(inetAddress, v.inetAddress);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return LdapUtils.computeHashCode(
|
||||
HASH_CODE_SEED,
|
||||
scheme,
|
||||
hostname,
|
||||
port,
|
||||
baseDn,
|
||||
attributes,
|
||||
scope,
|
||||
filter,
|
||||
inetAddress);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" +
|
||||
getClass().getName() + "@" + hashCode() + "::" +
|
||||
"scheme=" + scheme + ", " +
|
||||
"hostname=" + hostname + ", " +
|
||||
"port=" + port + ", " +
|
||||
"baseDn=" + baseDn + ", " +
|
||||
"attributes=" + Arrays.toString(attributes) + ", " +
|
||||
"scope=" + scope + ", " +
|
||||
"filter=" + filter + ", " +
|
||||
"inetAddress=" + inetAddress + "]";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Matches the supplied url against a pattern and reads its components.
|
||||
*
|
||||
* @param url to parse
|
||||
*/
|
||||
protected void parseURL(final String url) {
|
||||
final Matcher m = URL_PATTERN.matcher(url);
|
||||
if (!m.matches()) {
|
||||
throw new IllegalArgumentException("Invalid LDAP URL: " + url);
|
||||
}
|
||||
|
||||
// CheckStyle:MagicNumber OFF
|
||||
scheme = LdapUtils.toLowerCaseAscii(m.group(1));
|
||||
hostname = m.group(2);
|
||||
if (hostname != null) {
|
||||
// check for ipv6 address
|
||||
if (hostname.startsWith("[") && hostname.endsWith("]")) {
|
||||
hostname = hostname.substring(1, hostname.length() - 1).trim();
|
||||
}
|
||||
}
|
||||
|
||||
port = m.group(3) != null ? Integer.parseInt(m.group(3)) : -1;
|
||||
baseDn = m.group(4) != null ? LdapUtils.percentDecode(m.group(4)) : null;
|
||||
attributes = m.group(5) != null ? m.group(5).length() > 0 ? m.group(5).split(",") : null : null;
|
||||
final String scope = m.group(6);
|
||||
if (scope != null && scope.length() > 0) {
|
||||
if ("base".equalsIgnoreCase(scope)) {
|
||||
this.scope = SearchScope.OBJECT;
|
||||
} else if ("one".equalsIgnoreCase(scope)) {
|
||||
this.scope = SearchScope.ONELEVEL;
|
||||
} else if ("sub".equalsIgnoreCase(scope)) {
|
||||
this.scope = SearchScope.SUBTREE;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid scope: " + scope);
|
||||
}
|
||||
}
|
||||
|
||||
filter = m.group(7) != null
|
||||
? m.group(7).length() > 0 ? LdapUtils.percentDecode(m.group(7)) : null : null;
|
||||
// CheckStyle:MagicNumber ON
|
||||
}
|
||||
}
|
||||
// CheckStyle:HiddenField ON
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue