All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nstream.adapter.common.ext.JetTranslator Maven / Gradle / Ivy

There is a newer version: 4.15.23
Show newest version
// Copyright 2015-2024 Nstream, inc.
//
// Licensed under the Redis Source Available License 2.0 (RSALv2) Agreement;
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://redis.com/legal/rsalv2-agreement/
//
// 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 nstream.adapter.common.ext;

import java.util.Map;
import java.util.Properties;
import nstream.adapter.common.AdapterSettings;
import swim.recon.Recon;
import swim.structure.Attr;
import swim.structure.Form;
import swim.structure.Item;
import swim.structure.Record;
import swim.structure.Slot;
import swim.structure.Value;
import swim.util.Log;

public abstract class JetTranslator {

  protected final String tag;

  public JetTranslator(String tag) {
    this.tag = tag;
  }

  /**
   * Transforms an nstream-jet input, represented as the union of a baseline
   * {@code T} and a {@code Properties}, into a finalized {@code T} and {@code
   * Properties}.
   *
   * 

Because this function returns only a {@code T}, a change in the {@code * Properties} is represented by modifying {@code unified} in-place. * * @param log the logger to use during translation * @param settings the baseline {@code T} that represents a portion of the * nstream-jet input * @param unified the {@code Properties} that contain the union of all {@link * nstream.adapter.common.provision.Provision Provision} configurations * relevant to the adapter, plus any relevant jet-specific * fields * * @return the finalized {@code T}, which may differ from {@code settings} * depending on the contents of {@code unified}. */ public abstract T translate(Log log, T settings, Properties unified); /** * Creates the structural representation of a {@code T} equivalent to a * provided {@code Properties}. * * @param log the logger to use during molding * @param p the {@code Properties} representation of the desired structure, * where keys may be in either * * @return the structural representation of the {@code T} as described by * {@code p} */ public abstract Record moldFromProperties(Log log, Properties p); protected final T translate(Log log, T settings, Form tForm, Properties unified) { final Value mold = tForm.mold(settings).toValue(); final Record moldRecord = mold instanceof Record ? (Record) mold : Record.create(); return tForm.cast(strip(negotiateSchemas(log, settings, moldRecord, unified))); } /** * Handles possibly conflicting information regarding content handling * elements of nstream-jet input. * *

For example, the {@code T} that nstream-jet yields may declare a {@link * nstream.adapter.common.ingress.ValueAssembler ValueAssembler} (e.g. {@code * nstream.adapter.avro.SwimAvroAssembler}) and a {@code contentTypeOverride} * (e.g. JSON) that do not make sense together. Use this method to define * "conflict" criteria and whether they should be elegantly handled or throw * {@code Exceptions}. * * @param log the logger to use during negotiation * @param settings the baseline {@code T} that represents a portion of the * nstream-jet input * @param mold a {@code Record} the structural representation of {@code * settings}, which may be modified in place * @param unified a singular {@code Properties} that represents the structure * for relevant {@code Provisions}, which will be updated * in-place if needed * * @return the content-pertaining conflict-free structure of the {@code T} * to be used, which is an in-place-modified {@code mold} * whenever possible and a newly-created {@code Record} otherwise */ protected abstract Record negotiateSchemas(Log log, T settings, Record mold, Properties unified); protected Record setAvroGenericRecordAssembler(Log log, Record settingsMold, String camelField, Value assembler, String replacee) { final Attr graAttr = Attr.of("valueAssembler", "nstream.adapter.avro.GenericRecordAssembler"); if (!(assembler instanceof Record)) { log.info("Inferred " + camelField + " as GenericRecordAssembler"); settingsMold = settingsMold.updatedSlot(camelField, Record.create(1).item(graAttr)); } else if (!(assembler.head().equals(graAttr))) { log.warn("Replaced " + camelField + " as GenericRecordAssembler from " + assembler.head() + " due to incompatibility with " + replacee); settingsMold = settingsMold.updatedSlot(camelField, Record.create(1).item(graAttr)); } return settingsMold; } protected Record setSwimAvroAssembler(Log log, Record settingsMold, String camelField, Value assembler, String avroSchema) { if (avroSchema == null || avroSchema.isEmpty()) { // TODO: more validation possible here log.warn("No avro schema found despite declared avro content type"); } final Record saaRecord = Record.create(2) .attr("valueAssembler", "nstream.adapter.avro.SwimAvroAssembler") .slot("schema", avroSchema); if (!(assembler instanceof Record)) { log.info("Inferred " + camelField + " as " + saaRecord); settingsMold = settingsMold.updatedSlot(camelField, saaRecord); } else { log.warn("Explicit valueAssembler " + assembler + " overrides schema " + saaRecord); } return settingsMold; } protected Record moldFromProperties(Log log, Properties p, Map map) { Record result = Record.create(8); result.attr(this.tag); for (Map.Entry entry : map.entrySet()) { final String k = entry.getKey(); result = entry.getValue().append(log, result, this.tag, k, p.getProperty(k, p.getProperty(camelToDot(k)))); } return result; } protected static String camelToDot(String camel) { if (camel == null || camel.isEmpty()) { throw new IllegalArgumentException("Input string cannot be null or empty"); } if (!isLowerCase(camel.charAt(0))) { throw new IllegalArgumentException("Input string must start with a lowercase character"); } final StringBuilder result = new StringBuilder(); for (int i = 0; i < camel.length(); i++) { final char c = camel.charAt(i); if (isUpperCase(c)) { result.append('.').append(c + 'a' - 'A'); } else if (isLowerCase(c)) { result.append(c); } else { throw new IllegalArgumentException("char " + c + " at idx " + i + " not camelCase-compatible"); } } return result.toString(); } private static boolean isLowerCase(char c) { return c >= 'a' && c <= 'z'; } private static boolean isUpperCase(char c) { return c >= 'A' && c <= 'Z'; } protected static Record strip(Record input) { final Record result = Record.create(input.length()); for (Item i : input) { if (!(i instanceof Slot) || i.toValue().isDistinct()) { result.add(i); } } return result; } protected enum Entry { LONG() { @Override String type() { return "long"; } @Override Value mold(String prop) { return Value.fromObject(Long.parseLong(prop)); } }, LONGS() { @Override String type() { return "CSV"; } @Override Value mold(String prop) { final String[] split = prop.split(","); if (split.length == 0) { return Value.absent(); } final Record result = Record.create(3); for (String s : split) { result.item(Long.parseLong(s)); } return result; } }, STRING() { String type() { return "String"; } @Override Value mold(String prop) { return Value.fromObject(prop); } }, STRINGS() { String type() { return "CSV(String)"; } @Override Value mold(String prop) { final String[] split = prop.split(","); if (split.length == 0) { return Value.absent(); } final Record result = Record.create(3); for (String s : split) { result.item(s); } return result; } }, RECON() { @Override String type() { return "recon"; } @Override Value mold(String prop) { return Recon.parse(prop); } }; abstract String type(); abstract Value mold(String prop); Record append(Log log, Record soFar, String t, String field, String prop) { if (prop != null) { try { final Value result = mold(prop); if (result.isDefined()) { soFar.slot(field, result); } } catch (Exception e) { log.warn("Ignored " + t + "." + field + " configuration " + prop + " as it does not yield " + type()); } } return soFar; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy