/* * 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.log.shared.appender; import org.apache.avro.generic.GenericRecord; import org.apache.avro.specific.SpecificRecordBase; import org.kaaproject.kaa.common.avro.AvroByteArrayConverter; import org.kaaproject.kaa.common.avro.GenericAvroConverter; import org.kaaproject.kaa.common.dto.logs.LogAppenderDto; import org.kaaproject.kaa.common.dto.logs.LogEventDto; import org.kaaproject.kaa.common.dto.logs.LogHeaderStructureDto; import org.kaaproject.kaa.server.common.log.shared.avro.gen.RecordHeader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public abstract class AbstractLogAppender<T extends SpecificRecordBase> implements LogAppender { private static final Logger LOG = LoggerFactory.getLogger(AbstractLogAppender.class); private static final int LOG_HEADER_VERSION = 1; private final Class<T> configurationClass; private Map<String, GenericAvroConverter<GenericRecord>> converters = new HashMap<>(); private String appenderId; private String name; private String applicationToken; private List<LogHeaderStructureDto> header; private int minSchemaVersion; private int maxSchemaVersion; private boolean confirmDelivery; public AbstractLogAppender(Class<T> configurationClass) { this.configurationClass = configurationClass; } /** * Log in <code>LogAppender</code> specific way. * * @param logEventPack the pack of Log Events * @param header the header * @param listener the listener */ public abstract void doAppend(LogEventPack logEventPack, RecordHeader header, LogDeliveryCallback listener); @Override public void doAppend(LogEventPack logEventPack, LogDeliveryCallback listener) { if (logEventPack != null) { doAppend(logEventPack, generateHeader(logEventPack), listener); } else { LOG.warn("Can't append log events. LogEventPack object is null."); } } /** * Change parameters of log appender. * * @param appender the appender * @param configuration the configuration */ protected abstract void initFromConfiguration(LogAppenderDto appender, T configuration); /** * Change parameters of log appender. * * @param appender the appender */ public void initLogAppender(LogAppenderDto appender) { this.minSchemaVersion = appender.getMinLogSchemaVersion(); this.maxSchemaVersion = appender.getMaxLogSchemaVersion(); this.confirmDelivery = appender.isConfirmDelivery(); byte[] rawConfiguration = appender.getRawConfiguration(); try { AvroByteArrayConverter<T> converter = new AvroByteArrayConverter<>(configurationClass); T configuration = converter.fromByteArray(rawConfiguration); initFromConfiguration(appender, configuration); } catch (IOException ex) { LOG.error("Unable to parse configuration for appender '" + getName() + "'", ex); } } @Override public String getName() { return name; } @Override public void setName(String name) { this.name = name; } @Override public String getAppenderId() { return appenderId; } @Override public void setAppenderId(String appenderId) { this.appenderId = appenderId; } /** * Gets the application token. * * @return the applicationToken */ public String getApplicationToken() { return applicationToken; } @Override public void setApplicationToken(String applicationToken) { this.applicationToken = applicationToken; } /** * Gets the header. * * @return the header */ public List<LogHeaderStructureDto> getHeader() { return header; } /** * Sets the header. * * @param header the new header */ public void setHeader(List<LogHeaderStructureDto> header) { this.header = header; } @Override public void init(LogAppenderDto appender) { this.header = appender.getHeaderStructure(); initLogAppender(appender); } @Override public boolean isSchemaVersionSupported(int version) { return minSchemaVersion <= version && version <= maxSchemaVersion; } @Override public boolean isDeliveryConfirmationRequired() { return confirmDelivery; } /** * Generate log event. * * @param logEventPack the log event pack * @param header the header * @return the list * @throws IOException the io exception */ protected List<LogEventDto> generateLogEvent(LogEventPack logEventPack, RecordHeader header) throws IOException { LOG.debug("Generate LogEventDto objects from LogEventPack [{}] and header [{}]", logEventPack, header); List<LogEventDto> events = new ArrayList<>(logEventPack.getEvents().size()); GenericAvroConverter<GenericRecord> eventConverter = getConverter( logEventPack.getLogSchema().getSchema()); GenericAvroConverter<GenericRecord> headerConverter = getConverter( header.getSchema().toString()); try { for (LogEvent logEvent : logEventPack.getEvents()) { LOG.debug("Convert log events [{}] to dto objects.", logEvent); if (logEvent == null || logEvent.getLogData() == null) { continue; } LOG.trace("Avro record converter [{}] with log data [{}]", eventConverter, logEvent.getLogData()); GenericRecord decodedLog = eventConverter.decodeBinary(logEvent.getLogData()); LOG.trace("Avro header record converter [{}]", headerConverter); String encodedJsonLogHeader = headerConverter.encodeToJson(header); String encodedJsonLog = eventConverter.encodeToJson(decodedLog); events.add(new LogEventDto(encodedJsonLogHeader, encodedJsonLog)); } } catch (IOException ex) { LOG.error("Unexpected IOException while decoding LogEvents", ex); throw ex; } return events; } /** * Gets the converter. * * @param schema the schema * @return the converter */ private GenericAvroConverter<GenericRecord> getConverter(String schema) { LOG.trace("Get converter for schema [{}]", schema); GenericAvroConverter<GenericRecord> genAvroConverter = converters.get(schema); if (genAvroConverter == null) { LOG.trace("Create new converter for schema [{}]", schema); genAvroConverter = new GenericAvroConverter<>(schema); converters.put(schema, genAvroConverter); } LOG.trace("Get converter [{}] from map.", genAvroConverter); return genAvroConverter; } /** * Generate header. * * @param logEventPack the log event pack * @return the log header */ private RecordHeader generateHeader(LogEventPack logEventPack) { RecordHeader logHeader = null; if (header != null) { logHeader = new RecordHeader(); for (LogHeaderStructureDto field : header) { switch (field) { case KEYHASH: logHeader.setEndpointKeyHash(logEventPack.getEndpointKey()); break; case TIMESTAMP: logHeader.setTimestamp(System.currentTimeMillis()); break; case TOKEN: logHeader.setApplicationToken(applicationToken); break; case VERSION: logHeader.setHeaderVersion(LOG_HEADER_VERSION); break; case LSVERSION: logHeader.setLogSchemaVersion(logEventPack.getLogSchema().getVersion()); break; default: if (LOG.isWarnEnabled()) { LOG.warn("Current header field [{}] doesn't support", field); } break; } } } return logHeader; } }