/******************************************************************************* * Copyright 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. * ***************************************************************************** * __ _ _ ___ * ( )( \/\/ )/ __) * /__\ \ / \__ \ * (_)(_) \/\/ (___/ * * AWS SDK for .NET */ using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; using System.Globalization; using Amazon.Util.Internal; namespace Amazon.Util { public static class PaginatedResourceFactory { public static object Create<TItemType, TRequestType, TResponseType>(PaginatedResourceInfo pri) { pri.Verify(); return Create<TItemType, TRequestType, TResponseType>(pri.Client, pri.MethodName, pri.Request, pri.TokenRequestPropertyPath, pri.TokenResponsePropertyPath, pri.ItemListPropertyPath); } private static PaginatedResource<ItemType> Create<ItemType, TRequestType, TResponseType> (object client, string methodName, object request, string tokenRequestPropertyPath, string tokenResponsePropertyPath, string itemListPropertyPath) { ITypeInfo clientType = TypeFactory.GetTypeInfo(client.GetType()); MethodInfo fetcherMethod = clientType.GetMethod(methodName, new ITypeInfo[] { TypeFactory.GetTypeInfo(typeof(TRequestType)) }); Type funcType = GetFuncType<TRequestType, TResponseType>(); Func<TRequestType, TResponseType> call = (req) => { return (TResponseType)fetcherMethod.Invoke(client, new object[] { req }); }; return Create<ItemType, TRequestType, TResponseType>(call, (TRequestType)request, tokenRequestPropertyPath, tokenResponsePropertyPath, itemListPropertyPath); } private static PaginatedResource<ItemType> Create<ItemType, TRequestType, TResponseType> (Func<TRequestType, TResponseType> call, TRequestType request, string tokenRequestPropertyPath, string tokenResponsePropertyPath, string itemListPropertyPath) { Func<string, Marker<ItemType>> fetcher = token => { List<ItemType> currentItems; string nextToken; SetPropertyValueAtPath(request, tokenRequestPropertyPath, token); TResponseType nextSet = call(request); nextToken = GetPropertyValueFromPath<string>(nextSet, tokenResponsePropertyPath); currentItems = GetPropertyValueFromPath<List<ItemType>>(nextSet, itemListPropertyPath); return new Marker<ItemType>(currentItems, nextToken); }; return new PaginatedResource<ItemType>(fetcher); } private static void SetPropertyValueAtPath(object instance, string path, string value) { String[] propPath = path.Split('.'); object currentValue = instance; Type currentType = instance.GetType(); PropertyInfo currentProperty = null; int i = 0; for (; i < propPath.Length - 1; i++) { string property = propPath[i]; currentProperty = TypeFactory.GetTypeInfo(currentType).GetProperty(property); currentValue = currentProperty.GetValue(currentValue, null); currentType = currentProperty.PropertyType; } currentProperty = TypeFactory.GetTypeInfo(currentType).GetProperty(propPath[i]); currentProperty.SetValue(currentValue, value, null); } private static T GetPropertyValueFromPath<T>(object instance, string path) { String[] propPath = path.Split('.'); object currentValue = instance; Type currentType = instance.GetType(); PropertyInfo currentProperty = null; foreach (string property in propPath) { currentProperty = TypeFactory.GetTypeInfo(currentType).GetProperty(property); currentValue = currentProperty.GetValue(currentValue, null); currentType = currentProperty.PropertyType; } return (T)currentValue; } internal static Type GetPropertyTypeFromPath(Type start, string path) { String[] propPath = path.Split('.'); Type currentType = start; PropertyInfo currentProperty = null; foreach (string property in propPath) { currentProperty = TypeFactory.GetTypeInfo(currentType).GetProperty(property); currentType = currentProperty.PropertyType; } return currentType; } private static Type GetFuncType<T, U>() { return typeof(Func<T, U>); } internal static T Cast<T>(object o) { return (T)o; } } public class PaginatedResourceInfo { private string tokenRequestPropertyPath; private string tokenResponsePropertyPath; internal object Client { get; set; } internal string MethodName { get; set; } internal object Request { get; set; } internal string TokenRequestPropertyPath { get { string ret = tokenRequestPropertyPath; if (String.IsNullOrEmpty(ret)) { ret = "NextToken"; } return ret; } set { tokenRequestPropertyPath = value; } } internal string TokenResponsePropertyPath { get { string ret = tokenResponsePropertyPath; if (String.IsNullOrEmpty(ret)) { ret = "{0}"; if (Client != null && !String.IsNullOrEmpty(MethodName)) { MethodInfo mi = TypeFactory.GetTypeInfo(Client.GetType()).GetMethod(MethodName); if (mi != null) { Type responseType = mi.ReturnType; string baseName = responseType.Name; if (baseName.EndsWith("Response", StringComparison.Ordinal)) { baseName = baseName.Substring(0, baseName.Length - 8); } if (TypeFactory.GetTypeInfo(responseType).GetProperty(string.Format(CultureInfo.InvariantCulture, "{0}Result", baseName)) != null) { ret = string.Format(CultureInfo.InvariantCulture, ret, string.Format(CultureInfo.InvariantCulture, "{0}Result.{1}", baseName, "{0}")); } } } ret = string.Format(CultureInfo.InvariantCulture, ret, "NextToken"); } return ret; } set { tokenResponsePropertyPath = value; } } internal string ItemListPropertyPath { get; set; } public PaginatedResourceInfo WithClient(object client) { Client = client; return this; } public PaginatedResourceInfo WithMethodName(string methodName) { MethodName = methodName; return this; } public PaginatedResourceInfo WithRequest(object request) { Request = request; return this; } public PaginatedResourceInfo WithTokenRequestPropertyPath(string tokenRequestPropertyPath) { TokenRequestPropertyPath = tokenRequestPropertyPath; return this; } public PaginatedResourceInfo WithTokenResponsePropertyPath(string tokenResponsePropertyPath) { TokenResponsePropertyPath = tokenResponsePropertyPath; return this; } public PaginatedResourceInfo WithItemListPropertyPath(string itemListPropertyPath) { ItemListPropertyPath = itemListPropertyPath; return this; } internal void Verify() { //Client is set if (Client == null) { throw new ArgumentException("PaginatedResourceInfo.Client needs to be set."); } //MethodName exists on Client and takes one argument of the declared request type Type clientType = Client.GetType(); MethodInfo mi = TypeFactory.GetTypeInfo(clientType).GetMethod(MethodName, new ITypeInfo[] { TypeFactory.GetTypeInfo(Request.GetType()) }); if (mi == null) { throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "{0} has no method called {1}", clientType.Name, MethodName)); } //Request is valid type. Type requestType = mi.GetParameters()[0].ParameterType; try { Convert.ChangeType(Request, requestType, CultureInfo.InvariantCulture); } catch (Exception) { throw new ArgumentException("PaginatedResourcInfo.Request is an incompatible type."); } //Properties exist Type responseType = mi.ReturnType; VerifyProperty("TokenRequestPropertyPath", requestType, TokenRequestPropertyPath, typeof(string)); VerifyProperty("TokenResponsePropertyPath", responseType, TokenResponsePropertyPath, typeof(string)); VerifyProperty("ItemListPropertyPath", responseType, ItemListPropertyPath, typeof(string), true); } private static void VerifyProperty(string propName, Type start, string path, Type expectedType) { VerifyProperty(propName, start, path, expectedType, false); } private static void VerifyProperty(string propName, Type start, string path, Type expectedType, bool skipTypecheck) { Type type = null; if (String.IsNullOrEmpty(path)) { throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "{0} must contain a value.", propName)); } try { type = PaginatedResourceFactory.GetPropertyTypeFromPath(start, path); } catch (Exception) { throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "{0} does not exist on {1}", path, start.Name)); } if (!skipTypecheck && type != expectedType) { throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "{0} on {1} is not of type {2}", path, start.Name, expectedType.Name)); } } } }