/**
* Copyright 2014 Sunny Gleason and original author or 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
*
* 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 io.kazuki.v0.internal.v2schema;
import io.kazuki.v0.store.schema.model.Attribute;
import io.kazuki.v0.store.schema.model.TransformException;
import io.kazuki.v0.store.schema.model.Attribute.Type;
import io.kazuki.v0.store.schema.model.Schema;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
/**
* Validates schema instances for creation and upgrades. For validation of instances against a
* schema, use TypeCompaction.
*/
public class SchemaValidator {
private static final Pattern VALID_NAME_PATTERN = Pattern.compile("[a-zA-Z0-9_]+");
public static void validate(Schema schemaDefinition) throws TransformException {
for (Attribute attribute : schemaDefinition.getAttributes()) {
SchemaValidator.validateAttribute(attribute);
}
}
public static void validateUpgrade(Schema oldSchema, Schema newSchema) {
// validate existing attributes
for (Attribute oldAttr : oldSchema.getAttributes()) {
Attribute newAttr = newSchema.getAttribute(oldAttr.getName());
SchemaValidator.validateAttributeUpgrade(oldAttr, newAttr);
}
// tricky note: 'new' attributes may already exist in the entities;
// if those entities are incompatible, they will become irretrievable
}
public static void validateAttribute(Attribute attribute) {
String attrName = attribute.getName();
if (!VALID_NAME_PATTERN.matcher(attrName).matches()) {
throw new TransformException("Invalid attribute name : " + attrName);
}
if (attribute.getType().equals(Type.ENUM)) {
List<String> values = attribute.getValues();
if (values == null || values.size() < 1) {
throw new TransformException("Invalid enum attribute (contains no values) : " + attrName);
}
}
}
public static void validateAttributeUpgrade(Attribute oldAttr, Attribute newAttr) {
String name = oldAttr.getName();
Type oldType = oldAttr.getType();
Type newType = (newAttr == null) ? null : newAttr.getType();
switch (oldType) {
case ANY:
case ARRAY:
case MAP:
case BOOLEAN:
case CHAR_ONE:
assertAttributeTypeOneOf(name, newType, null, oldType);
break;
case ENUM:
assertAttributeTypeOneOf(name, newType, Type.ENUM);
assertEnumAttributeCompatible(oldAttr, newAttr);
break;
case I8:
assertAttributeTypeOneOf(name, newType, null, Type.I8, Type.I16, Type.I32, Type.I64);
break;
case I16:
assertAttributeTypeOneOf(name, newType, null, Type.I16, Type.I32, Type.I64);
break;
case I32:
assertAttributeTypeOneOf(name, newType, null, Type.I32, Type.I64);
break;
case I64:
assertAttributeTypeOneOf(name, newType, null, Type.I64);
break;
case U8:
assertAttributeTypeOneOf(name, newType, null, Type.U8, Type.U16, Type.U32, Type.U64);
break;
case U16:
assertAttributeTypeOneOf(name, newType, null, Type.U16, Type.U32, Type.U64);
break;
case U32:
assertAttributeTypeOneOf(name, newType, null, Type.U32, Type.U64);
break;
case U64:
assertAttributeTypeOneOf(name, newType, null, Type.U64);
break;
case UTC_DATE_SECS:
assertAttributeTypeOneOf(name, newType, null, Type.UTC_DATE_SECS, Type.I64);
break;
case UTF8_SMALLSTRING:
assertAttributeTypeOneOf(name, newType, null, Type.UTF8_SMALLSTRING, Type.UTF8_TEXT);
break;
case UTF8_TEXT:
assertAttributeTypeOneOf(name, newType, null, Type.UTF8_TEXT);
break;
}
}
public static void assertEnumAttributeCompatible(Attribute oldAttr, Attribute newAttr) {
if (newAttr == null) {
throw new TransformException("enum attributes, such as '" + oldAttr + "' may not be removed");
}
List<String> newValues = newAttr.getValues();
List<String> oldValues = oldAttr.getValues();
if (newValues == null || oldValues == null) {
throw new TransformException("enum attribute '" + oldAttr.getName() + "' has no values");
}
if (newValues.size() < oldValues.size()) {
throw new TransformException("may not remove enum values from '" + oldAttr.getName() + "', "
+ newValues + " has fewer items than " + oldValues);
}
for (int i = 0; i < oldValues.size(); i++) {
if (!oldValues.get(i).equals(newValues.get(i))) {
throw new TransformException("illegal enum values update for '" + oldAttr.getName() + "', "
+ newValues + " updates in the front or middle of " + oldValues);
}
}
}
public static void assertAttributeTypeOneOf(String attrName, Type theType, Type... possibles) {
for (Type candidate : possibles) {
if (theType == null && candidate == null) {
return;
}
if (theType != null && theType.equals(candidate)) {
return;
}
}
throw new TransformException("attribute '" + attrName + "' updated to '" + theType
+ "', must be one of " + Arrays.asList(possibles));
}
}