Java tutorial
/* * 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.streams.plugins.cassandra; import org.apache.streams.util.schema.FieldType; import org.apache.streams.util.schema.FieldUtil; import org.apache.streams.util.schema.Schema; import org.apache.streams.util.schema.SchemaStore; import org.apache.streams.util.schema.SchemaStoreImpl; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.commons.lang3.StringUtils; import org.jsonschema2pojo.util.URLUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import static org.apache.streams.util.schema.FileUtil.dropExtension; import static org.apache.streams.util.schema.FileUtil.dropSourcePathPrefix; import static org.apache.streams.util.schema.FileUtil.resolveRecursive; import static org.apache.streams.util.schema.FileUtil.writeFile; /** * Resource Generator for Cassandra. */ public class StreamsCassandraResourceGenerator implements Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(StreamsCassandraResourceGenerator.class); private static final String LS = System.getProperty("line.separator"); private StreamsCassandraGenerationConfig config; private SchemaStore schemaStore = new SchemaStoreImpl(); private int currentDepth = 0; /** * Run from CLI without Maven * * <p/> * java -jar streams-plugin-cassandra-jar-with-dependencies.jar StreamsCassandraResourceGenerator src/main/jsonschema target/generated-resources * * @param args [sourceDirectory, targetDirectory] */ public static void main(String[] args) { StreamsCassandraGenerationConfig config = new StreamsCassandraGenerationConfig(); String sourceDirectory = "./src/main/jsonschema"; String targetDirectory = "./target/generated-resources/cassandra"; if (args.length > 0) { sourceDirectory = args[0]; } if (args.length > 1) { targetDirectory = args[1]; } config.setSourceDirectory(sourceDirectory); config.setTargetDirectory(targetDirectory); StreamsCassandraResourceGenerator streamsCassandraResourceGenerator = new StreamsCassandraResourceGenerator( config); streamsCassandraResourceGenerator.run(); } public StreamsCassandraResourceGenerator(StreamsCassandraGenerationConfig config) { this.config = config; } @Override public void run() { Objects.requireNonNull(config); generate(config); } /** * run generate using supplied StreamsCassandraGenerationConfig. * @param config StreamsCassandraGenerationConfig */ public void generate(StreamsCassandraGenerationConfig config) { LinkedList<File> sourceFiles = new LinkedList<>(); for (Iterator<URL> sources = config.getSource(); sources.hasNext();) { URL source = sources.next(); sourceFiles.add(URLUtil.getFileFromURL(source)); } LOGGER.info("Seeded with {} source paths:", sourceFiles.size()); resolveRecursive(config, sourceFiles); LOGGER.info("Resolved {} schema files:", sourceFiles.size()); for (File item : sourceFiles) { schemaStore.create(item.toURI()); } LOGGER.info("Identified {} objects:", schemaStore.getSize()); String outputFile = config.getTargetDirectory() + "/" + "types.cql"; StringBuilder typesContent = new StringBuilder(); for (Iterator<Schema> schemaIterator = schemaStore.getSchemaIterator(); schemaIterator.hasNext();) { Schema schema = schemaIterator.next(); currentDepth = 0; if (schema.getUri().getScheme().equals("file")) { String inputFile = schema.getUri().getPath(); String resourcePath = dropSourcePathPrefix(inputFile, config.getSourceDirectory()); for (String sourcePath : config.getSourcePaths()) { resourcePath = dropSourcePathPrefix(resourcePath, sourcePath); } String resourceId = schemaSymbol(schema); LOGGER.info("Processing {}", resourcePath); String resourceContent = generateResource(schema, resourceId); typesContent.append(resourceContent); LOGGER.info("Added {}", resourceId); } } writeFile(outputFile, typesContent.toString()); } /** * generateResource String from schema and resourceId. * @param schema Schema * @param resourceId String * @return CREATE TYPE ... */ public String generateResource(Schema schema, String resourceId) { StringBuilder resourceBuilder = new StringBuilder(); resourceBuilder.append("CREATE TYPE "); resourceBuilder.append(resourceId); resourceBuilder.append(" IF NOT EXISTS ("); resourceBuilder.append(LS); resourceBuilder = appendRootObject(resourceBuilder, schema, resourceId, ' '); resourceBuilder.append(");"); resourceBuilder.append(LS); return resourceBuilder.toString(); } protected StringBuilder appendRootObject(StringBuilder builder, Schema schema, String resourceId, Character seperator) { ObjectNode propertiesNode = schemaStore.resolveProperties(schema, null, resourceId); if (propertiesNode.get("id") != null) { builder.append("id text PRIMARY KEY,"); builder.append(LS); propertiesNode.remove("id"); } if (propertiesNode != null && propertiesNode.isObject() && propertiesNode.size() > 0) { builder = appendPropertiesNode(builder, schema, propertiesNode, seperator); } return builder; } private StringBuilder appendValueField(StringBuilder builder, Schema schema, String fieldId, FieldType fieldType, Character seperator) { // safe to append nothing Objects.requireNonNull(builder); builder.append(cqlEscape(fieldId)); builder.append(seperator); builder.append(cqlType(fieldType)); return builder; } protected StringBuilder appendArrayItems(StringBuilder builder, Schema schema, String fieldId, ObjectNode itemsNode, Character seperator) { // not safe to append nothing Objects.requireNonNull(builder); if (itemsNode == null) { return builder; } if (itemsNode.has("type")) { try { FieldType itemType = FieldUtil.determineFieldType(itemsNode); switch (itemType) { case OBJECT: Schema objectSchema = null; URI parentUri = null; if (itemsNode.has("$ref") || itemsNode.has("extends")) { JsonNode refNode = itemsNode.get("$ref"); JsonNode extendsNode = itemsNode.get("extends"); if (refNode != null && refNode.isValueNode()) { parentUri = URI.create(refNode.asText()); } else if (extendsNode != null && extendsNode.isObject()) { parentUri = URI.create(extendsNode.get("$ref").asText()); } URI absoluteUri; if (parentUri.isAbsolute()) { absoluteUri = parentUri; } else { absoluteUri = schema.getUri().resolve(parentUri); if (!absoluteUri.isAbsolute() || (absoluteUri.isAbsolute() && !schemaStore.getByUri(absoluteUri).isPresent())) { absoluteUri = schema.getParentUri().resolve(parentUri); } } if (absoluteUri != null && absoluteUri.isAbsolute()) { Optional<Schema> schemaLookup = schemaStore.getByUri(absoluteUri); if (schemaLookup.isPresent()) { objectSchema = schemaLookup.get(); } } } // have to resolve schema here builder = appendArrayObject(builder, objectSchema, fieldId, seperator); break; case ARRAY: ObjectNode subArrayItems = (ObjectNode) itemsNode.get("items"); builder = appendArrayItems(builder, schema, fieldId, subArrayItems, seperator); break; default: builder = appendArrayField(builder, schema, fieldId, itemType, seperator); } } catch (Exception ex) { LOGGER.warn("No item type resolvable for {}", fieldId); } } Objects.requireNonNull(builder); return builder; } private StringBuilder appendArrayField(StringBuilder builder, Schema schema, String fieldId, FieldType fieldType, Character seperator) { // safe to append nothing Objects.requireNonNull(builder); Objects.requireNonNull(fieldId); builder.append(cqlEscape(fieldId)); builder.append(seperator); builder.append("list<" + cqlType(fieldType) + ">"); Objects.requireNonNull(builder); return builder; } private StringBuilder appendArrayObject(StringBuilder builder, Schema schema, String fieldId, Character seperator) { // safe to append nothing Objects.requireNonNull(builder); String schemaSymbol = schemaSymbol(schema); if (StringUtils.isNotBlank(fieldId) && schemaSymbol != null) { builder.append(cqlEscape(fieldId)); builder.append(seperator); builder.append("list<" + schemaSymbol + ">"); builder.append(LS); } Objects.requireNonNull(builder); return builder; } private StringBuilder appendSchemaField(StringBuilder builder, Schema schema, String fieldId, Character seperator) { // safe to append nothing Objects.requireNonNull(builder); String schemaSymbol = schemaSymbol(schema); if (StringUtils.isNotBlank(fieldId) && schemaSymbol != null) { builder.append(cqlEscape(fieldId)); builder.append(seperator); builder.append(schemaSymbol); } Objects.requireNonNull(builder); return builder; } /* can this be moved to streams-schemas if schemastore available in scope? maybe an interface? lot of boilerplate / reuse between plugins however treatment is way different when resolving a type symbol vs resolving and listing fields . */ private StringBuilder appendPropertiesNode(StringBuilder builder, Schema schema, ObjectNode propertiesNode, Character seperator) { Objects.requireNonNull(builder); Objects.requireNonNull(propertiesNode); Iterator<Map.Entry<String, JsonNode>> fields = propertiesNode.fields(); List<String> fieldStrings = new ArrayList<>(); for (; fields.hasNext();) { Map.Entry<String, JsonNode> field = fields.next(); String fieldId = field.getKey(); if (!config.getExclusions().contains(fieldId) && field.getValue().isObject()) { ObjectNode fieldNode = (ObjectNode) field.getValue(); FieldType fieldType = FieldUtil.determineFieldType(fieldNode); if (fieldType != null) { switch (fieldType) { case ARRAY: ObjectNode itemsNode = (ObjectNode) fieldNode.get("items"); if (currentDepth <= config.getMaxDepth()) { StringBuilder arrayItemsBuilder = appendArrayItems(new StringBuilder(), schema, fieldId, itemsNode, seperator); if (StringUtils.isNotBlank(arrayItemsBuilder.toString())) { fieldStrings.add(arrayItemsBuilder.toString()); } } break; case OBJECT: Schema objectSchema = null; URI parentUri = null; if (fieldNode.has("$ref") || fieldNode.has("extends")) { JsonNode refNode = fieldNode.get("$ref"); JsonNode extendsNode = fieldNode.get("extends"); if (refNode != null && refNode.isValueNode()) { parentUri = URI.create(refNode.asText()); } else if (extendsNode != null && extendsNode.isObject()) { parentUri = URI.create(extendsNode.get("$ref").asText()); } URI absoluteUri; if (parentUri.isAbsolute()) { absoluteUri = parentUri; } else { absoluteUri = schema.getUri().resolve(parentUri); if (!absoluteUri.isAbsolute() || (absoluteUri.isAbsolute() && !schemaStore.getByUri(absoluteUri).isPresent())) { absoluteUri = schema.getParentUri().resolve(parentUri); } } if (absoluteUri != null && absoluteUri.isAbsolute()) { Optional<Schema> schemaLookup = schemaStore.getByUri(absoluteUri); if (schemaLookup.isPresent()) { objectSchema = schemaLookup.get(); } } } //ObjectNode childProperties = schemaStore.resolveProperties(schema, fieldNode, fieldId); if (currentDepth < config.getMaxDepth()) { StringBuilder structFieldBuilder = appendSchemaField(new StringBuilder(), objectSchema, fieldId, seperator); if (StringUtils.isNotBlank(structFieldBuilder.toString())) { fieldStrings.add(structFieldBuilder.toString()); } } break; default: StringBuilder valueFieldBuilder = appendValueField(new StringBuilder(), schema, fieldId, fieldType, seperator); if (StringUtils.isNotBlank(valueFieldBuilder.toString())) { fieldStrings.add(valueFieldBuilder.toString()); } } } } } builder.append(String.join("," + LS, fieldStrings)).append(LS); Objects.requireNonNull(builder); return builder; } private static String cqlEscape(String fieldId) { return "`" + fieldId + "`"; } private static String cqlType(FieldType fieldType) { switch (fieldType) { case STRING: return "text"; case INTEGER: return "int"; case NUMBER: return "double"; case OBJECT: return "tuple"; case ARRAY: return "list"; default: return fieldType.name().toUpperCase(); } } private String schemaSymbol(Schema schema) { if (schema == null) { return null; } // this needs to return whatever if (schema.getUri().getScheme().equals("file")) { String inputFile = schema.getUri().getPath(); String resourcePath = dropSourcePathPrefix(inputFile, config.getSourceDirectory()); for (String sourcePath : config.getSourcePaths()) { resourcePath = dropSourcePathPrefix(resourcePath, sourcePath); } return dropExtension(resourcePath).replace("/", "_"); } else { return "IDK"; } } }