Skip to content

Commit

Permalink
Adjusted behavior of the base64 item and adapter to store the encoded…
Browse files Browse the repository at this point in the history
… 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.
  • Loading branch information
david-waltermire committed Jan 6, 2025
1 parent f7932f3 commit 0c8eafb
Show file tree
Hide file tree
Showing 17 changed files with 607 additions and 172 deletions.
4 changes: 4 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@
<groupId>nl.talsmasoftware</groupId>
<artifactId>lazy4j</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@
* @param <TYPE>
* the raw Java type this adapter supports
* @param <ITEM_TYPE>
* 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<TYPE, ITEM_TYPE extends IAnyAtomicItem>
implements IDataTypeAdapter<TYPE> {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* The maintained byte stream is kept in a decoded form.
*
* @param <ITEM_TYPE>
* the metapath item type supported by the adapter
*/
public abstract class AbstractBinaryAdapter<ITEM_TYPE extends IAnyAtomicItem>
extends AbstractDataTypeAdapter<ByteBuffer, ITEM_TYPE> {

/**
* 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<ITEM_TYPE> itemClass,
@NonNull AbstractAtomicOrUnionType.ICastExecutor<ITEM_TYPE> 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.
* <p>
* 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,152 +5,63 @@

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.
* <p>
* Provides support for the Metaschema
* <a href= "https://pages.nist.gov/metaschema/specification/datatypes/#base64">base64</a> data
* type.
* Provides support for the Metaschema <a href=
* "https://pages.nist.gov/metaschema/specification/datatypes/#BASE64">BASE64</a>
* data type.
*/
public class Base64Adapter
extends AbstractDataTypeAdapter<ByteBuffer, IBase64BinaryItem> {
extends AbstractBinaryAdapter<IBase64BinaryItem> {
@NonNull
private static final List<IEnhancedQName> NAMES = ObjectUtils.notNull(
List.of(
EQNameFactory.instance().newQName(MetapathConstants.NS_METAPATH, "base64"),
// 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<IEnhancedQName> 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<IEnhancedQName> getNames() {
return NAMES;
}

@Override
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;
}
}
Loading

0 comments on commit 0c8eafb

Please sign in to comment.