/*
* 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.client.configuration.manager;
import org.apache.avro.Schema;
import org.apache.avro.Schema.Field;
import org.apache.avro.generic.GenericArray;
import org.apache.avro.generic.GenericEnumSymbol;
import org.apache.avro.generic.GenericFixed;
import org.apache.avro.generic.GenericRecord;
import org.kaaproject.kaa.client.common.AvroGenericUtils;
import org.kaaproject.kaa.client.common.CommonFactory;
import org.kaaproject.kaa.client.common.CommonRecord;
import org.kaaproject.kaa.client.common.CommonValue;
import org.kaaproject.kaa.client.common.DefaultCommonFactory;
import org.kaaproject.kaa.client.configuration.ConfigurationProcessedObserver;
import org.kaaproject.kaa.client.configuration.GenericDeltaReceiver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* Default @{link ConfigurationManager} implementation.
*
* @author Yaroslav Zeygerman
*/
public class DefaultConfigurationManager implements GenericDeltaReceiver, ConfigurationManager,
ConfigurationProcessedObserver {
private static final Logger LOG = LoggerFactory.getLogger(DefaultConfigurationManager.class);
private static final String UUID = "__uuid";
private final CommonFactory commonFactory = new DefaultCommonFactory();
private final Map<UUID, CommonRecord> records = new HashMap<UUID, CommonRecord>();
private final List<ConfigurationReceiver> subscribers = new LinkedList<ConfigurationReceiver>();
private CommonRecord rootRecord;
public DefaultConfigurationManager() {
}
private CommonRecord createCommonRecord(GenericRecord avroRecord) {
GenericFixed uuidFixed = (GenericFixed) avroRecord.get(UUID);
if (uuidFixed != null) {
UUID uuid = AvroGenericUtils.createUuidFromFixed(uuidFixed);
CommonRecord newRecord = commonFactory.createCommonRecord(uuid, avroRecord.getSchema());
records.put(uuid, newRecord);
return newRecord;
} else {
return commonFactory.createCommonRecord(avroRecord.getSchema());
}
}
private void processRecordField(
CommonRecord record, GenericRecord deltaRecord, String fieldName) {
CommonRecord nextRecord = null;
CommonValue nextValue = record.getField(fieldName);
if (nextValue != null
&& nextValue.isRecord()
&& nextValue.getRecord().getSchema().getFullName()
.equals(deltaRecord.getSchema().getFullName())) {
nextRecord = nextValue.getRecord();
GenericFixed uuidFixed = (GenericFixed) deltaRecord.get(UUID);
if (uuidFixed != null) {
UUID uuid = AvroGenericUtils.createUuidFromFixed(uuidFixed);
// Checking if the uuid was changed
if (!uuid.equals(nextRecord.getUuid())) {
records.remove(nextRecord.getUuid());
records.put(uuid, nextRecord);
nextRecord.setUuid(uuid);
}
}
} else {
nextRecord = createCommonRecord(deltaRecord);
record.setField(fieldName, commonFactory.createCommonValue(nextRecord));
}
updateRecord(nextRecord, deltaRecord);
}
private void processArrayField(CommonRecord record, GenericArray array, String fieldName) {
List<CommonValue> currentArray;
CommonValue arrayValue = record.getField(fieldName);
if (arrayValue != null && arrayValue.isArray()) {
currentArray = arrayValue.getArray().getList();
} else {
currentArray = new LinkedList<CommonValue>();
record.setField(fieldName, commonFactory.createCommonValue(
commonFactory.createCommonArray(array.getSchema(), currentArray)));
}
if (!array.isEmpty()) {
Object rawItem = array.get(0);
if (AvroGenericUtils.isRecord(rawItem)) {
GenericArray<GenericRecord> recordItems = (GenericArray<GenericRecord>) array;
// Adding new records
for (GenericRecord item : recordItems) {
CommonRecord newRecord = createCommonRecord(item);
updateRecord(newRecord, item);
currentArray.add(commonFactory.createCommonValue(newRecord));
}
} else if (AvroGenericUtils.isFixed(rawItem)) {
GenericArray<GenericFixed> fixedItems = (GenericArray<GenericFixed>) array;
if (AvroGenericUtils.isUuid(rawItem)) {
// Removing items with given uuids
for (GenericFixed item : fixedItems) {
UUID currentUuid = AvroGenericUtils.createUuidFromFixed(item);
Iterator<CommonValue> valueIt = currentArray.iterator();
while (valueIt.hasNext()) {
CommonRecord currentRecord = valueIt.next().getRecord();
if (currentRecord.getUuid().equals(currentUuid)) {
valueIt.remove();
records.remove(currentUuid);
break;
}
}
}
} else {
for (GenericFixed item : fixedItems) {
currentArray.add(commonFactory.createCommonValue(
commonFactory.createCommonFixed(item.getSchema(), item.bytes())));
}
}
} else {
// Adding new primitive items
for (Object item : array) {
currentArray.add(commonFactory.createCommonValue(item));
}
}
}
}
private void processEnumField(CommonRecord record, GenericEnumSymbol symbol, String fieldName) {
Schema enumSchema = symbol.getSchema();
if (AvroGenericUtils.isReset(symbol)) {
record.getField(fieldName).getArray().getList().clear();
} else if (!AvroGenericUtils.isUnchanged(symbol)) {
record.setField(fieldName, commonFactory.createCommonValue(
commonFactory.createCommonEnum(enumSchema, symbol.toString())));
}
}
private void processFixedField(CommonRecord record, GenericFixed fixed, String fieldName) {
record.setField(fieldName, commonFactory.createCommonValue(
commonFactory.createCommonFixed(fixed.getSchema(), fixed.bytes())));
}
private void updateRecord(CommonRecord record, GenericRecord delta) {
List<Field> deltaFields = delta.getSchema().getFields();
for (Field deltaField : deltaFields) {
String fieldName = deltaField.name();
Object rawDeltaField = delta.get(fieldName);
if (LOG.isDebugEnabled()) {
LOG.debug("Processing field \"{}\", current value: {}",
fieldName, record.getField(fieldName) != null ? record
.getField(fieldName).toString() : null);
}
if (AvroGenericUtils.isRecord(rawDeltaField)) {
processRecordField(record, (GenericRecord) rawDeltaField, fieldName);
} else if (AvroGenericUtils.isArray(rawDeltaField)) {
processArrayField(record, (GenericArray) rawDeltaField, fieldName);
} else if (AvroGenericUtils.isEnum(rawDeltaField)) {
processEnumField(record, (GenericEnumSymbol) rawDeltaField, fieldName);
} else if (AvroGenericUtils.isFixed(rawDeltaField)) {
processFixedField(record, (GenericFixed) rawDeltaField, fieldName);
} else {
record.setField(fieldName, commonFactory.createCommonValue(rawDeltaField));
}
}
}
@Override
public synchronized void onDeltaReceived(int index, GenericRecord data, boolean fullResync) {
GenericFixed uuidFixed = (GenericFixed) data.get(UUID);
UUID uuid = AvroGenericUtils.createUuidFromFixed(uuidFixed);
if (LOG.isDebugEnabled()) {
LOG.debug("Processing delta with uuid {}", uuidFixed.toString());
}
CommonRecord currentRecord = null;
if (!fullResync && records.containsKey(uuid)) {
currentRecord = records.get(uuid);
} else {
records.clear();
currentRecord = createCommonRecord(data);
rootRecord = currentRecord;
}
updateRecord(currentRecord, data);
}
@Override
public void onConfigurationProcessed() {
CommonRecord copyRecord = commonFactory.createCommonRecord(rootRecord);
synchronized (subscribers) {
for (ConfigurationReceiver receiver : subscribers) {
receiver.onConfigurationUpdated(copyRecord);
}
}
}
@Override
public void subscribeForConfigurationUpdates(ConfigurationReceiver receiver) {
synchronized (subscribers) {
if (receiver != null && !subscribers.contains(receiver)) {
subscribers.add(receiver);
}
}
}
@Override
public void unsubscribeFromConfigurationUpdates(ConfigurationReceiver receiver) {
synchronized (subscribers) {
if (receiver != null) {
subscribers.remove(receiver);
}
}
}
@Override
public synchronized CommonRecord getConfiguration() {
if (rootRecord != null) {
return commonFactory.createCommonRecord(rootRecord);
}
return null;
}
}