/*
* Copyright 2012 NGDATA nv
*
* 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.
*/
package org.lilyproject.repository.impl.compat;
import java.lang.reflect.Constructor;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.UUID;
import org.lilyproject.bytes.api.DataInput;
import org.lilyproject.bytes.impl.DataInputImpl;
import org.lilyproject.repository.api.IdGenerator;
import org.lilyproject.repository.api.Link;
import org.lilyproject.repository.api.RecordId;
import org.lilyproject.repository.impl.id.IdGeneratorImpl;
import org.lilyproject.repository.impl.id.UUIDRecordId;
import org.lilyproject.repository.impl.id.UserRecordId;
import org.lilyproject.repository.impl.id.VariantRecordId;
/**
* This class contains code to parse Lily <= 1.1 style record id's encoded as bytes.
*/
public class Lily11RecordIdDecoder {
private static final Constructor UUID_CONSTRUCTOR;
static {
try {
UUID_CONSTRUCTOR = UUIDRecordId.class.getDeclaredConstructor(UUID.class, IdGeneratorImpl.class);
UUID_CONSTRUCTOR.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
private static final Constructor USER_CONSTRUCTOR;
static {
try {
USER_CONSTRUCTOR = UserRecordId.class.getDeclaredConstructor(String.class, IdGeneratorImpl.class);
USER_CONSTRUCTOR.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
private static final Constructor VARIANT_CONSTRUCTOR;
static {
try {
VARIANT_CONSTRUCTOR = VariantRecordId.class.getDeclaredConstructor(RecordId.class, Map.class, IdGeneratorImpl.class);
VARIANT_CONSTRUCTOR.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
private Lily11RecordIdDecoder(IdGeneratorImpl idGenerator) {
}
private Lily11RecordIdDecoder() {
}
private static enum IdIdentifier {USER((byte)0), UUID((byte)1), VARIANT((byte)2);
private final byte identifierByte;
IdIdentifier(byte identifierByte) {
this.identifierByte = identifierByte;
}
public byte getIdentifierByte() {
return identifierByte;
}
}
private static final IdIdentifier[] IDENTIFIERS;
static {
IDENTIFIERS = new IdIdentifier[3];
IDENTIFIERS[0] = IdIdentifier.USER;
IDENTIFIERS[1] = IdIdentifier.UUID;
IDENTIFIERS[2] = IdIdentifier.VARIANT;
}
public static RecordId decode(DataInput dataInput, IdGenerator idGenerator) {
try {
// Read the identifier byte at the end of the input
int pos = dataInput.getPosition();
int size = dataInput.getSize();
dataInput.setPosition(size - 1);
byte identifierByte = dataInput.readByte();
dataInput.setPosition(pos);
// Virtually remove the identifier byte from the input
dataInput.setSize(size - 1);
IdIdentifier idIdentifier = IDENTIFIERS[identifierByte];
RecordId recordId = null;
switch (idIdentifier) {
case UUID:
UUID uuid = new UUID(dataInput.readLong(), dataInput.readLong());
recordId = (RecordId)UUID_CONSTRUCTOR.newInstance(uuid, idGenerator);
break;
case USER:
String basicRecordIdString = dataInput.readUTF();
checkIdString(basicRecordIdString, "record id");
recordId = (RecordId)USER_CONSTRUCTOR.newInstance(basicRecordIdString, idGenerator);
break;
case VARIANT:
int position = dataInput.getPosition();
int length = dataInput.getSize();
dataInput.setPosition(length - 8);
int nrOfVariants = dataInput.readInt();
int masterRecordIdLength = dataInput.readInt();
DataInput masterRecordIdInput = new DataInputImpl((DataInputImpl)dataInput, position, masterRecordIdLength);
RecordId masterRecordId = decode(masterRecordIdInput, idGenerator);
dataInput.setPosition(masterRecordIdLength);
SortedMap<String, String> varProps = new TreeMap<String, String>();
for (int i = 0; i < nrOfVariants; i++) {
String dimension = dataInput.readUTF();
String dimensionValue = dataInput.readUTF();
checkVariantPropertyNameValue(dimension);
checkVariantPropertyNameValue(dimensionValue);
varProps.put(dimension, dimensionValue);
}
recordId = (RecordId)VARIANT_CONSTRUCTOR.newInstance(masterRecordId, varProps, idGenerator);
break;
default:
// will already have failed on the IDENTIFIERS array lookup above
break;
}
return recordId;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected static void checkVariantPropertyNameValue(String text) {
checkIdString(text, "variant property name or value");
}
protected static void checkIdString(String text, String what) {
if (text.length() == 0) {
throw new IllegalArgumentException("Zero-length " + what + " is not allowed.");
}
if (Character.isWhitespace(text.charAt(0)) || Character.isWhitespace(text.charAt(text.length() - 1))) {
throw new IllegalArgumentException(what + " should not start or end with whitespace: \"" + text + "\".");
}
checkReservedCharacters(text);
}
protected static void checkReservedCharacters(String text) {
for (int i = 0; i < text.length(); i++) {
switch (text.charAt(i)) {
case '.':
case '=':
case ',':
throw new IllegalArgumentException("Reserved record id character (one of: . , =) in \"" +
text + "\".");
}
}
}
public static Link decodeLink(DataInput dataInput, IdGenerator idGenerator) {
// Format: see toBytes.
int recordIdLength = dataInput.readInt();
byte[] recordIdBytes = null;
if (recordIdLength > 0) {
recordIdBytes = dataInput.readBytes(recordIdLength);
}
String args = dataInput.readUTF();
if (recordIdLength == 0 && args == null) {
return new Link();
}
Link.LinkBuilder builder = Link.newBuilder();
if (recordIdLength > 0) {
RecordId id = decode(new DataInputImpl(recordIdBytes), idGenerator);
builder.recordId(id);
}
if (args != null && args.length() > 0) {
argsFromString(args, builder, args /* does not matter, should never be invalid */);
}
return builder.create();
}
private static void argsFromString(String args, Link.LinkBuilder builder, String link) {
String[] variantStringParts = args.split(",");
for (String part : variantStringParts) {
int eqPos = part.indexOf('=');
if (eqPos == -1) {
String thing = part.trim();
if (thing.equals("*")) {
// this is the default, but if users want to make explicit, allow them
builder.copyAll(true);
} else if (thing.equals("!*")) {
builder.copyAll(false);
} else if (thing.startsWith("+") && thing.length() > 1) {
builder.copy(thing.substring(1));
} else if (thing.startsWith("-") && thing.length() > 1) {
builder.remove(thing.substring(1));
} else {
throw new IllegalArgumentException("Invalid link: " + link);
}
} else {
String name = part.substring(0, eqPos).trim();
String value = part.substring(eqPos + 1).trim();
if (name.length() == 0 || value.length() == 0) {
throw new IllegalArgumentException("Invalid link: " + link);
}
builder.set(name, value);
}
}
}
}