/* * 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.appenders.oraclenosql.appender; import oracle.kv.FaultException; import oracle.kv.KVSecurityConstants; import oracle.kv.KVStore; import oracle.kv.KVStoreConfig; import oracle.kv.KVStoreFactory; import oracle.kv.KVVersion; import oracle.kv.Key; import oracle.kv.Operation; import oracle.kv.OperationFactory; import oracle.kv.avro.AvroCatalog; import oracle.kv.avro.GenericAvroBinding; import oracle.kv.impl.api.avro.AvroDdl; import oracle.kv.impl.api.avro.AvroDdl.AddSchemaOptions; import oracle.kv.impl.api.avro.AvroDdl.AddSchemaResult; import oracle.kv.impl.api.avro.AvroDdl.SchemaDetails; import oracle.kv.impl.api.avro.AvroDdl.SchemaSummary; import oracle.kv.impl.api.avro.AvroSchemaMetadata; import oracle.kv.impl.api.avro.AvroSchemaStatus; import org.apache.avro.Schema; import org.apache.avro.generic.GenericData; import org.apache.avro.generic.GenericDatumReader; import org.apache.avro.generic.GenericRecord; import org.apache.avro.io.BinaryDecoder; import org.apache.avro.io.DatumReader; import org.apache.avro.io.DecoderFactory; import org.kaaproject.kaa.common.dto.logs.LogAppenderDto; import org.kaaproject.kaa.server.appenders.oraclenosql.config.gen.KvStoreNode; import org.kaaproject.kaa.server.appenders.oraclenosql.config.gen.OracleNoSqlConfig; import org.kaaproject.kaa.server.common.log.shared.RecordWrapperSchemaGenerator; import org.kaaproject.kaa.server.common.log.shared.appender.AbstractLogAppender; import org.kaaproject.kaa.server.common.log.shared.appender.LogDeliveryCallback; import org.kaaproject.kaa.server.common.log.shared.appender.LogEvent; import org.kaaproject.kaa.server.common.log.shared.appender.LogEventPack; 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.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Properties; import java.util.SortedMap; public class OracleNoSqlLogAppender extends AbstractLogAppender<OracleNoSqlConfig> { private static final Logger LOG = LoggerFactory.getLogger(OracleNoSqlLogAppender.class); private boolean closed = false; private KVStore kvStore; private String username; private GenericAvroBinding binding; private GenericRecord wrapperRecord; private BinaryDecoder binaryDecoder; private DatumReader<GenericRecord> datumReader; public OracleNoSqlLogAppender() { super(OracleNoSqlConfig.class); } private static String getHostName() { try { return InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException ex) { return ""; } } @Override public void doAppend(LogEventPack logEventPack, RecordHeader header, LogDeliveryCallback listener) { if (!closed) { if (kvStore != null) { LOG.debug("[{}] appending {} logs to Oracle NoSQL kvStore", this.getApplicationToken(), logEventPack.getEvents().size()); try { doAppendGenericAvro(logEventPack, header); listener.onSuccess(); } catch (FaultException ex) { LOG.error("Unable to append logs due to remote exception!", ex); listener.onRemoteError(); } catch (Exception ex) { LOG.error("Unable to append logs!", ex); listener.onInternalError(); } } else { LOG.info("[{}] Attempted to append to empty kvStore.", getName()); listener.onInternalError(); } } else { LOG.info("[{}] Attempted to append to closed appender.", getName()); listener.onInternalError(); } } @Override protected void initFromConfiguration(LogAppenderDto appender, OracleNoSqlConfig configuration) { try { kvStore = initKvStore(configuration); } catch (Exception ex) { LOG.error("Failed to init kvStore: ", ex); } } private void doAppendGenericAvro(LogEventPack logEventPack, RecordHeader header) throws Exception { if (binding == null) { initialize(logEventPack); } GenericRecord recordData = null; OperationFactory of = kvStore.getOperationFactory(); ArrayList<Operation> opList = new ArrayList<Operation>(); List<String> majorPath = Arrays.asList(getApplicationToken(), logEventPack.getLogSchema().getVersion() + "", logEventPack.getEndpointKey(), System.currentTimeMillis() + ""); int counter = 0; for (LogEvent event : logEventPack.getEvents()) { binaryDecoder = DecoderFactory.get().binaryDecoder(event.getLogData(), binaryDecoder); try { recordData = datumReader.read(recordData, binaryDecoder); } catch (IOException ex) { LOG.error("[{}] Unable to read log event!", ex); throw ex; } wrapperRecord.put(RecordWrapperSchemaGenerator.RECORD_HEADER_FIELD, header); wrapperRecord.put(RecordWrapperSchemaGenerator.RECORD_DATA_FIELD, recordData); Key key = Key.createKey(majorPath, Arrays.asList((counter++) + "")); opList.add(of.createPut(key, binding.toValue(wrapperRecord))); } kvStore.execute(opList); } private void initialize(LogEventPack logEventPack) throws Exception { try { Schema recordWrapperSchema = RecordWrapperSchemaGenerator.generateRecordWrapperSchema(logEventPack.getLogSchema().getSchema()); checkSchemaUploaded(recordWrapperSchema); AvroCatalog avroCatalog = kvStore.getAvroCatalog(); binding = avroCatalog.getGenericBinding(recordWrapperSchema); Schema userSchema = new Schema.Parser().parse(logEventPack.getLogSchema().getSchema()); datumReader = new GenericDatumReader<GenericRecord>(userSchema); wrapperRecord = new GenericData.Record(recordWrapperSchema); } catch (Exception ex) { LOG.error("[{}] Unable to initialize parameters for log event pack.", getName()); throw ex; } } private void checkSchemaUploaded(Schema schema) { String schemaText = schema.toString(true); AvroDdl avroDdl = new AvroDdl(kvStore); SortedMap<String, SchemaSummary> schemaSummaries = avroDdl.getSchemaSummaries(false); boolean uploaded = false; boolean evolve = schemaSummaries.containsKey(schema.getFullName()); if (evolve) { for (String key : schemaSummaries.keySet()) { SchemaSummary summary = schemaSummaries.get(key); if (checkSchemaUploaded(avroDdl, summary, schemaText)) { uploaded = true; break; } } } if (!uploaded) { AvroSchemaMetadata metadata = new AvroSchemaMetadata(AvroSchemaStatus.ACTIVE, System.currentTimeMillis(), username, getHostName()); AddSchemaOptions options = new AddSchemaOptions(evolve, true); AddSchemaResult result = avroDdl.addSchema(metadata, schemaText, options, KVVersion.CURRENT_VERSION); LOG.info("[{}] Uploaded new schema to store, extra message [{}].", getName(), result.getExtraMessage()); } } private boolean checkSchemaUploaded(AvroDdl avroDdl, SchemaSummary summary, String schemaText) { SchemaDetails details = avroDdl.getSchemaDetails(summary.getId()); if (details.getText().equals(schemaText)) { return true; } else if (summary.getPreviousVersion() != null) { return checkSchemaUploaded(avroDdl, summary.getPreviousVersion(), schemaText); } return false; } @Override public void close() { if (!closed) { closed = true; if (kvStore != null) { kvStore.close(); kvStore = null; } } LOG.debug("Stopped Oracle NoSQL log appender."); } private KVStore initKvStore(OracleNoSqlConfig configuration) throws Exception { List<KvStoreNode> kvStoreNodes = configuration.getKvStoreNodes(); String[] helperHostPorts = new String[kvStoreNodes.size()]; for (int i = 0; i < kvStoreNodes.size(); i++) { KvStoreNode node = kvStoreNodes.get(i); helperHostPorts[i] = node.getHost() + ":" + node.getPort(); } Properties securityProperties = new Properties(); if (configuration.getUsername() != null) { username = configuration.getUsername(); securityProperties.put(KVSecurityConstants.AUTH_USERNAME_PROPERTY, configuration.getUsername()); } else { username = ""; } if (configuration.getWalletDir() != null) { securityProperties.put(KVSecurityConstants.AUTH_WALLET_PROPERTY, configuration.getWalletDir()); } if (configuration.getPwdFile() != null) { securityProperties.put(KVSecurityConstants.AUTH_PWDFILE_PROPERTY, configuration.getPwdFile()); } if (configuration.getSecurityFile() != null) { securityProperties.put(KVSecurityConstants.SECURITY_FILE_PROPERTY, configuration.getSecurityFile()); } if (configuration.getTransport() != null) { securityProperties.put(KVSecurityConstants.TRANSPORT_PROPERTY, configuration.getTransport()); } if (configuration.getSsl() != null) { securityProperties.put(KVSecurityConstants.SSL_TRANSPORT_NAME, configuration.getSsl()); } if (configuration.getSslCipherSuites() != null) { securityProperties.put(KVSecurityConstants.SSL_CIPHER_SUITES_PROPERTY, configuration.getSslCipherSuites()); } if (configuration.getSslProtocols() != null) { securityProperties.put(KVSecurityConstants.SSL_PROTOCOLS_PROPERTY, configuration.getSslProtocols()); } if (configuration.getSslHostnameVerifier() != null) { securityProperties.put(KVSecurityConstants.SSL_HOSTNAME_VERIFIER_PROPERTY, configuration.getSslHostnameVerifier()); } if (configuration.getSslTrustStore() != null) { securityProperties.put(KVSecurityConstants.SSL_TRUSTSTORE_FILE_PROPERTY, configuration.getSslTrustStore()); } if (configuration.getSslTrustStoreType() != null) { securityProperties.put(KVSecurityConstants.SSL_TRUSTSTORE_TYPE_PROPERTY, configuration.getSslTrustStoreType()); } KVStoreConfig config = new KVStoreConfig(configuration.getStoreName(), helperHostPorts); config.setSecurityProperties(securityProperties); KVStore kvStore = KVStoreFactory.getStore(config); return kvStore; } }