Skip to content

Commit

Permalink
Proposed change to the Maven Plugin: Auto References (#3439)
Browse files Browse the repository at this point in the history
* Added a new ReferenceFinder component for artifacts (per-type).  Added a proposed change to the maven plugin to auto detect refs

* Added a ref finder for JSON Schema
  • Loading branch information
EricWittmann authored Jun 21, 2023
1 parent 4b31c9f commit 8394e10
Show file tree
Hide file tree
Showing 37 changed files with 1,814 additions and 32 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2023 Red Hat 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 io.apicurio.registry.content.refs;

/**
* OpenAPI implementation of a reference finder. Parses the OpenAPI document, finds all $refs, converts them
* to external references, and returns them.
* @author [email protected]
*/
public class AsyncApiReferenceFinder extends AbstractDataModelsReferenceFinder {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright 2023 Red Hat 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 io.apicurio.registry.content.refs;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;

import io.apicurio.registry.content.ContentHandle;

/**
* An Apache Avro implementation of a reference finder.
* @author [email protected]
*/
public class AvroReferenceFinder implements ReferenceFinder {

private static final ObjectMapper mapper = new ObjectMapper();
private static final Logger log = LoggerFactory.getLogger(AvroReferenceFinder.class);

private static final Set<String> PRIMITIVE_TYPES = Set.of("null", "boolean", "int", "long", "float", "double", "bytes", "string");

/**
* @see io.apicurio.registry.content.refs.ReferenceFinder#findExternalReferences(io.apicurio.registry.content.ContentHandle)
*/
@Override
public Set<ExternalReference> findExternalReferences(ContentHandle content) {
try {
JsonNode tree = mapper.readTree(content.content());
Set<String> externalTypes = new HashSet<>();
findExternalTypesIn(tree, externalTypes);
return externalTypes.stream().map(type -> new ExternalReference(type)).collect(Collectors.toSet());
} catch (Exception e) {
log.error("Error finding external references in an Avro file.", e);
return Collections.emptySet();
}
}

private static void findExternalTypesIn(JsonNode schema, Set<String> externalTypes) {
// Null check
if (schema == null || schema.isNull()) {
return;
}

// Handle primitive/external types
if (schema.isTextual()) {
String type = schema.asText();
if (!PRIMITIVE_TYPES.contains(type)) {
externalTypes.add(type);
}
}

// Handle unions
if (schema.isArray()) {
ArrayNode schemas = (ArrayNode) schema;
schemas.forEach(s -> findExternalTypesIn(s, externalTypes));
}

// Handle records
if (schema.isObject() && schema.has("type") && !schema.get("type").isNull() && schema.get("type").asText().equals("record")) {
JsonNode fieldsNode = schema.get("fields");
if (fieldsNode != null && fieldsNode.isArray()) {
ArrayNode fields = (ArrayNode) fieldsNode;
fields.forEach(fieldNode -> {
if (fieldNode.isObject()) {
JsonNode typeNode = fieldNode.get("type");
findExternalTypesIn(typeNode, externalTypes);
}
});
}
}
// Handle arrays
if (schema.has("type") && !schema.get("type").isNull() && schema.get("type").asText().equals("array")) {
JsonNode items = schema.get("items");
findExternalTypesIn(items, externalTypes);
}
// Handle maps
if (schema.has("type") && !schema.get("type").isNull() && schema.get("type").asText().equals("map")) {
JsonNode values = schema.get("values");
findExternalTypesIn(values, externalTypes);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
* Copyright 2023 Red Hat 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 io.apicurio.registry.content.refs;

import java.util.Objects;

/**
* Models a reference from one artifact to another. This represents the information found in the content
* of an artifact, and is very type-specific. For example, a JSON schema reference might look like this:
*
* <pre>
* {
* "$ref" : "types/data-types.json#/$defs/FooType"
* }
* </pre>
*
* In this case, the fields of this type will be:
*
* <ul>
* <li><em>fullReference</em>: <code>types/data-types.json#/$defs/FooType</code></li>
* <li><em>resource</em>: <code>types/data-types.json</code></li>
* <li><em>component</em>: <code>#/$defs/FooType</code></li>
* </ul>
*
* For an Avro artifact a reference might look like this:
*
* <pre>
* {
* "name": "exchange",
* "type": "com.kubetrade.schema.common.Exchange"
* }
* </pre>
*
* In this case, the fields of this type will be:
*
* <ul>
* <li><em>fullReference</em>: <code>com.kubetrade.schema.common.Exchange</code></li>
* <li><em>resource</em>: <code>com.kubetrade.schema.common.Exchange</code></li>
* <li><em>component</em>: <em>null</em></li>
* </ul>
*
* @author [email protected]
*/
public class ExternalReference {

private String fullReference;
private String resource;
private String component;

/**
* Constructor.
* @param fullReference
* @param resource
* @param component
*/
public ExternalReference(String fullReference, String resource, String component) {
this.fullReference = fullReference;
this.resource = resource;
this.component = component;
}

/**
* Constructor. This variant is useful if there is no component part of an external reference. In this
* case the full reference is also the resource (and the component is null).
* @param reference
*/
public ExternalReference(String reference) {
this(reference, reference, null);
}

/**
* @return the fullReference
*/
public String getFullReference() {
return fullReference;
}

/**
* @param fullReference the fullReference to set
*/
public void setFullReference(String fullReference) {
this.fullReference = fullReference;
}

/**
* @return the resource
*/
public String getResource() {
return resource;
}

/**
* @param resource the resource to set
*/
public void setResource(String resource) {
this.resource = resource;
}

/**
* @return the component
*/
public String getComponent() {
return component;
}

/**
* @param component the component to set
*/
public void setComponent(String component) {
this.component = component;
}

/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return Objects.hash(fullReference);
}

/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ExternalReference other = (ExternalReference) obj;
return Objects.equals(fullReference, other.fullReference);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright 2023 Red Hat 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 io.apicurio.registry.content.refs;

/**
* @author [email protected]
*/
public class JsonPointerExternalReference extends ExternalReference {

/**
* Constructor.
* @param jsonPointer
*/
public JsonPointerExternalReference(String jsonPointer) {
super(jsonPointer, resourceFrom(jsonPointer), componentFrom(jsonPointer));
}

private static String componentFrom(String jsonPointer) {
int idx = jsonPointer.indexOf('#');
if (idx == 0) {
return jsonPointer;
} else if (idx > 0) {
return jsonPointer.substring(idx);
} else {
return null;
}
}

private static String resourceFrom(String jsonPointer) {
int idx = jsonPointer.indexOf('#');
if (idx == 0) {
return null;
} else if (idx > 0) {
return jsonPointer.substring(0, idx);
} else {
return jsonPointer;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2023 Red Hat 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 io.apicurio.registry.content.refs;

import java.util.Collections;
import java.util.Set;

import io.apicurio.registry.content.ContentHandle;

/**
* @author [email protected]
*/
public class NoOpReferenceFinder implements ReferenceFinder {

public static final ReferenceFinder INSTANCE = new NoOpReferenceFinder();

/**
* @see io.apicurio.registry.content.refs.ReferenceFinder#findExternalReferences(io.apicurio.registry.content.ContentHandle)
*/
@Override
public Set<ExternalReference> findExternalReferences(ContentHandle content) {
return Collections.emptySet();
}

}
Loading

0 comments on commit 8394e10

Please sign in to comment.