Package com.amazonaws.resources.internal

Source Code of com.amazonaws.resources.internal.ResourceImpl

/*
* Copyright 2014 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 com.amazonaws.resources.internal;

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

import com.amazonaws.AmazonWebServiceRequest;
import com.amazonaws.resources.ResultCapture;
import com.amazonaws.resources.internal.model.ActionModel;
import com.amazonaws.resources.internal.model.CollectionModel;
import com.amazonaws.resources.internal.model.FlatMapping;
import com.amazonaws.resources.internal.model.IdentifierModel;
import com.amazonaws.resources.internal.model.PathSourceMapping;
import com.amazonaws.resources.internal.model.ReferenceModel;
import com.amazonaws.resources.internal.model.ResourceModel;
import com.amazonaws.resources.internal.model.ServiceModel;
import com.amazonaws.resources.internal.model.SubResourceGetterModel;
import com.amazonaws.resources.internal.model.Utils;

/**
* A generic implementation of an arbitrary resource type. Wraps a set of
* metadata about the resource which describe how to map operations on the
* resource to service calls.
*/
public final class ResourceImpl implements ActionContext {

    private final ServiceModel serviceModel;
    private final ResourceModel resourceModel;
    private final Object client;
    private final Map<String, ?> identifiers;

    private volatile Map<String, ?> attributes;

    public ResourceImpl(
            ServiceModel serviceModel,
            ResourceModel resourceModel,
            Object client,
            Map<String, ?> identifiers) {

        this(serviceModel, resourceModel, client, identifiers, null);
    }

    public ResourceImpl(
            ServiceModel serviceModel,
            ResourceModel resourceModel,
            Object client,
            Map<String, ?> identifiers,
            Object data) {

        this.serviceModel = serviceModel;
        this.resourceModel = resourceModel;
        this.client = client;
        this.identifiers = identifiers;

        if (data != null) {
            this.attributes = parseAttributes(resourceModel, data);
        }

        for (Map.Entry<String, IdentifierModel> entry : resourceModel
                .getIdentifiers().entrySet()) {
            if (!(identifiers.containsKey(entry.getKey()))) {
                throw new IllegalArgumentException(
                        "Missing Identifier data. Identifier *" + entry.getKey()
                                + "* expected to construct the resource.");
            }
        }
    }

    @Override
    public boolean hasState() {
        return true;
    }

    @Override
    public ServiceModel getServiceModel() {
        return serviceModel;
    }

    /**
     * @return the model for this resource type
     */
    public ResourceModel getResourceModel() {
        return resourceModel;
    }

    @Override
    public Object getClient() {
        return client;
    }

    /**
     * @return the set of identifiers for this resource
     */
    public Map<String, ?> getIdentifiers() {
        return identifiers;
    }

    @Override
    public Object getIdentifier(String name) {
        return identifiers.get(name);
    }

    /**
     * Returns the value for the given attribute by lazily loading the
     * resource if need be and digging into the result.
     */
    @Override
    public Object getAttribute(String name) {
        if (attributes == null) {
            load(null, null);
        }
        return attributes.get(name);
    }

    private Object getAttributeDataByPath(List<String> path) {
        Object attrObj = getAttribute(path.get(0));

        List<String> pathInAttr = path.subList(1, path.size());
        if (Utils.isMultiValuedPath(path)) {
            return ReflectionUtils.getAllByPath(attrObj, pathInAttr);
        } else {
            return ReflectionUtils.getByPath(attrObj, pathInAttr);
        }
    }

    /**
     * @return true if this resource has been loaded; false otherwise
     */
    public boolean isLoaded() {
        return (attributes != null);
    }

    /**
     * Explicitly loads a representation of this resource by calling the
     * service to retrieve a new set of attributes.
     *
     * @param extractor optional result extractor object
     */
    public synchronized boolean load(
            AmazonWebServiceRequest request,
            ResultCapture<?> extractor) {

        if (attributes != null) {
            return false;
        }

        ActionModel action = resourceModel.getLoadAction();
        if (action == null) {
            throw new UnsupportedOperationException(
                "This resource does not support being loaded.");
        }

        // The facade generator ensures it's only ever possible to pass an
        // instance of the appropriate type.
        @SuppressWarnings("unchecked")
        ResultCapture<Object> erasedExtractor =
                (ResultCapture<Object>) extractor;

        ActionResult result = ActionUtils.perform(
                this, action, request, erasedExtractor);

        this.attributes = parseAttributes(resourceModel, result.getData());

        return true;
    }

    private static Map<String, ?> parseAttributes(
            ResourceModel resourceModel,
            Object object) {

        Map<String, Object> result = new HashMap<>();

        for (String attribute : resourceModel.getAttributes().keySet()) {
            Object value = ReflectionUtils.getByPath(
                    object, Collections.singletonList(attribute));

            result.put(attribute, value);
        }

        return result;
    }

    /**
     * Returns a resource that is referenced by this resource. This may involve
     * a call to the service to dereference this resource if one or more of
     * the identifiers of the referenced resource are contained in the
     * attributes of this resource, and we don't have a fresh set of attributes
     * for this resource in our cache.
     * <p>
     * For example, an S3 Object has a reference to S3 Bucket it belongs to.
     *
     * @param name the name of the reference to follow
     * @return the referenced resource
     */
    public ResourceImpl getReference(String name) {
        ReferenceModel reference = resourceModel.getReference(name);

        if (reference == null) {
            throw new IllegalArgumentException("No reference named " + name);
        }
        if (reference.isMultiValued()) {
            throw new IllegalArgumentException(
                    "The " + name + " reference is multi-valued. You must use "
                    + "the getMultiReference method to resolve it.");
        }

        Map<String, Object> ids = new HashMap<>();

        if (reference.getIdentifierMappings() != null) {
            for (FlatMapping mapping : reference.getIdentifierMappings()) {
                Object value = identifiers.get(mapping.getSource());
                if (value == null) {
                    throw new IllegalStateException(
                            "The " + name + " reference model has a mapping "
                            + "for the " + mapping.getSource() + " identifier, "
                            + "but this resource doesn't have an identifier of "
                            + "that name!");
                }
                ids.put(mapping.getTarget(), value);
            }
        }

        if (reference.getAttributeMappings() != null) {
            for (PathSourceMapping mapping
                    : reference.getAttributeMappings()) {

                // AttributeMapping inside a resource reference has to be
                // single-valued
                if (mapping.isMultiValued()) {
                    throw new IllegalStateException(
                            "The " + name + " reference model has an invalid "
                            + "attribute-mapping where the attribute source "
                            + mapping.getSource() + " is multi-valued.");
                }

                Object value = getAttributeDataByPath(mapping.getSource());
                if (value == null) {
                    // TODO: Check whether the identifier is nullable; if so
                    // it may be fine for this to be null...
                    return null;
                }

                ids.put(mapping.getTarget(), value);
            }
        }

        ResourceModel refTypeModel =
                serviceModel.getResource(reference.getType());

        return new ResourceImpl(serviceModel, refTypeModel, client, ids);
    }

    /**
     * Returns a set of resources that are referenced by this resource. This
     * may involve a call to the service to dereference this resource if we
     * don't have a fresh set of attributes for this resource in our cache.
     * <p>
     * For example, an EC2 Instance has a reference to the set of security
     * groups to which it belongs.
     *
     * @param name the name of the reference
     * @return the list of referenced resources
     */
    public List<ResourceImpl> getMultiReference(String name) {
        ReferenceModel reference = resourceModel.getReference(name);
        if (reference == null) {
            throw new IllegalArgumentException("No reference named " + name);
        }

        Map<String, Object> sharedIds = new HashMap<>();

        if (reference.getIdentifierMappings() != null) {
            for (FlatMapping mapping : reference.getIdentifierMappings()) {
                Object value = identifiers.get(mapping.getSource());
                if (value == null) {
                    throw new IllegalStateException(
                            "The " + name + " reference model has a mapping "
                            + "for the " + mapping.getSource() + " identifier, "
                            + "but this resource doesn't have an identifier of "
                            + "that name!");
                }
                sharedIds.put(mapping.getTarget(), value);
            }
        }

        Map<String, List<?>> ids = new HashMap<>();
        int listSize = -1;

        if (reference.getAttributeMappings() != null) {
            for (PathSourceMapping mapping
                    : reference.getAttributeMappings()) {

                if (mapping.isMultiValued()) {
                    List<?> value =
                            (List<?>) getAttributeDataByPath(mapping.getSource());
                    if (listSize < 0) {
                        listSize = value.size();
                    } else {
                        if (listSize != value.size()) {
                            throw new IllegalStateException(
                                    "List size mismatch! " + listSize + " vs "
                                    + value.size());
                        }
                    }
                    ids.put(mapping.getTarget(), value);
                } else {
                    Object value = getAttributeDataByPath(mapping.getSource());
                    sharedIds.put(mapping.getTarget(), value);
                }
            }
        }

        if (listSize == 0) {
            return Collections.emptyList();
        }

        ResourceModel refTypeModel =
                serviceModel.getResource(reference.getType());

        List<ResourceImpl> rval = new ArrayList<>(listSize);

        for (int i = 0; i < listSize; ++i) {
            Map<String, Object> myIds = new HashMap<>(sharedIds);
            for (Map.Entry<String, List<?>> entry : ids.entrySet()) {
                myIds.put(entry.getKey(), entry.getValue().get(i));
            }
            rval.add(new ResourceImpl(
                    serviceModel, refTypeModel, client, myIds));
        }

        return Collections.unmodifiableList(rval);
    }

    /**
     * Returns a collection of resources which is referenced by this resource.
     * This may involve a call to the service to dereference this resource if
     * the collection has an attribute of this resource as one of its
     * identifiers and we don't have a fresh set of attributes for this
     * resource in our cache. Enumerating the collection always involves a
     * request (or multiple requests) to the service, otherwise this would
     * simply be a multi-valued reference.
     * <p>
     * For example, an S3 Bucket has a reference to a collection of objects.
     *
     * @param name the name of the collection reference
     * @param request client-specified parameters to merge onto the request
     * @return the referenced collection
     */
    public ResourceCollectionImpl getCollection(
            String name,
            AmazonWebServiceRequest request) {

        CollectionModel collection = resourceModel.getCollection(name);
        if (collection == null) {
            throw new IllegalArgumentException("No collection named " + name);
        }

        return new ResourceCollectionImpl(
                this,
                collection.getListAction(),
                request);
    }

    /**
     * Get a subresource of this resource by identifier, (optionally mapping
     * in other identifiers of this resource).
     *
     * @param name the name of the subresource type
     * @param parameter the parameter for this request
     * @return the referenced subresource
     */
    public ResourceImpl getSubResource(String name, Object parameter) {

        SubResourceGetterModel getter =
                resourceModel.getSubResourceGetter(name);
        if (getter == null) {
            throw new UnsupportedOperationException(
                    "No sub-resource named " + name);
        }

        Map<String, Object> ids = new HashMap<>();

        if (getter.getParameterMapping() != null) {
            ids.put(getter.getParameterMapping().getTarget(), parameter);
        }

        if (getter.getIdentifierMappings() != null) {
            for (FlatMapping mapping : getter.getIdentifierMappings()) {
                Object value = identifiers.get(mapping.getSource());
                if (value == null) {
                    throw new IllegalStateException(
                        "The " + name + " subresource model has a mapping "
                        + "for the " + mapping.getSource() + " identifier, but "
                        + "this resource doesn't have an identifier of that "
                        + "name!");
                }
                ids.put(mapping.getTarget(), value);
            }
        }

        ResourceModel refTypeModel = serviceModel.getResource(name);

        return new ResourceImpl(serviceModel, refTypeModel, client, ids);
    }

    /**
     * Performs the given action on this resource. This always involves a
     * request to the service. It may mark the cached attributes of this
     * resource object dirty.
     *
     * @param name the name of the action to perform
     * @param request the client-specified request object
     * @param extractor an optional result extractor object
     * @return the result of executing the action
     */
    public ActionResult performAction(
            String name,
            AmazonWebServiceRequest request,
            ResultCapture<?> extractor) {

        ActionModel action = resourceModel.getAction(name);
        if (action == null) {
            throw new UnsupportedOperationException(
                    "Resource does not support the action " + name);
        }

        // The facade generator will ensure we only ever pass in an
        // appropriately-typed extractor object.
        @SuppressWarnings("unchecked")
        ResultCapture<Object> erasedExtractor =
                (ResultCapture<Object>) extractor;

        return ActionUtils.perform(this, action, request, erasedExtractor);
    }

    @Override
    public String toString() {
        return "{identifiers=" + identifiers
                + ", attributes=" + attributes
                + "}";
    }
}
TOP

Related Classes of com.amazonaws.resources.internal.ResourceImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.