Skip to content
Permalink
Browse files

Provide an interface for extended attributes

- Provided a reference implementation PersistenceStateAttribute
- Moved tests to PersistenceAttributeTest
  • Loading branch information...
ZacBlanco committed May 16, 2019
1 parent 0ee05f2 commit 709e582857cb71f3c772f235ac0f057fe174802b
@@ -11,9 +11,6 @@

package alluxio.master.file.meta;

import java.util.ArrayList;
import java.util.List;

import javax.annotation.concurrent.ThreadSafe;

/**
@@ -25,59 +22,4 @@
TO_BE_PERSISTED, // the file is to be persisted in the under FS
PERSISTED, // the file is persisted in the under FS
LOST // the file is lost but not persisted in the under FS
;

/**
* Encode a state into a single byte.
*
* @return the state encoded as a byte
*/
public byte encode() {
return (byte) ordinal();
}

/**
* Encode a list of states into a string.
*
* @param states The states to encode
* @return A string where each char represents a state with input ordering preserved
*/
public static String encode(List<PersistenceState> states) {
byte[] chars = new byte[states.size()];
for (int i = 0; i < states.size(); i++) {
chars[i] = states.get(i).encode();
}
return new String(chars);
}

/**
* Decode a string of encoded states back into a list.
*
* @param encoded the string with states encoded as chars
* @return the list of states as PersistenceState values with ordering preserved
*/
public static List<PersistenceState> decode(String encoded) {
List<PersistenceState> states = new ArrayList<>(encoded.length());
for (int i = 0; i < encoded.length(); i++) {
// use charAt to avoid creating a copy of the underlying byte[]
states.add(i, decode((byte) encoded.charAt(i)));
}
return states;
}

/**
* Decode a single byte of data into a PersistenceState.
*
* @param state the byte to decode
* @return the PersistenceState which the byte represents
*/
public static PersistenceState decode(byte state) {
int loc = state & 0xFF;
if (loc < PersistenceState.values().length) {
return PersistenceState.values()[loc];
} else {
throw new IllegalArgumentException(
String.format("Unable to decode PersistenceState. Decoded value is invalid %d", loc));
}
}
}

This file was deleted.

@@ -15,10 +15,7 @@
* This enum is used to define constant values for extended attribute keys on inodes within
* the inode tree.
*
*
*
* Extended attributes take the form of
* {@code {NAMESPACE}{SEPARATOR}{ATTRIBUTE_name}}
* Extended attributes take the form of {@code {NAMESPACE}{SEPARATOR}{ATTRIBUTE_NAME}}
*
* For example, the persistence state uses the default {@link NamespacePrefix#SYSTEM} prefix and
* appends {@code ps} (an acronym for persistence state) so that the attribute when stored is
@@ -0,0 +1,106 @@
package alluxio.master.file.meta.xattr;

import com.google.common.base.Preconditions;
import com.google.protobuf.ByteString;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
* Extend this class to implement any extended attributes.
*
* Any attributes which extend this class get a default implementation for multiEncode and
* multiDecode interface methods.
*
* @param <T> The class which the attribute should encode from, and decode to
*/
public abstract class AbstractExtendedAttribute<T> implements ExtendedAttribute<T> {
private static final String SEPARATOR = ".";

private final NamespacePrefix mPrefix;
private final String mIdentifier;
private final String mFullName;
private final int mEncodedSize;

AbstractExtendedAttribute(NamespacePrefix prefix, String identifier, int encodedSize) {
mPrefix = prefix;
mIdentifier = identifier;
mFullName = buildAttr(prefix.toString(), identifier);
Preconditions.checkArgument(encodedSize > 0, "encoding size");
mEncodedSize = encodedSize;
}

@Override
public String getName() {
return mFullName;
}

@Override
public String getNamespace() {
return mPrefix.toString();
}

@Override
public String getIdentifier() {
return mIdentifier;
}

@Override
public int getEncodedSize() {
return mEncodedSize;
}

@Override
public ByteString multiEncode(List<T> objects) {
byte[] b = new byte[objects.size() * mEncodedSize];
int offset = 0;
for (T obj: objects) {
encode(obj).copyTo(b, offset);
offset += mEncodedSize;
}
return ByteString.copyFrom(b);
}

@Override
public List<T> multiDecode(ByteString bytes) throws IOException {
if (bytes.size() % mEncodedSize != 0) {
throw new IOException("Cannot decode attribute. Byte array is not a multiple of encoding "
+ "size");
}
int numObjects = bytes.size() / mEncodedSize;
ArrayList<T> obj = new ArrayList<>(numObjects);
byte[] tmp = new byte[mEncodedSize];
for (int i = 0; i < numObjects; i++) {
bytes.copyTo(tmp, i * mEncodedSize, 0, mEncodedSize);
obj.add(decode(ByteString.copyFrom(tmp)));
}
return obj;
}

/**
* Builds an attribute by joining namespace components together.
*
* @param components the components to join
* @return the attribute joined by {@link #SEPARATOR}
*/
private static String buildAttr(String... components) {
return String.join(SEPARATOR, components);
}

enum NamespacePrefix {
SYSTEM("s"),
USER("u"),
;

private final String mPrefixName;
NamespacePrefix(String name) {
mPrefixName = name;
}

@Override
public String toString() {
return mPrefixName;
}
}
}
@@ -0,0 +1,75 @@
package alluxio.master.file.meta.xattr;

import com.google.protobuf.ByteString;

import java.io.IOException;
import java.util.List;

/**
* This class defines an interface for implementing an extended attribute which can be stored on
* inodes.
*
* Attributes are stored on inodes as a simple array of bytes ({@link ByteString}) and can be
* the implementation for encoding/decoding
* @param <T>
*/
public interface ExtendedAttribute<T> {

PersistenceStateAttribute PERSISTENCE_STATE = new PersistenceStateAttribute();

/**
* @return the full attribute with namespace and identifier
*/
String getName();

/**
* @return the namespace of the attribute lies within
*/
String getNamespace();

/**
* @return the identifier within the namespace of the attribute
*/
String getIdentifier();

/**
* @return the number of bytes it takes to encode a single object
*/
int getEncodedSize();

/**
* Encode a single object into a byte string.
*
* @param object the object to encode
* @return the byte representation of the object
*/
ByteString encode(T object);

/**
* Decode an object from a single byte string.
*
* @param bytes the bytes to decode into a single instance of
* @return the instance of the decoded object
* @throws IOException if the size of the byte string isn't equal to the encoding length
*/
T decode(ByteString bytes) throws IOException;

/**
* Encode a list of objects into a byte string.
*
* @param objects the objects to encode
* @return a bytestring with a size equal to {@link #getEncodedSize()} * {@code objects.size()}
*/
ByteString multiEncode(List<T> objects);

/**
* Decode a byte string into a list of objects, preserving ordering.
*
* The byte string size must be a multiple of {@link #getEncodedSize()}
*
* @param bytes the bytes to decode
* @return a list of the decoded objects, preserving ordering
* @throws IOException if decoding any of the objects fails
*/
List<T> multiDecode(ByteString bytes) throws IOException;
}
@@ -0,0 +1,32 @@
package alluxio.master.file.meta.xattr;

import alluxio.master.file.meta.PersistenceState;

import com.google.protobuf.ByteString;

import java.io.IOException;

/**
* An implementation of an extended attribute for {@link PersistenceState}.
*/
public class PersistenceStateAttribute extends AbstractExtendedAttribute<PersistenceState> {

PersistenceStateAttribute() {
super(NamespacePrefix.SYSTEM, "ps",
(int) Math.ceil(Math.log((double) PersistenceState.values().length) / 8));
}

@Override
public ByteString encode(PersistenceState state) {
return ByteString.copyFrom(new byte[]{(byte) state.ordinal()});
}

@Override
public PersistenceState decode(ByteString bytes) throws IOException {
if (bytes.size() > 1) {
throw new IOException("Unable to convert bytes to persistenceState");
}
int loc = bytes.byteAt(0) & 0xFF;
return PersistenceState.values()[loc];
}
}
@@ -0,0 +1,37 @@
package alluxio.master.file.meta.xattr;

import static alluxio.master.file.meta.xattr.ExtendedAttribute.PERSISTENCE_STATE;
import static org.junit.Assert.assertEquals;

import alluxio.master.file.meta.PersistenceState;

import com.google.protobuf.ByteString;
import org.junit.Test;

import java.util.Arrays;
import java.util.List;

public class PersistenceAttributeTest {

@Test
public void testSingleEncode() throws Exception {
for (int i = 0; i < PersistenceState.values().length; i++) {
PersistenceState state = PersistenceState.values()[i];
assertEquals(state, PERSISTENCE_STATE.decode(
PERSISTENCE_STATE.encode(state)));
}
}

@Test
public void testMultiEncode() throws Exception {
PersistenceState[] states = { PersistenceState.TO_BE_PERSISTED,
PersistenceState.TO_BE_PERSISTED, PersistenceState.PERSISTED, PersistenceState.LOST,
PersistenceState.NOT_PERSISTED, PersistenceState.LOST, PersistenceState.PERSISTED};
ByteString encoded = PERSISTENCE_STATE.multiEncode(Arrays.asList(states));
assertEquals(states.length, encoded.size() / PERSISTENCE_STATE.getEncodedSize());
List<PersistenceState> decoded = PERSISTENCE_STATE.multiDecode(encoded);
for (int i = 0; i < states.length; i++) {
assertEquals(states[i], decoded.get(i));
}
}
}

0 comments on commit 709e582

Please sign in to comment.
You can’t perform that action at this time.