1 /* 2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"). 5 * You may not use this file except in compliance with the License. 6 * A copy of the License is located at 7 * 8 * http://aws.amazon.com/apache2.0 9 * 10 * or in the "license" file accompanying this file. This file is distributed 11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 * express or implied. See the License for the specific language governing 13 * permissions and limitations under the License. 14 */ 15 16 package software.amazon.awssdk.profiles; 17 18 import java.nio.file.Path; 19 import java.util.Collections; 20 import java.util.LinkedHashMap; 21 import java.util.Map; 22 import java.util.Objects; 23 import java.util.Optional; 24 import java.util.concurrent.atomic.AtomicReference; 25 import java.util.function.Supplier; 26 import software.amazon.awssdk.annotations.SdkPublicApi; 27 import software.amazon.awssdk.profiles.internal.ProfileFileRefresher; 28 29 /** 30 * Encapsulates the logic for supplying either a single or multiple ProfileFile instances. 31 * <p> 32 * Each call to the {@link #get()} method will result in either a new or previously supplied profile based on the 33 * implementation's rules. 34 */ 35 @SdkPublicApi 36 @FunctionalInterface 37 public interface ProfileFileSupplier extends Supplier<ProfileFile> { 38 39 /** 40 * Creates a {@link ProfileFileSupplier} capable of producing multiple profile objects by aggregating the default 41 * credentials and configuration files as determined by {@link ProfileFileLocation#credentialsFileLocation()} abd 42 * {@link ProfileFileLocation#configurationFileLocation()}. This supplier will return a new ProfileFile instance only once 43 * either disk file has been modified. Multiple calls to the supplier while both disk files are unchanged will return the 44 * same object. 45 * 46 * @return Implementation of {@link ProfileFileSupplier} that is capable of supplying a new aggregate profile when either file 47 * has been modified. 48 */ defaultSupplier()49 static ProfileFileSupplier defaultSupplier() { 50 Optional<ProfileFileSupplier> credentialsSupplierOptional 51 = ProfileFileLocation.credentialsFileLocation() 52 .map(path -> reloadWhenModified(path, ProfileFile.Type.CREDENTIALS)); 53 54 Optional<ProfileFileSupplier> configurationSupplierOptional 55 = ProfileFileLocation.configurationFileLocation() 56 .map(path -> reloadWhenModified(path, ProfileFile.Type.CONFIGURATION)); 57 58 ProfileFileSupplier supplier = () -> ProfileFile.builder().build(); 59 if (credentialsSupplierOptional.isPresent() && configurationSupplierOptional.isPresent()) { 60 supplier = aggregate(credentialsSupplierOptional.get(), configurationSupplierOptional.get()); 61 } else if (credentialsSupplierOptional.isPresent()) { 62 supplier = credentialsSupplierOptional.get(); 63 } else if (configurationSupplierOptional.isPresent()) { 64 supplier = configurationSupplierOptional.get(); 65 } 66 67 return supplier; 68 } 69 70 /** 71 * Creates a {@link ProfileFileSupplier} capable of producing multiple profile objects from a file. This supplier will 72 * return a new ProfileFile instance only once the disk file has been modified. Multiple calls to the supplier while the 73 * disk file is unchanged will return the same object. 74 * 75 * @param path Path to the file to read from. 76 * @param type The type of file. See {@link ProfileFile.Type} for possible values. 77 * @return Implementation of {@link ProfileFileSupplier} that is capable of supplying a new profile when the file 78 * has been modified. 79 */ reloadWhenModified(Path path, ProfileFile.Type type)80 static ProfileFileSupplier reloadWhenModified(Path path, ProfileFile.Type type) { 81 return new ProfileFileSupplier() { 82 83 final ProfileFile.Builder builder = ProfileFile.builder() 84 .content(path) 85 .type(type); 86 87 final ProfileFileRefresher refresher = ProfileFileRefresher.builder() 88 .profileFile(builder::build) 89 .profileFilePath(path) 90 .build(); 91 92 @Override 93 public ProfileFile get() { 94 return refresher.refreshIfStale(); 95 } 96 97 }; 98 } 99 100 /** 101 * Creates a {@link ProfileFileSupplier} that produces an existing profile. 102 * 103 * @param profileFile Profile object to supply. 104 * @return Implementation of {@link ProfileFileSupplier} that is capable of supplying a single profile. 105 */ fixedProfileFile(ProfileFile profileFile)106 static ProfileFileSupplier fixedProfileFile(ProfileFile profileFile) { 107 return () -> profileFile; 108 } 109 110 /** 111 * Creates a {@link ProfileFileSupplier} by combining the {@link ProfileFile} objects from multiple {@code 112 * ProfileFileSupplier}s. Objects are passed into {@link ProfileFile.Aggregator}. 113 * 114 * @param suppliers Array of {@code ProfileFileSupplier} objects. {@code ProfileFile} objects are passed to 115 * {@link ProfileFile.Aggregator#addFile(ProfileFile)} in the same argument order as the supplier that 116 * generated it. 117 * @return Implementation of {@link ProfileFileSupplier} aggregating results from the supplier objects. 118 */ aggregate(ProfileFileSupplier... suppliers)119 static ProfileFileSupplier aggregate(ProfileFileSupplier... suppliers) { 120 121 return new ProfileFileSupplier() { 122 123 final AtomicReference<ProfileFile> currentAggregateProfileFile = new AtomicReference<>(); 124 final Map<Supplier<ProfileFile>, ProfileFile> currentValuesBySupplier 125 = Collections.synchronizedMap(new LinkedHashMap<>()); 126 127 @Override 128 public ProfileFile get() { 129 boolean refreshAggregate = false; 130 for (ProfileFileSupplier supplier : suppliers) { 131 if (didSuppliedValueChange(supplier)) { 132 refreshAggregate = true; 133 } 134 } 135 136 if (refreshAggregate) { 137 refreshCurrentAggregate(); 138 } 139 140 return currentAggregateProfileFile.get(); 141 } 142 143 private boolean didSuppliedValueChange(Supplier<ProfileFile> supplier) { 144 ProfileFile next = supplier.get(); 145 ProfileFile current = currentValuesBySupplier.put(supplier, next); 146 147 return !Objects.equals(next, current); 148 } 149 150 private void refreshCurrentAggregate() { 151 ProfileFile.Aggregator aggregator = ProfileFile.aggregator(); 152 currentValuesBySupplier.values().forEach(aggregator::addFile); 153 ProfileFile current = currentAggregateProfileFile.get(); 154 ProfileFile next = aggregator.build(); 155 if (!Objects.equals(current, next)) { 156 currentAggregateProfileFile.compareAndSet(current, next); 157 } 158 } 159 160 }; 161 } 162 163 } 164