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

org.apache.kafka.connect.data.SchemaBuilder Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.kafka.connect.data;

import org.apache.kafka.connect.errors.DataException;
import org.apache.kafka.connect.errors.SchemaBuilderException;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 

* SchemaBuilder provides a fluent API for constructing {@link Schema} objects. It allows you to set each of the * properties for the schema and each call returns the SchemaBuilder so the calls can be chained. When nested types * are required, use one of the predefined schemas from {@link Schema} or use a second SchemaBuilder inline. *

*

* Here is an example of building a struct schema: *

 *     Schema dateSchema = SchemaBuilder.struct()
 *         .name("com.example.CalendarDate").version(2).doc("A calendar date including month, day, and year.")
 *         .field("month", Schema.STRING_SCHEMA)
 *         .field("day", Schema.INT8_SCHEMA)
 *         .field("year", Schema.INT16_SCHEMA)
 *         .build();
 *     
*

*

* Here is an example of using a second SchemaBuilder to construct complex, nested types: *

 *     Schema userListSchema = SchemaBuilder.array(
 *         SchemaBuilder.struct().name("com.example.User").field("username", Schema.STRING_SCHEMA).field("id", Schema.INT64_SCHEMA).build()
 *     ).build();
 *     
*

*/ public class SchemaBuilder implements Schema { private static final String TYPE_FIELD = "type"; private static final String OPTIONAL_FIELD = "optional"; private static final String DEFAULT_FIELD = "default"; private static final String NAME_FIELD = "name"; private static final String VERSION_FIELD = "version"; private static final String DOC_FIELD = "doc"; private final Type type; private Boolean optional = null; private Object defaultValue = null; private Map fields = null; private Schema keySchema = null; private Schema valueSchema = null; private String name; private Integer version; // Optional human readable documentation describing this schema. private String doc; // Additional parameters for logical types. private Map parameters; public SchemaBuilder(Type type) { if (null == type) throw new SchemaBuilderException("type cannot be null"); this.type = type; if (type == Type.STRUCT) { fields = new LinkedHashMap<>(); } } // Common/metadata fields @Override public boolean isOptional() { return optional != null && optional; } /** * Set this schema as optional. * @return the SchemaBuilder */ public SchemaBuilder optional() { checkCanSet(OPTIONAL_FIELD, optional, true); optional = true; return this; } /** * Set this schema as required. This is the default, but this method can be used to make this choice explicit. * @return the SchemaBuilder */ public SchemaBuilder required() { checkCanSet(OPTIONAL_FIELD, optional, false); optional = false; return this; } @Override public Object defaultValue() { return defaultValue; } /** * Set the default value for this schema. The value is validated against the schema type, throwing a * {@link SchemaBuilderException} if it does not match. * @param value the default value * @return the SchemaBuilder */ public SchemaBuilder defaultValue(Object value) { checkCanSet(DEFAULT_FIELD, defaultValue, value); checkNotNull(TYPE_FIELD, type, DEFAULT_FIELD); try { ConnectSchema.validateValue(this, value); } catch (DataException e) { throw new SchemaBuilderException("Invalid default value", e); } defaultValue = value; return this; } @Override public String name() { return name; } /** * Set the name of this schema. * @param name the schema name * @return the SchemaBuilder */ public SchemaBuilder name(String name) { checkCanSet(NAME_FIELD, this.name, name); this.name = name; return this; } @Override public Integer version() { return version; } /** * Set the version of this schema. Schema versions are integers which, if provided, must indicate which schema is * newer and which is older by their ordering. * @param version the schema version * @return the SchemaBuilder */ public SchemaBuilder version(Integer version) { checkCanSet(VERSION_FIELD, this.version, version); this.version = version; return this; } @Override public String doc() { return doc; } /** * Set the documentation for this schema. * @param doc the documentation * @return the SchemaBuilder */ public SchemaBuilder doc(String doc) { checkCanSet(DOC_FIELD, this.doc, doc); this.doc = doc; return this; } @Override public Map parameters() { return parameters == null ? null : Collections.unmodifiableMap(parameters); } /** * Set a schema parameter. * @param propertyName name of the schema property to define * @param propertyValue value of the schema property to define, as a String * @return the SchemaBuilder */ public SchemaBuilder parameter(String propertyName, String propertyValue) { // Preserve order of insertion with a LinkedHashMap. This isn't strictly necessary, but is nice if logical types // can print their properties in a consistent order. if (parameters == null) parameters = new LinkedHashMap<>(); parameters.put(propertyName, propertyValue); return this; } /** * Set schema parameters. This operation is additive; it does not remove existing parameters that do not appear in * the set of properties pass to this method. * @param props Map of properties to set * @return the SchemaBuilder */ public SchemaBuilder parameters(Map props) { // Avoid creating an empty set of properties so we never have an empty map if (props.isEmpty()) return this; if (parameters == null) parameters = new LinkedHashMap<>(); parameters.putAll(props); return this; } @Override public Type type() { return type; } /** * Create a SchemaBuilder for the specified type. *

* Usually it will be simpler to use one of the variants like {@link #string()} or {@link #struct()}, but this form * can be useful when generating schemas dynamically. * * @param type the schema type * @return a new SchemaBuilder */ public static SchemaBuilder type(Type type) { return new SchemaBuilder(type); } // Primitive types /** * @return a new {@link Schema.Type#INT8} SchemaBuilder */ public static SchemaBuilder int8() { return new SchemaBuilder(Type.INT8); } /** * @return a new {@link Schema.Type#INT16} SchemaBuilder */ public static SchemaBuilder int16() { return new SchemaBuilder(Type.INT16); } /** * @return a new {@link Schema.Type#INT32} SchemaBuilder */ public static SchemaBuilder int32() { return new SchemaBuilder(Type.INT32); } /** * @return a new {@link Schema.Type#INT64} SchemaBuilder */ public static SchemaBuilder int64() { return new SchemaBuilder(Type.INT64); } /** * @return a new {@link Schema.Type#FLOAT32} SchemaBuilder */ public static SchemaBuilder float32() { return new SchemaBuilder(Type.FLOAT32); } /** * @return a new {@link Schema.Type#FLOAT64} SchemaBuilder */ public static SchemaBuilder float64() { return new SchemaBuilder(Type.FLOAT64); } /** * @return a new {@link Schema.Type#BOOLEAN} SchemaBuilder */ public static SchemaBuilder bool() { return new SchemaBuilder(Type.BOOLEAN); } /** * @return a new {@link Schema.Type#STRING} SchemaBuilder */ public static SchemaBuilder string() { return new SchemaBuilder(Type.STRING); } /** * @return a new {@link Schema.Type#BYTES} SchemaBuilder */ public static SchemaBuilder bytes() { return new SchemaBuilder(Type.BYTES); } // Structs /** * @return a new {@link Schema.Type#STRUCT} SchemaBuilder */ public static SchemaBuilder struct() { return new SchemaBuilder(Type.STRUCT); } /** * Add a field to this {@link Schema.Type#STRUCT} schema. Throws a {@link SchemaBuilderException} if this is not a struct schema. * @param fieldName the name of the field to add * @param fieldSchema the Schema for the field's value * @return the SchemaBuilder */ public SchemaBuilder field(String fieldName, Schema fieldSchema) { if (type != Type.STRUCT) throw new SchemaBuilderException("Cannot create fields on type " + type); if (null == fieldName || fieldName.isEmpty()) throw new SchemaBuilderException("fieldName cannot be null."); if (null == fieldSchema) throw new SchemaBuilderException("fieldSchema for field " + fieldName + " cannot be null."); int fieldIndex = fields.size(); if (fields.containsKey(fieldName)) throw new SchemaBuilderException("Cannot create field because of field name duplication " + fieldName); fields.put(fieldName, new Field(fieldName, fieldIndex, fieldSchema)); return this; } /** * Get the list of fields for this Schema. Throws a {@link DataException} if this schema is not a {@link Schema.Type#STRUCT}. * @return the list of fields for this Schema */ @Override public List fields() { if (type != Type.STRUCT) throw new DataException("Cannot list fields on non-struct type"); return new ArrayList<>(fields.values()); } @Override public Field field(String fieldName) { if (type != Type.STRUCT) throw new DataException("Cannot look up fields on non-struct type"); return fields.get(fieldName); } // Maps & Arrays /** * @param valueSchema the schema for elements of the array * @return a new {@link Schema.Type#ARRAY} SchemaBuilder */ public static SchemaBuilder array(Schema valueSchema) { if (null == valueSchema) throw new SchemaBuilderException("valueSchema cannot be null."); SchemaBuilder builder = new SchemaBuilder(Type.ARRAY); builder.valueSchema = valueSchema; return builder; } /** * @param keySchema the schema for keys in the map * @param valueSchema the schema for values in the map * @return a new {@link Schema.Type#MAP} SchemaBuilder */ public static SchemaBuilder map(Schema keySchema, Schema valueSchema) { if (null == keySchema) throw new SchemaBuilderException("keySchema cannot be null."); if (null == valueSchema) throw new SchemaBuilderException("valueSchema cannot be null."); SchemaBuilder builder = new SchemaBuilder(Type.MAP); builder.keySchema = keySchema; builder.valueSchema = valueSchema; return builder; } static SchemaBuilder arrayOfNull() { return new SchemaBuilder(Type.ARRAY); } static SchemaBuilder mapOfNull() { return new SchemaBuilder(Type.MAP); } static SchemaBuilder mapWithNullKeys(Schema valueSchema) { SchemaBuilder result = new SchemaBuilder(Type.MAP); result.valueSchema = valueSchema; return result; } static SchemaBuilder mapWithNullValues(Schema keySchema) { SchemaBuilder result = new SchemaBuilder(Type.MAP); result.keySchema = keySchema; return result; } @Override public Schema keySchema() { return keySchema; } @Override public Schema valueSchema() { return valueSchema; } /** * Build the Schema using the current settings * @return the {@link Schema} */ public Schema build() { return new ConnectSchema(type, isOptional(), defaultValue, name, version, doc, parameters == null ? null : Collections.unmodifiableMap(parameters), fields == null ? null : Collections.unmodifiableList(new ArrayList<>(fields.values())), keySchema, valueSchema); } /** * Return a concrete instance of the {@link Schema} specified by this builder * @return the {@link Schema} */ @Override public Schema schema() { return build(); } private static void checkCanSet(String fieldName, Object fieldVal, Object val) { if (fieldVal != null && fieldVal != val) throw new SchemaBuilderException("Invalid SchemaBuilder call: " + fieldName + " has already been set."); } private static void checkNotNull(String fieldName, Object val, String fieldToSet) { if (val == null) throw new SchemaBuilderException("Invalid SchemaBuilder call: " + fieldName + " must be specified to set " + fieldToSet); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy