/*
* Copyright 2014-2016 CyberVision, Inc.
*
* 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.kaaproject.kaa.server.common.core.algorithms.validator;
import static org.kaaproject.kaa.server.common.core.algorithms.CommonConstants.UUID_FIELD;
import org.apache.avro.Schema;
import org.apache.avro.generic.GenericArray;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericFixed;
import org.apache.avro.generic.GenericRecord;
import org.kaaproject.kaa.common.avro.GenericAvroConverter;
import org.kaaproject.kaa.server.common.core.algorithms.AvroUtils;
import org.kaaproject.kaa.server.common.core.configuration.KaaData;
import org.kaaproject.kaa.server.common.core.configuration.KaaDataFactory;
import org.kaaproject.kaa.server.common.core.schema.KaaSchema;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class DefaultUuidValidator<U extends KaaSchema, T extends KaaData> implements
UuidValidator<T> {
/**
* The Constant LOG.
*/
private static final Logger LOG = LoggerFactory.getLogger(DefaultUuidValidator.class);
private final U schema;
private final KaaDataFactory<U, T> dataFactory;
private final Set<GenericFixed> processedUuids = new HashSet<>();
public DefaultUuidValidator(U schema, KaaDataFactory<U, T> factory) {
this.schema = schema;
this.dataFactory = factory;
}
private static boolean isRecordHaveUuid(GenericRecord record) {
if (record != null) {
Schema schema = record.getSchema();
return schema.getField(UUID_FIELD) != null;
}
return false;
}
@SuppressWarnings({"rawtypes"})
private static GenericRecord findRecordByUuid(GenericRecord rootRecord, Object uuid) {
if (rootRecord != null && uuid != null) {
if (isRecordHaveUuid(rootRecord)) {
Object uuidValue = rootRecord.get(UUID_FIELD);
if (uuid.equals(uuidValue)) {
return rootRecord;
}
}
List<Schema.Field> fields = rootRecord.getSchema().getFields();
if (fields != null && !fields.isEmpty()) {
for (Schema.Field field : fields) {
int position = field.pos();
Object value = rootRecord.get(position);
if (value instanceof GenericRecord) {
GenericRecord record = (GenericRecord) value;
return findRecordByUuid(record, uuid);
} else if (value instanceof GenericArray) {
GenericArray array = (GenericArray) value;
if (array != null) {
for (Object item : array) {
if (item instanceof GenericRecord) {
GenericRecord result = findRecordByUuid((GenericRecord) item, uuid);
if (result != null) {
return result;
}
}
}
}
}
}
}
}
return null;
}
private void generateUuidForRecord(GenericRecord record) {
GenericData.Fixed newUuid = AvroUtils.generateUuidObject();
LOG.trace("Generated new UUID {}", newUuid);
record.put(UUID_FIELD, newUuid);
processedUuids.add(newUuid);
}
private void copyUuid(GenericRecord destRecord, GenericRecord srcRecord) {
GenericData.Fixed uuid = (GenericData.Fixed) srcRecord.get(UUID_FIELD);
LOG.trace("Replacing with previous UUID {}", uuid);
destRecord.put(UUID_FIELD, uuid);
processedUuids.add(uuid);
}
/**
* Validating record's uuid field. If empty uuid fields exists in the record,
* the new values will be generated.
*
* @param currentRecord the current record.
* @param previousRecord the previous record.
* @param rootPreviousRecord the root previous record.
* @return the generic record with updated uuid fields.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
private GenericRecord validateRecord(GenericRecord currentRecord, GenericRecord previousRecord,
GenericRecord rootPreviousRecord) {
if (currentRecord != null) {
LOG.trace("Processing new record: {}, old record: {}", currentRecord, previousRecord);
if (isRecordHaveUuid(currentRecord)) {
GenericData.Fixed uuidValue = (GenericData.Fixed) currentRecord.get(UUID_FIELD);
if (uuidValue == null) {
if (previousRecord != null) {
copyUuid(currentRecord, previousRecord);
} else {
generateUuidForRecord(currentRecord);
}
} else {
LOG.trace("Validating existing UUID {}", uuidValue);
if (!processedUuids.contains(uuidValue)) {
if (previousRecord == null) {
GenericRecord validatingRecord = findRecordByUuid(rootPreviousRecord, uuidValue);
if (validatingRecord == null
|| !validatingRecord.getSchema().getFullName().equals(
currentRecord.getSchema().getFullName())) {
LOG.trace("Unknown UUID {}. Generating a new one", uuidValue);
generateUuidForRecord(currentRecord);
} else if (!uuidValue.equals(validatingRecord.get(UUID_FIELD))) {
copyUuid(currentRecord, validatingRecord);
} else {
LOG.trace("UUID {} is valid", uuidValue);
processedUuids.add(uuidValue);
}
} else if (!uuidValue.equals(previousRecord.get(UUID_FIELD))) {
copyUuid(currentRecord, previousRecord);
} else {
LOG.trace("UUID {} is valid", uuidValue);
processedUuids.add(uuidValue);
}
} else {
LOG.trace("UUID {} is already in use. Generating a new one", uuidValue);
generateUuidForRecord(currentRecord);
}
}
}
List<Schema.Field> fields = currentRecord.getSchema().getFields();
if (fields != null && !fields.isEmpty()) {
for (Schema.Field field : fields) {
int position = field.pos();
Object currentValue = currentRecord.get(position);
Object previousValue = null;
if (previousRecord != null) {
previousValue = previousRecord.get(position);
}
if (currentValue instanceof GenericRecord) {
GenericRecord subResult = null;
GenericRecord currentSubRecord = (GenericRecord) currentValue;
if (previousValue != null && previousValue instanceof GenericRecord) {
GenericRecord previousSubRecord = (GenericRecord) previousValue;
String currentName = currentSubRecord.getSchema().getFullName();
String previousName = previousSubRecord.getSchema().getFullName();
subResult = validateRecord(currentSubRecord,
currentName.equals(previousName) ? previousSubRecord : null, rootPreviousRecord);
} else {
subResult = validateRecord(currentSubRecord, null, rootPreviousRecord);
}
currentRecord.put(position, subResult);
} else if (currentValue instanceof GenericArray) {
LOG.trace("Found array value {}", currentValue);
GenericArray array = (GenericArray) currentValue;
if (array != null) {
int size = array.size();
for (int i = 0; i < size; i++) {
Object item = array.get(i);
if (item instanceof GenericRecord) {
GenericRecord itemRecord = (GenericRecord) item;
array.set(i, validateRecord(itemRecord, null, rootPreviousRecord));
}
}
currentRecord.put(position, array);
}
}
}
}
}
return currentRecord;
}
/* (non-Javadoc)
*/
@Override
public T validateUuidFields(T configurationToValidate, T previousConfiguration)
throws IOException {
processedUuids.clear();
String config = null;
GenericAvroConverter<GenericRecord> converter = new GenericAvroConverter<>(
schema.getRawSchema());
GenericRecord currentRecord = converter.decodeJson(configurationToValidate.getRawData());
GenericRecord previousRecord = null;
if (previousConfiguration != null) {
previousRecord = converter.decodeJson(previousConfiguration.getRawData());
}
validateRecord(currentRecord, previousRecord, previousRecord);
if (currentRecord != null) {
config = converter.encodeToJson(currentRecord);
}
LOG.trace("Generated uuid fields for records {}", currentRecord);
return dataFactory.createData(schema, config);
}
/* (non-Javadoc)
*/
@Override
public T validateUuidFields(GenericRecord configurationToValidate,
GenericRecord previousConfiguration) throws IOException {
processedUuids.clear();
String config = null;
GenericAvroConverter<GenericRecord> converter = new GenericAvroConverter<>(
schema.getRawSchema());
validateRecord(configurationToValidate, previousConfiguration, previousConfiguration);
if (configurationToValidate != null) {
config = converter.encodeToJson(configurationToValidate);
}
LOG.trace("Generated uuid fields for records {}", configurationToValidate);
return dataFactory.createData(schema, config);
}
}