From 0c8eafb1c2d22f8b418874803359c0f5dac558cc Mon Sep 17 00:00:00 2001 From: David Waltermire Date: Mon, 6 Jan 2025 07:50:51 -0500 Subject: [PATCH] Adjusted behavior of the base64 item and adapter to store the encoded text, instead of decoded text. This should make the use of this item more effecient. Added the 'hex-binary' data type to represent raw binary data. --- core/pom.xml | 4 + .../datatype/AbstractDataTypeAdapter.java | 3 +- .../adapter/AbstractBinaryAdapter.java | 192 ++++++++++++++++++ .../core/datatype/adapter/Base64Adapter.java | 129 ++---------- .../datatype/adapter/HexBinaryAdapter.java | 65 ++++++ .../adapter/MetaschemaDataTypeProvider.java | 6 + .../function/library/MpBase64Encode.java | 4 +- .../item/atomic/IBase64BinaryItem.java | 45 ++-- .../metapath/item/atomic/IHexBinaryItem.java | 122 +++++++++++ .../metapath/item/atomic/IStringItem.java | 13 +- .../item/atomic/impl/AbstractBinaryItem.java | 40 ++++ .../atomic/impl/Base64BinaryItemImpl.java | 13 +- .../item/atomic/impl/HexBinaryItem.java | 75 +++++++ .../item/atomic/impl/IBinaryItem.java | 29 +++ core/src/main/java/module-info.java | 4 +- .../item/atomic/IBase64BinaryItemTest.java | 29 ++- pom.xml | 6 + 17 files changed, 607 insertions(+), 172 deletions(-) create mode 100644 core/src/main/java/gov/nist/secauto/metaschema/core/datatype/adapter/AbstractBinaryAdapter.java create mode 100644 core/src/main/java/gov/nist/secauto/metaschema/core/datatype/adapter/HexBinaryAdapter.java create mode 100644 core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IHexBinaryItem.java create mode 100644 core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AbstractBinaryItem.java create mode 100644 core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/HexBinaryItem.java create mode 100644 core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/IBinaryItem.java diff --git a/core/pom.xml b/core/pom.xml index 720a18fea..65975dd49 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -99,6 +99,10 @@ nl.talsmasoftware lazy4j + + commons-codec + commons-codec + org.apache.commons commons-text diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/AbstractDataTypeAdapter.java b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/AbstractDataTypeAdapter.java index 59bb5a0f0..a1c805e1a 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/AbstractDataTypeAdapter.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/AbstractDataTypeAdapter.java @@ -39,8 +39,7 @@ * @param * the raw Java type this adapter supports * @param - * the metapath item type corresponding to the raw Java type supported - * by the adapter + * the metapath item type supported by the adapter */ public abstract class AbstractDataTypeAdapter implements IDataTypeAdapter { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/adapter/AbstractBinaryAdapter.java b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/adapter/AbstractBinaryAdapter.java new file mode 100644 index 000000000..56c15d659 --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/adapter/AbstractBinaryAdapter.java @@ -0,0 +1,192 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.datatype.adapter; + +import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatTypes; + +import gov.nist.secauto.metaschema.core.datatype.AbstractDataTypeAdapter; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; +import gov.nist.secauto.metaschema.core.metapath.type.AbstractAtomicOrUnionType; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; + +import org.apache.commons.codec.BinaryDecoder; +import org.apache.commons.codec.BinaryEncoder; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.EncoderException; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * Maintains a byte buffer representation of a byte stream. + *

+ * The maintained byte stream is kept in a decoded form. + * + * @param + * the metapath item type supported by the adapter + */ +public abstract class AbstractBinaryAdapter + extends AbstractDataTypeAdapter { + + /** + * Construct a new Java type adapter for a provided class. + * + * @param itemClass + * the Java type of the Matepath item this adapter supports + * @param castExecutor + * the method to call to cast an item to an item based on this type + */ + protected AbstractBinaryAdapter( + @NonNull Class itemClass, + @NonNull AbstractAtomicOrUnionType.ICastExecutor castExecutor) { + super(ByteBuffer.class, itemClass, castExecutor); + } + + @NonNull + protected abstract BinaryDecoder getDecoder(); + + @NonNull + protected abstract BinaryEncoder getEncoder(); + + @NonNull + private static byte[] stringToBytes(@NonNull String text) { + return text.getBytes(StandardCharsets.UTF_8); + } + + @NonNull + private static String bytesToString(@NonNull byte[] bytes) { + return new String(bytes, StandardCharsets.UTF_8); + } + + private static String elide(@NonNull String text, int length) { + return text.length() <= length ? text : text.substring(0, length) + "…"; + } + + @NonNull + public byte[] encode(@NonNull byte[] decodedBytes) { + try { + return ObjectUtils.notNull(getEncoder().encode(decodedBytes)); + } catch (EncoderException ex) { + throw new IllegalArgumentException( + String.format("unable to encode text '%s'", elide(bytesToString(decodedBytes), 64)), + ex); + } + } + + @NonNull + public ByteBuffer encodeToByteBuffer(@NonNull ByteBuffer decodedBuffer) { + byte[] decodedBytes = bufferToBytes(decodedBuffer, false); + return encodeToByteBuffer(decodedBytes); + } + + @NonNull + public ByteBuffer encodeToByteBuffer(String decodedText) { + byte[] decodedBytes = stringToBytes(decodedText); + return encodeToByteBuffer(decodedBytes); + } + + @NonNull + public ByteBuffer encodeToByteBuffer(byte[] decodedBytes) { + byte[] encodedBytes = encode(decodedBytes); + return ObjectUtils.notNull(ByteBuffer.wrap(encodedBytes)); + } + + @NonNull + public byte[] decode(@NonNull byte[] enodedBytes) { + try { + return ObjectUtils.notNull(getDecoder().decode(enodedBytes)); + } catch (DecoderException ex) { + throw new IllegalArgumentException( + String.format("unable to decode text '%s'", elide(bytesToString(enodedBytes), 64)), + ex); + } + } + + @NonNull + public ByteBuffer decode(@NonNull ByteBuffer encodedBuffer) { + byte[] encodedBytes = bufferToBytes(encodedBuffer, false); + byte[] decodedBytes = decode(encodedBytes); + return ObjectUtils.notNull(ByteBuffer.wrap(decodedBytes)); + } + + @NonNull + public String decodeToString(@NonNull byte[] encodedBytes) { + byte[] decodedBytes = decode(encodedBytes); + return bytesToString(decodedBytes); + } + + /** + * Decodes the provided string. + * + * @return a buffer containing the decoded bytes + */ + @Override + public ByteBuffer parse(String encodedString) { + byte[] encodedBytes = stringToBytes(encodedString); + // byte[] decodedBytes = decode(encodedBytes); + // return ObjectUtils.notNull(ByteBuffer.wrap(decodedBytes)); + return ObjectUtils.notNull(ByteBuffer.wrap(encodedBytes)); + } + + /** + * Get the wrapped value as a base64 encoded string. + *

+ * Encodes the wrapped value to produce a string. + * + * @return the base64 encoded value + */ + @Override + public String asString(Object encodedBuffer) { + byte[] encodedBytes = bufferToBytes((ByteBuffer) encodedBuffer, false); + return new String(encodedBytes, StandardCharsets.UTF_8); + // return bytesToString(encodedBytes); + + // byte[] decodedBytes = bufferToBytes((ByteBuffer) decodedBuffer, false); + // byte[] encodedBytes = encode(decodedBytes); + // return bytesToString(encodedBytes); + } + + @Override + public JsonFormatTypes getJsonRawType() { + return JsonFormatTypes.STRING; + } + + @Override + public ByteBuffer copy(Object obj) { + ByteBuffer buffer = (ByteBuffer) obj; + ByteBuffer clone = buffer.isDirect() + ? ByteBuffer.allocateDirect(buffer.capacity()) + : ByteBuffer.allocate(buffer.capacity()); + ByteBuffer readOnlyCopy = buffer.asReadOnlyBuffer(); + readOnlyCopy.flip(); + clone.put(readOnlyCopy); + return clone; + } + + @NonNull + public static byte[] bufferToBytes(@NonNull ByteBuffer buffer, boolean copy) { + byte[] array; + if (buffer.hasArray()) { + array = buffer.array(); + if (copy) { + array = Arrays.copyOf(array, array.length); + } + } else { + // Handle direct buffers + array = new byte[buffer.remaining()]; + buffer.mark(); + try { + buffer.get(array); + } finally { + buffer.reset(); + } + } + return ObjectUtils.notNull(array); + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/adapter/Base64Adapter.java b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/adapter/Base64Adapter.java index fc006d11b..0724903a9 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/adapter/Base64Adapter.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/adapter/Base64Adapter.java @@ -5,33 +5,31 @@ package gov.nist.secauto.metaschema.core.datatype.adapter; -import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatTypes; - -import gov.nist.secauto.metaschema.core.datatype.AbstractDataTypeAdapter; import gov.nist.secauto.metaschema.core.metapath.MetapathConstants; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IBase64BinaryItem; import gov.nist.secauto.metaschema.core.qname.EQNameFactory; import gov.nist.secauto.metaschema.core.qname.IEnhancedQName; import gov.nist.secauto.metaschema.core.util.ObjectUtils; +import org.apache.commons.codec.BinaryDecoder; +import org.apache.commons.codec.BinaryEncoder; +import org.apache.commons.codec.binary.Base64; + import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Base64; import java.util.List; import edu.umd.cs.findbugs.annotations.NonNull; /** - * Maintains a byte buffer backed representation of a byte stream parsed from a base64 encoded - * string. + * Maintains a byte buffer backed representation of a byte stream parsed from a + * BASE64 encoded string. *

- * Provides support for the Metaschema - * base64 data - * type. + * Provides support for the Metaschema BASE64 + * data type. */ public class Base64Adapter - extends AbstractDataTypeAdapter { + extends AbstractBinaryAdapter { @NonNull private static final List NAMES = ObjectUtils.notNull( List.of( @@ -39,48 +37,26 @@ public class Base64Adapter // for backwards compatibility with original type name EQNameFactory.instance().newQName(MetapathConstants.NS_METAPATH, "base64Binary"))); - Base64Adapter() { - super(ByteBuffer.class, IBase64BinaryItem.class, IBase64BinaryItem::cast); - } - - @Override - public List getNames() { - return NAMES; - } + @NonNull + private static final Base64 BASE64 = ObjectUtils.notNull(Base64.builder().get()); - @Override - public JsonFormatTypes getJsonRawType() { - return JsonFormatTypes.STRING; + Base64Adapter() { + super(IBase64BinaryItem.class, IBase64BinaryItem::cast); } @Override - public ByteBuffer parse(String value) { - Base64.Decoder decoder = Base64.getDecoder(); - byte[] result = decoder.decode(value); - return ObjectUtils.notNull(ByteBuffer.wrap(result)); + protected BinaryEncoder getEncoder() { + return BASE64; } @Override - public ByteBuffer copy(Object obj) { - ByteBuffer buffer = (ByteBuffer) obj; - ByteBuffer clone = buffer.isDirect() - ? ByteBuffer.allocateDirect(buffer.capacity()) - : ByteBuffer.allocate(buffer.capacity()); - ByteBuffer readOnlyCopy = buffer.asReadOnlyBuffer(); - readOnlyCopy.flip(); - clone.put(readOnlyCopy); - return clone; + protected BinaryDecoder getDecoder() { + return BASE64; } - /** - * Get the wrapped value as a base64 encoded string. - * - * @return the base64 encoded value - */ @Override - public String asString(Object value) { - byte[] array = bufferToBytes((ByteBuffer) value, false); - return ObjectUtils.notNull(Base64.getEncoder().encodeToString(array)); + public List getNames() { + return NAMES; } @Override @@ -88,69 +64,4 @@ public IBase64BinaryItem newItem(Object value) { ByteBuffer item = toValue(value); return IBase64BinaryItem.valueOf(item); } - - @NonNull - public static ByteBuffer encode(@NonNull ByteBuffer decodedBuffer) { - return Base64.getEncoder().encode(decodedBuffer); - } - - @NonNull - public static ByteBuffer encodeToByteBuffer(@NonNull String decodedString) { - return encodeToByteBuffer(decodedString.getBytes()); - } - - @NonNull - public static ByteBuffer encodeToByteBuffer(@NonNull byte[] bytes) { - byte[] encodedBytes = Base64.getEncoder().encode(bytes); - return ByteBuffer.wrap(encodedBytes); - } - - @NonNull - public static ByteBuffer decode(@NonNull ByteBuffer encodedBuffer) { - return Base64.getDecoder().decode(encodedBuffer); - } - - @NonNull - public static String decodeToString(@NonNull ByteBuffer encodedBuffer) { - ByteBuffer decodedBuffer = decode(encodedBuffer); - byte[] decodedBytes = bufferToBytes(decodedBuffer, true); - return new String(decodedBytes, StandardCharsets.UTF_8); - } - - @NonNull - public static String decodeToString(@NonNull String encodedString) { - byte[] decodedBytes = Base64.getDecoder().decode(encodedString); - return new String(decodedBytes, StandardCharsets.UTF_8); - } - - @NonNull - public ByteBuffer stringToByteBuffer(@NonNull String text) { - return ByteBuffer.wrap(text.getBytes(StandardCharsets.UTF_8)); - } - - @NonNull - public String byteBufferToString(@NonNull ByteBuffer buffer) { - byte[] bytes = bufferToBytes(buffer, false); - return new String(bytes, StandardCharsets.UTF_8); - } - - private static byte[] bufferToBytes(@NonNull ByteBuffer buffer, boolean copy) { - byte[] array; - if (buffer.hasArray()) { - array = buffer.array(); - if (copy) { - array = Arrays.copyOf(array, array.length); - } - } else { - // Handle direct buffers - array = new byte[buffer.remaining()]; - buffer.mark(); - try { - buffer.get(array); - } finally { - buffer.reset(); - } - } - return array; - } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/adapter/HexBinaryAdapter.java b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/adapter/HexBinaryAdapter.java new file mode 100644 index 000000000..94021308b --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/adapter/HexBinaryAdapter.java @@ -0,0 +1,65 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.datatype.adapter; + +import gov.nist.secauto.metaschema.core.metapath.MetapathConstants; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IHexBinaryItem; +import gov.nist.secauto.metaschema.core.qname.EQNameFactory; +import gov.nist.secauto.metaschema.core.qname.IEnhancedQName; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; + +import org.apache.commons.codec.BinaryDecoder; +import org.apache.commons.codec.BinaryEncoder; +import org.apache.commons.codec.binary.Hex; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * Maintains a byte buffer backed representation of a byte stream parsed from a + * base64 encoded string. + *

+ * Provides support for the Metaschema base64 + * data type. + */ +public class HexBinaryAdapter + extends AbstractBinaryAdapter { + @NonNull + private static final List NAMES = ObjectUtils.notNull(List.of( + EQNameFactory.instance().newQName(MetapathConstants.NS_METAPATH, "hex-binary"))); + + @NonNull + private static final Hex HEX = new Hex(StandardCharsets.UTF_8); + + HexBinaryAdapter() { + super(IHexBinaryItem.class, IHexBinaryItem::cast); + } + + @Override + protected BinaryEncoder getEncoder() { + return HEX; + } + + @Override + protected BinaryDecoder getDecoder() { + return HEX; + } + + @Override + public List getNames() { + return NAMES; + } + + @Override + public IHexBinaryItem newItem(Object value) { + ByteBuffer item = toValue(value); + return IHexBinaryItem.valueOf(item); + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/adapter/MetaschemaDataTypeProvider.java b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/adapter/MetaschemaDataTypeProvider.java index 447071ece..fb18c5360 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/adapter/MetaschemaDataTypeProvider.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/adapter/MetaschemaDataTypeProvider.java @@ -17,6 +17,11 @@ @SuppressWarnings("PMD.CouplingBetweenObjects") public final class MetaschemaDataTypeProvider // NOPMD - Used for service initialization extends AbstractDataTypeProvider { + /** + * The Metaschema hex-binary data type instance. + */ + @NonNull + public static final HexBinaryAdapter HEX_BINARY = new HexBinaryAdapter(); /** * The Metaschema base64 @@ -210,6 +215,7 @@ public MetaschemaDataTypeProvider() { register(DAY_TIME_DURATION); register(DECIMAL); register(EMAIL_ADDRESS); + register(HEX_BINARY); register(HOSTNAME); register(INTEGER); register(IP_V4_ADDRESS); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/MpBase64Encode.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/MpBase64Encode.java index c87a00797..665c7942c 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/MpBase64Encode.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/MpBase64Encode.java @@ -21,8 +21,8 @@ import edu.umd.cs.findbugs.annotations.NonNull; /** - * Provides new Metapath functions that evaluate a Metapath recursively over sequences generated by - * evaluating that expression. + * Provides new Metapath functions that evaluate a Metapath recursively over + * sequences generated by evaluating that expression. */ public final class MpBase64Encode { private static final String NAME = "base64-encode-text"; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IBase64BinaryItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IBase64BinaryItem.java index 2b44b31c4..30df72be6 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IBase64BinaryItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IBase64BinaryItem.java @@ -5,10 +5,10 @@ package gov.nist.secauto.metaschema.core.metapath.item.atomic; -import gov.nist.secauto.metaschema.core.datatype.adapter.Base64Adapter; import gov.nist.secauto.metaschema.core.datatype.adapter.MetaschemaDataTypeProvider; import gov.nist.secauto.metaschema.core.metapath.function.InvalidValueForCastFunctionException; import gov.nist.secauto.metaschema.core.metapath.item.atomic.impl.Base64BinaryItemImpl; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.impl.IBinaryItem; import gov.nist.secauto.metaschema.core.metapath.type.IAtomicOrUnionType; import gov.nist.secauto.metaschema.core.metapath.type.InvalidTypeMetapathException; @@ -19,7 +19,7 @@ /** * An atomic Metapath item containing a Base64 encoded data value. */ -public interface IBase64BinaryItem extends IAnyAtomicItem { +public interface IBase64BinaryItem extends IBinaryItem { /** * Get the type information for this item. * @@ -36,27 +36,30 @@ default IAtomicOrUnionType getType() { } static IBase64BinaryItem encode(@NonNull String value) { - // Encode the string to Base64 - return valueOf(Base64Adapter.encodeToByteBuffer(value)); + return valueOf(MetaschemaDataTypeProvider.BASE64.encodeToByteBuffer(value)); } @NonNull static IBase64BinaryItem encode(@NonNull byte[] bytes) { - return valueOf(Base64Adapter.encodeToByteBuffer(bytes)); + return valueOf(MetaschemaDataTypeProvider.BASE64.encodeToByteBuffer(bytes)); } @NonNull static IBase64BinaryItem encode(@NonNull ByteBuffer buffer) { - return valueOf(Base64Adapter.encode(buffer)); + return valueOf(MetaschemaDataTypeProvider.BASE64.encodeToByteBuffer(buffer)); + } + + default IHexBinaryItem decode() { + return IHexBinaryItem.valueOf(MetaschemaDataTypeProvider.BASE64.decode(asByteBuffer())); } default IStringItem decodeAsString() { - // Encode the string to Base64 - return IStringItem.valueOf(Base64Adapter.decodeToString(asByteBuffer())); + return IStringItem.valueOf(MetaschemaDataTypeProvider.BASE64.decodeToString(asBytes())); } /** - * Construct a new base64 byte sequence item using the provided base64 encoded string {@code value}. + * Construct a new base64 byte sequence item using the provided base64 encoded + * string {@code value}. * * @param value * a string representing base64 encoded data @@ -79,11 +82,12 @@ static IBase64BinaryItem valueOf(@NonNull String value) { } /** - * Construct a new URI base64 encoded byte sequence using the provided {@link ByteBuffer} - * {@code value}. + * Construct a new URI base64 encoded byte sequence using the provided + * {@link ByteBuffer} {@code value}. *

- * The provided buffer will be managed by this instance. Make a copy of the buffer to ensure that - * the position, limit, and mark of the original are not affect by this. + * The provided buffer will be managed by this instance. Make a copy of the + * buffer to ensure that the position, limit, and mark of the original are not + * affect by this. * * @param buffer * a byte buffer @@ -99,7 +103,8 @@ static IBase64BinaryItem valueOf(@NonNull ByteBuffer buffer) { * * @param item * the item to cast - * @return the original item if it is already this type, otherwise a new item cast to this type + * @return the original item if it is already this type, otherwise a new item + * cast to this type * @throws InvalidValueForCastFunctionException * if the provided {@code item} cannot be cast to this type */ @@ -120,14 +125,6 @@ default IBase64BinaryItem castAsType(IAnyAtomicItem item) { return cast(item); } - /** - * Get the "wrapped" byte buffer value. - * - * @return the underlying byte buffer value - */ - @NonNull - ByteBuffer asByteBuffer(); - @Override default int compareTo(IAnyAtomicItem item) { return compareTo(cast(item)); @@ -138,8 +135,8 @@ default int compareTo(IAnyAtomicItem item) { * * @param item * the item to compare with this value - * @return a negative integer, zero, or a positive integer if this value is less than, equal to, or - * greater than the {@code item}. + * @return a negative integer, zero, or a positive integer if this value is less + * than, equal to, or greater than the {@code item}. */ default int compareTo(@NonNull IBase64BinaryItem item) { return asByteBuffer().compareTo(item.asByteBuffer()); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IHexBinaryItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IHexBinaryItem.java new file mode 100644 index 000000000..0221aede8 --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IHexBinaryItem.java @@ -0,0 +1,122 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.item.atomic; + +import gov.nist.secauto.metaschema.core.datatype.adapter.MetaschemaDataTypeProvider; +import gov.nist.secauto.metaschema.core.metapath.function.InvalidValueForCastFunctionException; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.impl.HexBinaryItem; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.impl.IBinaryItem; +import gov.nist.secauto.metaschema.core.metapath.type.IAtomicOrUnionType; +import gov.nist.secauto.metaschema.core.metapath.type.InvalidTypeMetapathException; + +import java.nio.ByteBuffer; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * An atomic Metapath item containing a Base64 encoded data value. + */ +public interface IHexBinaryItem extends IBinaryItem { + /** + * Get the type information for this item. + * + * @return the type information + */ + @NonNull + static IAtomicOrUnionType type() { + return MetaschemaDataTypeProvider.HEX_BINARY.getItemType(); + } + + @Override + default IAtomicOrUnionType getType() { + return type(); + } + + /** + * Construct a new base64 byte sequence item using the provided base64 encoded + * string {@code value}. + * + * @param value + * a string representing base64 encoded data + * @return the new item + * @throws InvalidTypeMetapathException + * if the provided string is not a valid Base64 character sequence + */ + @NonNull + static IHexBinaryItem valueOf(@NonNull String value) { + try { + return valueOf(MetaschemaDataTypeProvider.BASE64.parse(value)); + } catch (IllegalArgumentException ex) { + throw new InvalidTypeMetapathException( + null, + String.format("The value starting with '%s' is not a valid hex encoded character sequence. %s", + value.substring(0, Math.min(value.length(), 200)), + ex.getLocalizedMessage()), + ex); + } + } + + /** + * Construct a new URI base64 encoded byte sequence using the provided + * {@link ByteBuffer} {@code value}. + *

+ * The provided buffer will be managed by this instance. Make a copy of the + * buffer to ensure that the position, limit, and mark of the original are not + * affect by this. + * + * @param buffer + * a byte buffer + * @return the new item + */ + @NonNull + static IHexBinaryItem valueOf(@NonNull ByteBuffer buffer) { + return new HexBinaryItem(buffer); + } + + /** + * Cast the provided type to this item type. + * + * @param item + * the item to cast + * @return the original item if it is already this type, otherwise a new item + * cast to this type + * @throws InvalidValueForCastFunctionException + * if the provided {@code item} cannot be cast to this type + */ + @NonNull + static IHexBinaryItem cast(@NonNull IAnyAtomicItem item) { + try { + return item instanceof IHexBinaryItem + ? (IHexBinaryItem) item + : valueOf(item.asString()); + } catch (IllegalStateException | InvalidTypeMetapathException ex) { + // asString can throw IllegalStateException exception + throw new InvalidValueForCastFunctionException(ex); + } + } + + @Override + default IHexBinaryItem castAsType(IAnyAtomicItem item) { + return cast(item); + } + + @Override + default int compareTo(IAnyAtomicItem item) { + return compareTo(cast(item)); + } + + /** + * Compares this value with the argument. + * + * @param item + * the item to compare with this value + * @return a negative integer, zero, or a positive integer if this value is less + * than, equal to, or greater than the {@code item}. + */ + default int compareTo(@NonNull IHexBinaryItem item) { + return asByteBuffer().compareTo(item.asByteBuffer()); + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IStringItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IStringItem.java index 1760f1888..07ff0266d 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IStringItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IStringItem.java @@ -36,7 +36,8 @@ default IAtomicOrUnionType getType() { * Construct a new item using the provided string {@code value}. * * @param value - * a string value that must conform to Metaschema string validation rules + * a string value that must conform to Metaschema string validation + * rules * @return the new item * @throws InvalidTypeMetapathException * if the value fails string validation @@ -65,7 +66,8 @@ default IBase64BinaryItem encode() { * * @param item * the item to cast - * @return the original item if it is already this type, otherwise a new item cast to this type + * @return the original item if it is already this type, otherwise a new item + * cast to this type * @throws InvalidValueForCastFunctionException * if the provided {@code item} cannot be cast to this type */ @@ -92,12 +94,13 @@ default IStringItem castAsType(IAnyAtomicItem item) { } /** - * Compares this value with the argument. Ordering is in lexical dictionary order. + * Compares this value with the argument. Ordering is in lexical dictionary + * order. * * @param other * the item to compare with this value - * @return a negative integer, zero, or a positive integer if this value is less than, equal to, or - * greater than the {@code item}. + * @return a negative integer, zero, or a positive integer if this value is less + * than, equal to, or greater than the {@code item}. */ default int compareTo(@NonNull IStringItem other) { return asString().compareTo(other.asString()); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AbstractBinaryItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AbstractBinaryItem.java new file mode 100644 index 000000000..84f15eabb --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AbstractBinaryItem.java @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.item.atomic.impl; + +import gov.nist.secauto.metaschema.core.metapath.item.atomic.AbstractAnyAtomicItem; + +import java.nio.ByteBuffer; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * An implementation of a Metapath atomic item containing a binary data value. + */ +public abstract class AbstractBinaryItem + extends AbstractAnyAtomicItem + implements IBinaryItem { + + /** + * Construct a new item with the provided {@code value}. + * + * @param value + * the value to wrap + */ + public AbstractBinaryItem(@NonNull ByteBuffer value) { + super(value); + } + + @Override + public ByteBuffer asByteBuffer() { + return getValue(); + } + + @Override + protected String getValueSignature() { + return "'" + asString() + "'"; + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/Base64BinaryItemImpl.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/Base64BinaryItemImpl.java index 8191c74af..2c8ee77c2 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/Base64BinaryItemImpl.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/Base64BinaryItemImpl.java @@ -7,7 +7,6 @@ import gov.nist.secauto.metaschema.core.datatype.adapter.Base64Adapter; import gov.nist.secauto.metaschema.core.datatype.adapter.MetaschemaDataTypeProvider; -import gov.nist.secauto.metaschema.core.metapath.item.atomic.AbstractAnyAtomicItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IBase64BinaryItem; import gov.nist.secauto.metaschema.core.metapath.item.function.IMapKey; @@ -20,7 +19,7 @@ * value. */ public class Base64BinaryItemImpl - extends AbstractAnyAtomicItem + extends AbstractBinaryItem implements IBase64BinaryItem { /** @@ -38,16 +37,6 @@ public Base64Adapter getJavaTypeAdapter() { return MetaschemaDataTypeProvider.BASE64; } - @Override - public ByteBuffer asByteBuffer() { - return getValue(); - } - - @Override - protected String getValueSignature() { - return "'" + asString() + "'"; - } - @Override public IMapKey asMapKey() { return new MapKey(); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/HexBinaryItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/HexBinaryItem.java new file mode 100644 index 000000000..d9070ef2b --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/HexBinaryItem.java @@ -0,0 +1,75 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.item.atomic.impl; + +import gov.nist.secauto.metaschema.core.datatype.adapter.HexBinaryAdapter; +import gov.nist.secauto.metaschema.core.datatype.adapter.MetaschemaDataTypeProvider; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IHexBinaryItem; +import gov.nist.secauto.metaschema.core.metapath.item.function.IMapKey; + +import java.nio.ByteBuffer; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * An implementation of a Metapath atomic item containing a Base64 encoded data + * value. + */ +public class HexBinaryItem + extends AbstractBinaryItem + implements IHexBinaryItem { + + /** + * Construct a new item with the provided {@code value}. + * + * @param value + * the value to wrap + */ + public HexBinaryItem(@NonNull ByteBuffer value) { + super(value); + } + + @Override + public HexBinaryAdapter getJavaTypeAdapter() { + return MetaschemaDataTypeProvider.HEX_BINARY; + } + + @Override + public IMapKey asMapKey() { + return new MapKey(); + } + + @Override + public int hashCode() { + return asByteBuffer().hashCode(); + } + + @SuppressWarnings("PMD.OnlyOneReturn") + @Override + public boolean equals(Object obj) { + return this == obj + || obj instanceof IHexBinaryItem && compareTo((IHexBinaryItem) obj) == 0; + } + + private final class MapKey implements IMapKey { + @Override + public IHexBinaryItem getKey() { + return HexBinaryItem.this; + } + + @Override + public int hashCode() { + return getKey().hashCode(); + } + + @Override + public boolean equals(Object obj) { + return this == obj || + obj instanceof MapKey + && getKey().equals(((MapKey) obj).getKey()); + } + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/IBinaryItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/IBinaryItem.java new file mode 100644 index 000000000..7917b1855 --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/IBinaryItem.java @@ -0,0 +1,29 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.item.atomic.impl; + +import gov.nist.secauto.metaschema.core.datatype.adapter.AbstractBinaryAdapter; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; + +import java.nio.ByteBuffer; + +import edu.umd.cs.findbugs.annotations.NonNull; + +public interface IBinaryItem extends IAnyAtomicItem { + + /** + * Get the "wrapped" byte buffer value. + * + * @return the underlying byte buffer value + */ + @NonNull + ByteBuffer asByteBuffer(); + + @NonNull + default byte[] asBytes() { + return AbstractBinaryAdapter.bufferToBytes(asByteBuffer(), true); + } +} diff --git a/core/src/main/java/module-info.java b/core/src/main/java/module-info.java index c69b5f2b0..f3468d7b8 100644 --- a/core/src/main/java/module-info.java +++ b/core/src/main/java/module-info.java @@ -14,8 +14,7 @@ * @provides IFunctionLibrary for core built-in Metapath functions * @uses IDataTypeProvider to discover data types implementing * {@link gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter} - * @uses IFunctionLibrary to discover collections of Metapath functions - * implementing + * @uses IFunctionLibrary to discover collections of Metapath functions implementing * {@link gov.nist.secauto.metaschema.core.metapath.function.IFunction} */ module gov.nist.secauto.metaschema.core { @@ -35,6 +34,7 @@ requires transitive inet.ipaddr; requires nl.talsmasoftware.lazy4j; requires org.antlr.antlr4.runtime; + requires org.apache.commons.codec; requires org.apache.commons.lang3; requires org.apache.commons.text; requires org.apache.logging.log4j; diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IBase64BinaryItemTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IBase64BinaryItemTest.java index cad887f03..dc39268c4 100644 --- a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IBase64BinaryItemTest.java +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IBase64BinaryItemTest.java @@ -8,7 +8,6 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; -import gov.nist.secauto.metaschema.core.datatype.adapter.Base64Adapter; import gov.nist.secauto.metaschema.core.datatype.adapter.MetaschemaDataTypeProvider; import gov.nist.secauto.metaschema.core.util.ObjectUtils; @@ -30,16 +29,19 @@ class IBase64BinaryItemTest { @Test void testValueOf() { - IBase64BinaryItem item = IBase64BinaryItem.valueOf(ObjectUtils.notNull( - ByteBuffer.allocate(16).putLong(MIN_LONG).putLong(MAX_LONG))); + IBase64BinaryItem item = IBase64BinaryItem.encode(ObjectUtils.notNull( + ByteBuffer.allocate(16) + .putLong(MIN_LONG) + .putLong(MAX_LONG))); assertEquals(BASE_64, item.asString()); } @Test void testCastSame() { - ByteBuffer buf - = ObjectUtils.notNull(ByteBuffer.allocate(16).putLong(MIN_LONG).putLong(MAX_LONG)); - IBase64BinaryItem item = IBase64BinaryItem.valueOf(buf); + ByteBuffer buf = ObjectUtils.notNull(ByteBuffer.allocate(16) + .putLong(MIN_LONG) + .putLong(MAX_LONG)); + IBase64BinaryItem item = IBase64BinaryItem.encode(buf); assertEquals(IBase64BinaryItem.cast(item), item); } @@ -47,7 +49,7 @@ void testCastSame() { void testCastString() { ByteBuffer buf = ObjectUtils.notNull(ByteBuffer.allocate(16).putLong(MIN_LONG).putLong(MAX_LONG)); - IBase64BinaryItem expected = IBase64BinaryItem.valueOf(buf); + IBase64BinaryItem expected = IBase64BinaryItem.encode(buf); IBase64BinaryItem actual = IBase64BinaryItem.cast(IStringItem.valueOf(BASE_64)); Assertions.assertAll( // TODO: use equals method? @@ -69,20 +71,15 @@ private static Stream provideValuesForEncodeDecode() { @MethodSource("provideValuesForEncodeDecode") void testEncodeDecodeEncode(@NonNull String expectedDecodedString, @NonNull String expectedEncodedString) { // test encode to buffer - ByteBuffer encodedBuffer = Base64Adapter.encodeToByteBuffer(expectedDecodedString); + ByteBuffer encodedBuffer = MetaschemaDataTypeProvider.BASE64.encodeToByteBuffer(expectedDecodedString); encodedBuffer.mark(); - String encodedString = MetaschemaDataTypeProvider.BASE64.byteBufferToString(encodedBuffer); + String encodedString = MetaschemaDataTypeProvider.BASE64.asString(encodedBuffer); assertEquals(expectedEncodedString, encodedString); // test decode from buffer - ByteBuffer decodedBuffer = Base64Adapter.decode(encodedBuffer); + ByteBuffer decodedBuffer = MetaschemaDataTypeProvider.BASE64.decode(encodedBuffer); decodedBuffer.mark(); - String decodedString = MetaschemaDataTypeProvider.BASE64.byteBufferToString(decodedBuffer); - assertEquals(expectedDecodedString, decodedString); - - // test decode from buffer directly to string - encodedBuffer.reset(); - decodedString = Base64Adapter.decodeToString(encodedBuffer); + String decodedString = MetaschemaDataTypeProvider.BASE64.asString(decodedBuffer); assertEquals(expectedDecodedString, decodedString); } } diff --git a/pom.xml b/pom.xml index cb971dd31..862ca32fb 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,7 @@ 4.13.2 3.27.1 1.9.0 + 1.17.1 4.4 2.18.0 3.17.0 @@ -400,6 +401,11 @@ ${dependency.commons-lang3.version} + commons-codec + commons-codec + ${dependency.commons-codec.version} + + org.apache.commons commons-collections4 ${dependency.commons-collections4.version}