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

software.amazon.smithy.model.knowledge.IdentifierBindingIndex Maven / Gradle / Ivy

Go to download

This module provides the core implementation of loading, validating, traversing, mutating, and serializing a Smithy model.

There is a newer version: 1.54.0
Show newest version
/*
 * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file 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 software.amazon.smithy.model.knowledge;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.SortedSet;
import java.util.TreeSet;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.MemberShape;
import software.amazon.smithy.model.shapes.ResourceShape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.shapes.StructureShape;
import software.amazon.smithy.model.shapes.ToShapeId;
import software.amazon.smithy.model.traits.RequiredTrait;
import software.amazon.smithy.model.traits.ResourceIdentifierTrait;

/**
 * Index of operation shapes to the identifiers bound to the operation.
 */
public final class IdentifierBindingIndex implements KnowledgeIndex {
    /** Map of Resource shape ID to a map of Operation shape ID to a map of identifier name to the member name. */
    private final Map>> inputBindings = new HashMap<>();
    private final Map>> outputBindings = new HashMap<>();
    private final SortedSet allIdentifiers = new TreeSet<>();

    /** Map of Resource shape ID to a map of Operation shape ID to a binding type. */
    private final Map> bindingTypes = new HashMap<>();

    public enum BindingType {
        /** Indicates that the operation is bound to a resource as an instance operation. */
        INSTANCE,
        /** Indicates that the operation is bound to a resource as a collection operation. */
        COLLECTION,
        /** Indicates that the operation is not bound to a resource. */
        NONE
    }

    public IdentifierBindingIndex(Model model) {
        OperationIndex operationIndex = OperationIndex.of(model);
        for (ResourceShape resource : model.getResourceShapes()) {
            processResource(resource, operationIndex, model);
        }
    }

    public static IdentifierBindingIndex of(Model model) {
        return model.getKnowledge(IdentifierBindingIndex.class, IdentifierBindingIndex::new);
    }

    /**
     * Gets the identifier binding type of an operation to a resource.
     *
     * 

The NONE Binding type is returned if the resource can't be found, * the operation can't be found, or if the operation is not bound to the * resource. * * @param resource Shape ID of a resource. * @param operation Shape ID of an operation. * @return Returns the binding type of the operation. */ public BindingType getOperationBindingType(ToShapeId resource, ToShapeId operation) { return Optional.ofNullable(bindingTypes.get(resource.toShapeId())) .flatMap(resourceMap -> Optional.ofNullable(resourceMap.get(operation.toShapeId()))) .orElse(BindingType.NONE); } /** * Gets a map of identifier names to input member names that provide a * value for that identifier. * * @param resource Shape ID of a resource. * @param operation Shape ID of an operation. * @return Returns the identifier bindings map or an empty map if the * binding is invalid or cannot be found. */ public Map getOperationInputBindings(ToShapeId resource, ToShapeId operation) { return Optional.ofNullable(inputBindings.get(resource.toShapeId())) .flatMap(resourceMap -> Optional.ofNullable(resourceMap.get(operation.toShapeId()))) .map(Collections::unmodifiableMap) .orElseGet(Collections::emptyMap); } /** * Gets a map of identifier names to output member names that provide a * value for that identifier. * * @param resource Shape ID of a resource. * @param operation Shape ID of an operation. * @return Returns the identifier bindings map or an empty map if the * binding is invalid or cannot be found. */ public Map getOperationOutputBindings(ToShapeId resource, ToShapeId operation) { return Optional.ofNullable(outputBindings.get(resource.toShapeId())) .flatMap(resourceMap -> Optional.ofNullable(resourceMap.get(operation.toShapeId()))) .map(Collections::unmodifiableMap) .orElseGet(Collections::emptyMap); } /** * Gets a map of identifier names to input member names that provide a * value for that identifier. * * @param resource Shape ID of a resource. * @param operation Shape ID of an operation. * @return Returns the identifier bindings map or an empty map if the * binding is invalid or cannot be found. * * @deprecated Use {@link #getOperationInputBindings} instead. */ @Deprecated public Map getOperationBindings(ToShapeId resource, ToShapeId operation) { return getOperationInputBindings(resource, operation); } private void processResource(ResourceShape resource, OperationIndex operationIndex, Model model) { inputBindings.put(resource.getId(), new HashMap<>()); outputBindings.put(resource.getId(), new HashMap<>()); bindingTypes.put(resource.getId(), new HashMap<>()); resource.getAllOperations().forEach(operationId -> { // Ignore broken models in this index. Map computedInputBindings = operationIndex.getInputShape(operationId) .map(inputShape -> computeBindings(resource, inputShape)) .orElse(Collections.emptyMap()); inputBindings.get(resource.getId()).put(operationId, computedInputBindings); allIdentifiers.addAll(computedInputBindings.keySet()); Map computedOutputBindings = operationIndex.getOutputShape(operationId) .map(outputShape -> computeBindings(resource, outputShape)) .orElse(Collections.emptyMap()); outputBindings.get(resource.getId()).put(operationId, computedOutputBindings); allIdentifiers.addAll(computedOutputBindings.keySet()); bindingTypes.get(resource.getId()).put(operationId, isCollection(resource, operationId) ? BindingType.COLLECTION : BindingType.INSTANCE); }); } private boolean isCollection(ResourceShape resource, ToShapeId operationId) { return resource.getCollectionOperations().contains(operationId.toShapeId()) || (resource.getCreate().isPresent() && resource.getCreate().get().toShapeId().equals(operationId)) || (resource.getList().isPresent() && resource.getList().get().toShapeId().equals(operationId)); } private boolean isImplicitIdentifierBinding(MemberShape member, ResourceShape resource) { return resource.getIdentifiers().containsKey(member.getMemberName()) && member.getTrait(RequiredTrait.class).isPresent() && member.getTarget().equals(resource.getIdentifiers().get(member.getMemberName())); } private Map computeBindings(ResourceShape resource, StructureShape shape) { Map bindings = new HashMap<>(); for (MemberShape member : shape.getAllMembers().values()) { if (member.hasTrait(ResourceIdentifierTrait.class)) { // Mark as a binding if the member has an explicit @resourceIdentifier trait. String bindingName = member.expectTrait(ResourceIdentifierTrait.class).getValue(); // Override any implicit bindings with explicit trait bindings. bindings.put(bindingName, member.getMemberName()); } else if (isImplicitIdentifierBinding(member, resource)) { // Mark as a binding if the member is an implicit identifier binding. // Only utilize implicit bindings when an explicit binding wasn't found. bindings.putIfAbsent(member.getMemberName(), member.getMemberName()); } } return bindings; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy