| /* |
| * 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 |
| * |
| * 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.util.schema; |
| |
| import org.apache.streams.util.PropertyUtil; |
| |
| import com.fasterxml.jackson.databind.JsonNode; |
| import com.fasterxml.jackson.databind.node.JsonNodeFactory; |
| import com.fasterxml.jackson.databind.node.ObjectNode; |
| import com.google.common.collect.Ordering; |
| import org.apache.commons.lang3.StringUtils; |
| import org.jsonschema2pojo.ContentResolver; |
| import org.jsonschema2pojo.FragmentResolver; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.net.URI; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| |
| import static org.apache.streams.util.schema.UriUtil.safeResolve; |
| |
| /** |
| * Default Implementation of SchemaStore. |
| */ |
| public class SchemaStoreImpl extends Ordering<Schema> implements SchemaStore { |
| |
| private static final Logger LOGGER = LoggerFactory.getLogger(SchemaStore.class); |
| private static final JsonNodeFactory NODE_FACTORY = JsonNodeFactory.instance; |
| |
| protected Map<URI, Schema> schemas = new HashMap<>(); |
| protected FragmentResolver fragmentResolver = new FragmentResolver(); |
| protected ContentResolver contentResolver = new ContentResolver(); |
| |
| public SchemaStoreImpl() { |
| } |
| |
| @Override |
| public synchronized Schema create(URI uri) { |
| if (!getByUri(uri).isPresent()) { |
| URI baseUri = UriUtil.removeFragment(uri); |
| JsonNode baseNode = this.contentResolver.resolve(baseUri); |
| if (uri.toString().contains("#") && !uri.toString().endsWith("#")) { |
| Schema newSchema = new Schema(baseUri, baseNode, null, true); |
| this.schemas.put(baseUri, newSchema); |
| JsonNode childContent = this.fragmentResolver.resolve(baseNode, '#' + StringUtils.substringAfter(uri.toString(), "#"), "#"); |
| this.schemas.put(uri, new Schema(uri, childContent, newSchema, false)); |
| } else { |
| if ( baseNode.has("extends") && baseNode.get("extends").isObject()) { |
| URI ref = URI.create((baseNode.get("extends")).get("$ref").asText()); |
| URI absoluteUri; |
| if ( ref.isAbsolute()) { |
| absoluteUri = ref; |
| } else { |
| absoluteUri = baseUri.resolve(ref); |
| } |
| JsonNode parentNode = this.contentResolver.resolve(absoluteUri); |
| Schema parentSchema; |
| if ( this.schemas.get(absoluteUri) != null ) { |
| parentSchema = this.schemas.get(absoluteUri); |
| } else { |
| parentSchema = create(absoluteUri); |
| } |
| this.schemas.put(uri, new Schema(uri, baseNode, parentSchema, true)); |
| } else { |
| this.schemas.put(uri, new Schema(uri, baseNode, null, true)); |
| } |
| } |
| List<JsonNode> refs = baseNode.findValues("$ref"); |
| for ( JsonNode ref : refs ) { |
| if ( ref.isValueNode() ) { |
| String refVal = ref.asText(); |
| URI refUri = null; |
| try { |
| refUri = URI.create(refVal); |
| } catch ( Exception ex ) { |
| LOGGER.info("Exception: {}", ex.getMessage()); |
| } |
| if (refUri != null && !getByUri(refUri).isPresent()) { |
| if (refUri.isAbsolute()) { |
| create(refUri); |
| } else { |
| create(baseUri.resolve(refUri)); |
| } |
| } |
| } |
| } |
| } |
| |
| return this.schemas.get(uri); |
| } |
| |
| @Override |
| public Schema create(Schema parent, String path) { |
| if (path.equals("#")) { |
| return parent; |
| } else { |
| path = StringUtils.stripEnd(path, "#?&/"); |
| URI id = (parent != null && parent.getId() != null) |
| ? parent.getId().resolve(path) |
| : URI.create(path); |
| if (this.selfReferenceWithoutParentFile(parent, path)) { |
| Schema newSchema = new Schema(id, this.fragmentResolver.resolve(parent.getParentContent(), path, "#"), parent, false); |
| this.schemas.put(id, newSchema); |
| return this.schemas.get(id); |
| } else { |
| return this.create(id); |
| } |
| } |
| } |
| |
| protected boolean selfReferenceWithoutParentFile(Schema parent, String path) { |
| return parent != null && (parent.getId() == null || parent.getId().toString().startsWith("#/")) && path.startsWith("#/"); |
| } |
| |
| @Override |
| public synchronized void clearCache() { |
| this.schemas.clear(); |
| } |
| |
| @Override |
| public Integer getSize() { |
| return schemas.size(); |
| } |
| |
| @Override |
| public Optional<Schema> getById(URI id) { |
| for ( Schema schema : schemas.values() ) { |
| if ( schema.getId() != null && schema.getId().equals(id) ) { |
| return Optional.of(schema); |
| } |
| } |
| return Optional.empty(); |
| } |
| |
| @Override |
| public Optional<Schema> getByUri(URI uri) { |
| for ( Schema schema : schemas.values() ) { |
| if ( schema.getUri().equals(uri) ) { |
| return Optional.of(schema); |
| } |
| } |
| return Optional.empty(); |
| } |
| |
| @Override |
| public Integer getFileUriCount() { |
| int count = 0; |
| for ( Schema schema : schemas.values() ) { |
| if ( schema.getUri().getScheme().equals("file") ) { |
| count++; |
| } |
| } |
| return count; |
| } |
| |
| @Override |
| public Integer getHttpUriCount() { |
| int count = 0; |
| for ( Schema schema : schemas.values() ) { |
| if ( schema.getUri().getScheme().equals("http") ) { |
| count++; |
| } |
| } |
| return count; |
| } |
| |
| @Override |
| public Iterator<Schema> getSchemaIterator() { |
| List<Schema> schemaList = new ArrayList<>(schemas.values()); |
| schemaList.sort(this); |
| return schemaList.iterator(); |
| } |
| |
| @Override |
| public ObjectNode resolveProperties(Schema schema, ObjectNode fieldNode, String resourceId) { |
| // this should return something more suitable like: |
| // Map<String, Pair<Schema, ObjectNode>> |
| ObjectNode schemaProperties = NODE_FACTORY.objectNode(); |
| ObjectNode parentProperties = NODE_FACTORY.objectNode(); |
| if (fieldNode == null) { |
| ObjectNode schemaContent = (ObjectNode) schema.getContent(); |
| if (schemaContent.has("properties")) { |
| schemaProperties = (ObjectNode) schemaContent.get("properties"); |
| if (schema.getParentContent() != null) { |
| ObjectNode parentContent = (ObjectNode) schema.getParentContent(); |
| if (parentContent.has("properties")) { |
| parentProperties = (ObjectNode) parentContent.get("properties"); |
| } |
| } |
| } |
| } else if (fieldNode.size() > 0) { |
| if (fieldNode.has("properties") && fieldNode.get("properties").isObject() && fieldNode.get("properties").size() > 0) { |
| schemaProperties = (ObjectNode) fieldNode.get("properties"); |
| } |
| 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()); |
| } |
| ObjectNode parentContent = null; |
| URI absoluteUri; |
| if (parentUri.isAbsolute()) { |
| absoluteUri = parentUri; |
| } else { |
| absoluteUri = schema.getUri().resolve(parentUri); |
| if (!absoluteUri.isAbsolute() || (absoluteUri.isAbsolute() && !getByUri(absoluteUri).isPresent() )) { |
| absoluteUri = schema.getParentUri().resolve(parentUri); |
| } |
| } |
| if (absoluteUri.isAbsolute()) { |
| if (getByUri(absoluteUri).isPresent()) { |
| parentContent = (ObjectNode) getByUri(absoluteUri).get().getContent(); |
| } |
| if (parentContent != null && parentContent.isObject() && parentContent.has("properties")) { |
| parentProperties = (ObjectNode) parentContent.get("properties"); |
| } else if (absoluteUri.getPath().endsWith("#properties")) { |
| absoluteUri = URI.create(absoluteUri.toString().replace("#properties", "")); |
| parentProperties = (ObjectNode) getByUri(absoluteUri).get().getContent().get("properties"); |
| } |
| } |
| } |
| |
| |
| } |
| |
| ObjectNode resolvedProperties = NODE_FACTORY.objectNode(); |
| if (parentProperties != null && parentProperties.size() > 0) { |
| resolvedProperties = PropertyUtil.mergeProperties(schemaProperties, parentProperties); |
| } else { |
| resolvedProperties = schemaProperties.deepCopy(); |
| } |
| |
| return resolvedProperties; |
| } |
| |
| /** |
| * resolve full definition of 'items'. |
| * @param schema Schema |
| * @param fieldNode ObjectNode |
| * @param resourceId resourceId |
| * @return ObjectNode |
| */ |
| public ObjectNode resolveItems(Schema schema, ObjectNode fieldNode, String resourceId) { |
| ObjectNode schemaItems = NODE_FACTORY.objectNode(); |
| ObjectNode parentItems = NODE_FACTORY.objectNode(); |
| if (fieldNode == null) { |
| ObjectNode schemaContent = (ObjectNode) schema.getContent(); |
| if ( schemaContent.has("items") ) { |
| schemaItems = (ObjectNode) schemaContent.get("items"); |
| if (schema.getParentContent() != null) { |
| ObjectNode parentContent = (ObjectNode) schema.getParentContent(); |
| if (parentContent.has("items")) { |
| parentItems = (ObjectNode) parentContent.get("items"); |
| } |
| } |
| } |
| } else if (fieldNode.size() > 0) { |
| if (fieldNode.has("items") && fieldNode.get("items").isObject() && fieldNode.get("items").size() > 0) { |
| schemaItems = (ObjectNode) fieldNode.get("items"); |
| } |
| 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()); |
| } |
| ObjectNode parentContent = null; |
| URI absoluteUri; |
| if (parentUri.isAbsolute()) { |
| absoluteUri = parentUri; |
| } else { |
| absoluteUri = schema.getUri().resolve(parentUri); |
| if (!absoluteUri.isAbsolute() || (absoluteUri.isAbsolute() && !getByUri(absoluteUri).isPresent() )) { |
| absoluteUri = schema.getParentUri().resolve(parentUri); |
| } |
| } |
| if (absoluteUri.isAbsolute()) { |
| if (getByUri(absoluteUri).isPresent()) { |
| parentContent = (ObjectNode) getByUri(absoluteUri).get().getContent(); |
| } |
| if (parentContent != null && parentContent.isObject() && parentContent.has("items")) { |
| parentItems = (ObjectNode) parentContent.get("items"); |
| } else if (absoluteUri.getPath().endsWith("#items")) { |
| absoluteUri = URI.create(absoluteUri.toString().replace("#items", "")); |
| parentItems = (ObjectNode) getByUri(absoluteUri).get().getContent().get("items"); |
| } |
| } |
| } |
| } |
| |
| ObjectNode resolvedItems = NODE_FACTORY.objectNode(); |
| if (parentItems != null && parentItems.size() > 0) { |
| resolvedItems = PropertyUtil.mergeProperties(schemaItems, parentItems); |
| } else { |
| resolvedItems = schemaItems.deepCopy(); |
| } |
| |
| return resolvedItems; |
| } |
| |
| @Override |
| public int compare(Schema left, Schema right) { |
| // are they the same? |
| if ( left.equals(right)) { |
| return 0; |
| } |
| // is one an ancestor of the other |
| Schema candidateAncestor = left; |
| while ( candidateAncestor.getParent() != null ) { |
| candidateAncestor = candidateAncestor.getParent(); |
| if ( candidateAncestor.equals(right)) { |
| return 1; |
| } |
| } |
| candidateAncestor = right; |
| while ( candidateAncestor.getParent() != null ) { |
| candidateAncestor = candidateAncestor.getParent(); |
| if ( candidateAncestor.equals(left)) { |
| return -1; |
| } |
| } |
| // does one have a field that reference the other? |
| for ( JsonNode refNode : left.getContent().findValues("$ref") ) { |
| String refText = refNode.asText(); |
| Optional<URI> resolvedUri = safeResolve(left.getUri(), refText); |
| if ( resolvedUri.isPresent() && resolvedUri.get().equals(right.getUri())) { |
| return 1; |
| } |
| } |
| for ( JsonNode refNode : right.getContent().findValues("$ref") ) { |
| String refText = refNode.asText(); |
| Optional<URI> resolvedUri = safeResolve(right.getUri(), refText); |
| if ( resolvedUri.isPresent() && resolvedUri.get().equals(left.getUri())) { |
| return -1; |
| } |
| } |
| // does one have a field that reference a third schema that references the other? |
| for ( JsonNode refNode : left.getContent().findValues("$ref") ) { |
| String refText = refNode.asText(); |
| Optional<URI> possibleConnectorUri = safeResolve(left.getUri(), refText); |
| if ( possibleConnectorUri.isPresent()) { |
| Optional<Schema> possibleConnector = getByUri(possibleConnectorUri.get()); |
| if (possibleConnector.isPresent()) { |
| for (JsonNode connectorRefNode : possibleConnector.get().getContent().findValues("$ref")) { |
| String connectorRefText = connectorRefNode.asText(); |
| Optional<URI> resolvedUri = safeResolve(possibleConnector.get().getUri(), connectorRefText); |
| if (resolvedUri.isPresent() && resolvedUri.get().equals(right.getUri())) { |
| return 1; |
| } |
| } |
| } |
| } |
| } |
| for ( JsonNode refNode : right.getContent().findValues("$ref") ) { |
| String refText = refNode.asText(); |
| Optional<URI> possibleConnectorUri = safeResolve(right.getUri(), refText); |
| if ( possibleConnectorUri.isPresent()) { |
| Optional<Schema> possibleConnector = getByUri(possibleConnectorUri.get()); |
| if (possibleConnector.isPresent()) { |
| for (JsonNode connectorRefNode : possibleConnector.get().getContent().findValues("$ref")) { |
| String connectorRefText = connectorRefNode.asText(); |
| Optional<URI> resolvedUri = safeResolve(possibleConnector.get().getUri(), connectorRefText); |
| if (resolvedUri.isPresent() && resolvedUri.get().equals(left.getUri())) { |
| return -1; |
| } |
| } |
| } |
| } |
| } |
| // there still has to be some order even when there are no connections. |
| // we'll arbitrarily pick alphabetic by ID |
| int lexigraphic = right.toString().compareTo(left.toString()); |
| return ( lexigraphic / Math.abs(lexigraphic) ); |
| } |
| |
| } |
| |