Package com.amazonaws.resources.internal

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

/*
* 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.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
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.FlatMapping;
import com.amazonaws.resources.internal.model.PathSourceMapping;
import com.amazonaws.resources.internal.model.PathTargetMapping;
import com.amazonaws.resources.internal.model.RequestModel;
import com.amazonaws.resources.internal.model.ResourceMapping;
import com.amazonaws.resources.internal.model.ResourceModel;
import com.amazonaws.resources.internal.model.ResponseModel;

/**
* A helper class containing logic for performing actions on a resource or a
* collection.
*/
final class ActionUtils {

    // TODO: Automate keeping me up to date.
    private static final String USER_AGENT = "Resources/0.0.1";

    /**
     * Performs the given action passing the given parameters.
     *
     * @param context the context on which this action is being called
     * @param action the model of the action to perform
     * @param request the client-supplied request object
     * @param extractor a result extractor object
     * @return the result of the action
     */
    public static ActionResult perform(
            ActionContext context,
            ActionModel action,
            AmazonWebServiceRequest request,
            ResultCapture<Object> extractor) {

        return perform(context, action, request, extractor, null);
    }

    /**
     * Performs the given action passing the given parameters.
     *
     * @param context the context on which this action is being called
     * @param action the model of the action to perform
     * @param request the client-supplied request object
     * @param token the pagination token for the action
     * @param extractor a result extractor object
     * @return the result of the action
     */
    public static ActionResult perform(
            ActionContext context,
            ActionModel action,
            AmazonWebServiceRequest request,
            ResultCapture<Object> extractor,
            Object token) {

        try {

            Method method = findClientMethod(
                    context.getClient(), action.getRequest().getMethod());

            request = generateRequest(
                    context,
                    method.getParameterTypes()[0],
                    action.getRequest(),
                    request,
                    token);

            Object clientResult = method.invoke(context.getClient(), request);

            if (extractor != null) {
                Map<String, String> responseMetadata =
                        getResponseMetadata(context, request);

                extractor.setResponseMetadata(responseMetadata);
                extractor.setClientResult(clientResult);
            }

            ResponseModel responseModel = action.getResponse();

            Object data = null;
            List<ResourceImpl> resources = null;
            Object nextToken = null;

            if (responseModel != null) {
                if (responseModel.getDataMapping() != null) {
                    data = getResultAttributes(
                        responseModel.getDataMapping().getSource(),
                        clientResult);

                } else if (responseModel.getResourceMapping() != null) {
                    resources = getResultResources(
                        context,
                        responseModel.getResourceMapping(),
                        request,
                        clientResult);

                } else {
                    data = clientResult;
                }

                if (responseModel.getNextTokenPath() != null) {
                    nextToken = ReflectionUtils.getByPath(
                        clientResult, responseModel.getNextTokenPath());
                }
            }

            return new ActionResult(data, resources, nextToken);

        } catch (IllegalAccessException | InstantiationException exception) {
            throw new IllegalStateException("BOOM", exception);

        } catch (InvocationTargetException exception) {
            if (exception.getCause() instanceof RuntimeException) {
                throw (RuntimeException) exception.getCause();
            }
            throw new IllegalStateException("BOOM", exception);
        }
    }

    /**
     * Generates a client-level request by extracting the user parameters (if
     */
    private static AmazonWebServiceRequest generateRequest(
            ActionContext context,
            Class<?> type,
            RequestModel model,
            AmazonWebServiceRequest request,
            Object token)
                    throws InstantiationException, IllegalAccessException {

        if (request == null) {
            request = (AmazonWebServiceRequest) type.newInstance();
        }

        request.getRequestClientOptions().appendUserAgent(USER_AGENT);

        for (PathTargetMapping mapping : model.getIdentifierMappings()) {
            Object value = context.getIdentifier(mapping.getSource());
            if (value == null) {
                throw new IllegalStateException(
                        "Action has a mapping for identifier "
                        + mapping.getSource() + ", but the target has no "
                        + "identifier of that name!");
            }
            ReflectionUtils.setByPath(request, value, mapping.getTarget());
        }

        for (PathTargetMapping mapping : model.getAttributeMappings()) {
            Object value = context.getAttribute(mapping.getSource());
            if (value == null) {
                // TODO: Is this ever valid?
                throw new IllegalStateException(
                        "Action has a mapping for attribute "
                        + mapping.getSource() + ", but the target has no "
                        + "attribute of that name!");
            }
            ReflectionUtils.setByPath(request, value, mapping.getTarget());
        }

        for (PathTargetMapping mapping : model.getConstantMappings()) {
            // TODO: This only works for strings; can we do better?
            ReflectionUtils.setByPath(
                    request, mapping.getSource(), mapping.getTarget());
        }

        if (token != null) {
            List<String> tokenPath = model.getTokenPath();
            if (tokenPath == null) {
                throw new IllegalArgumentException(
                        "Cannot pass a token with a null token path");
            }

            ReflectionUtils.setByPath(request, token, tokenPath);
        }

        return request;
    }

    private static Object getResultAttributes(
            List<String> basePath,
            Object result) {

        boolean multivalued = basePath.contains("*");

        if (multivalued) {
            return Collections.unmodifiableList(
                    ReflectionUtils.getAllByPath(result, basePath));
        } else {
            return ReflectionUtils.getByPath(result, basePath);
        }
    }

    private static List<ResourceImpl> getResultResources(
            ActionContext context,
            ResourceMapping mapping,
            AmazonWebServiceRequest request,
            Object result) {

        List<Map<String, Object>> identifiers =
                extractIdentifiers(context, mapping, request, result);

        List<Object> data = null;
        if (mapping.getPath() != null) {
            data = ReflectionUtils.getAllByPath(result, mapping.getPath());
        }

        ResourceModel refTypeModel =
                context.getServiceModel().getResource(mapping.getType());

        List<ResourceImpl> rval = new ArrayList<>(identifiers.size());

        for (int i = 0; i < identifiers.size(); ++i) {
            Object attributes = null;
            if (data != null) {
                attributes = data.get(i);
            }

            rval.add(new ResourceImpl(
                    context.getServiceModel(),
                    refTypeModel,
                    context.getClient(),
                    identifiers.get(i),
                    attributes));
        }

        return Collections.unmodifiableList(rval);
    }

    private static List<Map<String, Object>> extractIdentifiers(
            ActionContext context,
            ResourceMapping mapping,
            AmazonWebServiceRequest request,
            Object result) {

        // Identifier sourced from the parent's resource id can only have one
        // value.
        Map<String, Object> ids =
                extractParentIdentifiers(context, mapping, request);

        // Identifier extracted from the request or response data could be
        // multi-valued.
        Map<String, List<Object>> multiIds =
                extractMultiValuedIdentifiers(mapping, request, result);

        if (multiIds.isEmpty()) {
            return Collections.singletonList(ids);
        }

        List<Map<String, Object>> rval = new ArrayList<>();

        for (Map.Entry<String, List<Object>> entry : multiIds.entrySet()) {
            String key = entry.getKey();
            List<Object> values = entry.getValue();

            for (int i = 0; i < values.size(); ++i) {
                if (rval.size() == i) {
                    rval.add(new HashMap<String, Object>());
                }
                rval.get(i).put(key, values.get(i));
            }
        }

        for (Map.Entry<String, Object> entry : ids.entrySet()) {
            for (Map<String, Object> map : rval) {
                map.put(entry.getKey(), entry.getValue());
            }
        }

        return rval;
    }

    private static Map<String, Object> extractParentIdentifiers(
            ActionContext context,
            ResourceMapping mapping,
            AmazonWebServiceRequest request) {

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

        for (FlatMapping m : mapping.getParentIdentifierMappings()) {
            Object value = context.getIdentifier(m.getSource());
            if (value == null) {
                throw new IllegalStateException(
                        "This action metadata has a mapping "
                        + "for the " + m.getSource() + " identifier, but "
                        + "this resource doesn't have an identifier of that "
                        + "name!");
            }
            ids.put(m.getTarget(), value);
        }

        return ids;
    }

    private static Map<String, List<Object>> extractMultiValuedIdentifiers(
            ResourceMapping mapping,
            AmazonWebServiceRequest request,
            Object result) {

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

        int listSize = -1;

        for (PathSourceMapping m : mapping.getResponseIdentifierMappings()) {
            List<Object> values =
                    ReflectionUtils.getAllByPath(result, m.getSource());

            if (listSize == -1) {
                listSize = values.size();
            } else if (values.size() != listSize) {
                throw new IllegalStateException(
                        "List size mismatch! " + listSize + " vs "
                        + values.size());
            }

            ids.put(m.getTarget(), values);
        }

        for (PathSourceMapping m : mapping.getRequestParamMappings()) {
            List<Object> values;

            /*
             * When the response contains multiple resources, the source of a
             * request param mapping could be either single-valued (e.g. in
             * Glacier.getVaults() action, the single-valued "AccountId" param
             * is mapped to the "AccountId"s of all the returned vaults), or
             * multi-valued (e.g. in EC2.Instance.createTags() action, multiple
             * "Tag[].Key" parameters are mapped to the "Key"s of all the
             * returned Tag resources.
             */
            if (m.isMultiValued()) {
                values =
                        ReflectionUtils.getAllByPath(request, m.getSource());

                if (listSize == -1) {
                    listSize = values.size();
                } else if (values.size() != listSize) {
                    throw new IllegalStateException(
                            "List size mismatch! " + listSize + " vs "
                            + values.size());
                }
            }
            else {
                // If single valued, augment the value into a list of ids, which
                // match the length of the ids extracted from response.
                Object singleValue =
                        ReflectionUtils.getByPath(request, m.getSource());

                values = new ArrayList<Object>(listSize);
                for (int i = 0; i < listSize; i++ ) {
                    values.add(singleValue);
                }
            }

            ids.put(m.getTarget(), values);
        }

        return ids;
    }

    private static Map<String, String> getResponseMetadata(
            ActionContext context,
            Object parameter)
                    throws IllegalAccessException, InvocationTargetException {

        Method method = tryFindClientMethod(
                context.getClient(), "getCachedResponseMetadata");
        if (method == null) {
            return Collections.<String, String>emptyMap();
        }

        Object result = method.invoke(context.getClient(), parameter);
        if (result == null) {
            return Collections.<String, String>emptyMap();
        }

        Map<String, String> metadata = new HashMap<>();

        for (Method getter : result.getClass().getMethods()) {
            String name = getter.getName();
            if (name.startsWith("get")
                    && getter.getParameterTypes().length == 0
                    && getter.getReturnType().equals(String.class)) {

                String value = (String) getter.invoke(result);
                metadata.put(name.substring(3), value);
            }
        }

        return Collections.unmodifiableMap(metadata);
    }

    private static Method findClientMethod(Object client, String name) {
        Method result = tryFindClientMethod(client, name);

        if (result == null) {
            throw new RuntimeException("No client method named " + name);
        }

        return result;
    }

    private static Method tryFindClientMethod(Object client, String name) {
        // TODO: Cache me.
        for (Method method : client.getClass().getMethods()) {
            if (!method.getName().equals(name)) {
                continue;
            }

            Class<?>[] parameters = method.getParameterTypes();
            if (parameters.length != 1) {
                continue;
            }

            // This is the inverse of the normal approach of findMethod() -
            // we're looking for a method which will accept a specific subtype
            // of AmazonWebServiceRequest, without worrying overmuch about
            // what subtype it is. We'll create an object of the appropriate
            // type and fill it in.
            if (AmazonWebServiceRequest.class.isAssignableFrom(parameters[0])) {
                return method;
            }
        }

        return null;
    }

    private ActionUtils() {
    }
}
TOP

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

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.