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.release; 17 18 import static java.nio.charset.StandardCharsets.UTF_8; 19 20 import java.io.IOException; 21 import java.nio.file.FileVisitResult; 22 import java.nio.file.Files; 23 import java.nio.file.Path; 24 import java.nio.file.Paths; 25 import java.nio.file.SimpleFileVisitor; 26 import java.nio.file.attribute.BasicFileAttributes; 27 import java.util.Arrays; 28 import java.util.Collections; 29 import java.util.LinkedHashSet; 30 import java.util.List; 31 import java.util.Set; 32 import java.util.stream.Collectors; 33 import java.util.stream.Stream; 34 import org.apache.commons.cli.CommandLine; 35 import org.apache.commons.io.FileUtils; 36 import org.apache.commons.lang3.StringUtils; 37 import org.w3c.dom.Document; 38 import org.w3c.dom.Node; 39 import software.amazon.awssdk.utils.Validate; 40 import software.amazon.awssdk.utils.internal.CodegenNamingUtils; 41 42 /** 43 * A command line application to create a new, empty service. This *does not* add the new service to the shared pom.xmls, that 44 * should be done via {@link FinalizeNewServiceModuleMain}. 45 * 46 * Example usage: 47 * <pre> 48 * mvn exec:java -pl :release-scripts \ 49 * -Dexec.mainClass="software.amazon.awssdk.release.CreateNewServiceModuleMain" \ 50 * -Dexec.args="--maven-project-root /path/to/root 51 * --maven-project-version 2.1.4-SNAPSHOT 52 * --service-id 'Service Id' 53 * --service-module-name service-module-name 54 * --service-protocol json" 55 * </pre> 56 * 57 * <p>By default the service new pom will include a dependency to the http-auth-aws module, this is only needed if the service 58 * has one or more operations signed by any of the aws algorithms, e.g., sigv4 or sigv4a, but not needed if the service uses, 59 * say, bearer auth (e.g., codecatalyst at the moment). Excluding this can be done by adding the 60 * {@code --exclude-internal-dependency http-auth-aws} switch. For example 61 * <pre> 62 * mvn exec:java -pl :release-scripts \ 63 * -Dexec.mainClass="software.amazon.awssdk.release.CreateNewServiceModuleMain" \ 64 * -Dexec.args="--maven-project-root /path/to/root 65 * --maven-project-version 2.1.4-SNAPSHOT 66 * --service-id 'Service Id' 67 * --service-module-name service-module-name 68 * --service-protocol json 69 * --exclude-internal-dependency http-auth-aws" 70 * </pre> 71 */ 72 public class CreateNewServiceModuleMain extends Cli { 73 74 private static final Set<String> DEFAULT_INTERNAL_DEPENDENCIES = toSet("http-auth-aws"); 75 CreateNewServiceModuleMain()76 private CreateNewServiceModuleMain() { 77 super(requiredOption("service-module-name", "The name of the service module to be created."), 78 requiredOption("service-id", "The service ID of the service module to be created."), 79 requiredOption("service-protocol", "The protocol of the service module to be created."), 80 requiredOption("maven-project-root", "The root directory for the maven project."), 81 requiredOption("maven-project-version", "The maven version of the service module to be created."), 82 optionalMultiValueOption("include-internal-dependency", "Includes an internal dependency from new service pom."), 83 optionalMultiValueOption("exclude-internal-dependency", "Excludes an internal dependency from new service pom.")); 84 } 85 main(String[] args)86 public static void main(String[] args) { 87 new CreateNewServiceModuleMain().run(args); 88 } 89 toSet(String...args)90 static Set<String> toSet(String...args) { 91 Set<String> result = new LinkedHashSet<>(); 92 for (String arg : args) { 93 result.add(arg); 94 } 95 return Collections.unmodifiableSet(result); 96 97 } 98 toList(String[] optionValues)99 static List<String> toList(String[] optionValues) { 100 if (optionValues == null) { 101 return Collections.emptyList(); 102 } 103 return Arrays.asList(optionValues); 104 } 105 computeInternalDependencies(List<String> includes, List<String> excludes)106 static Set<String> computeInternalDependencies(List<String> includes, List<String> excludes) { 107 Set<String> result = new LinkedHashSet<>(DEFAULT_INTERNAL_DEPENDENCIES); 108 result.addAll(includes); 109 excludes.forEach(result::remove); 110 return Collections.unmodifiableSet(result); 111 } 112 113 @Override run(CommandLine commandLine)114 protected void run(CommandLine commandLine) throws Exception { 115 new NewServiceCreator(commandLine).run(); 116 } 117 118 private static class NewServiceCreator { 119 private final Path mavenProjectRoot; 120 private final String mavenProjectVersion; 121 private final String serviceModuleName; 122 private final String serviceId; 123 private final String serviceProtocol; 124 private final Set<String> internalDependencies; 125 NewServiceCreator(CommandLine commandLine)126 private NewServiceCreator(CommandLine commandLine) { 127 this.mavenProjectRoot = Paths.get(commandLine.getOptionValue("maven-project-root").trim()); 128 this.mavenProjectVersion = commandLine.getOptionValue("maven-project-version").trim(); 129 this.serviceModuleName = commandLine.getOptionValue("service-module-name").trim(); 130 this.serviceId = commandLine.getOptionValue("service-id").trim(); 131 this.serviceProtocol = transformSpecialProtocols(commandLine.getOptionValue("service-protocol").trim()); 132 this.internalDependencies = computeInternalDependencies(toList(commandLine 133 .getOptionValues("include-internal-dependency")), 134 toList(commandLine 135 .getOptionValues("exclude-internal-dependency"))); 136 Validate.isTrue(Files.exists(mavenProjectRoot), "Project root does not exist: " + mavenProjectRoot); 137 } 138 transformSpecialProtocols(String protocol)139 private String transformSpecialProtocols(String protocol) { 140 switch (protocol) { 141 case "ec2": return "query"; 142 case "rest-xml": return "xml"; 143 case "rest-json": return "json"; 144 default: return protocol; 145 } 146 } 147 run()148 public void run() throws Exception { 149 Path servicesRoot = mavenProjectRoot.resolve("services"); 150 Path templateModulePath = servicesRoot.resolve("new-service-template"); 151 Path newServiceModulePath = servicesRoot.resolve(serviceModuleName); 152 153 createNewModuleFromTemplate(templateModulePath, newServiceModulePath); 154 replaceTemplatePlaceholders(newServiceModulePath); 155 156 Path newServicePom = newServiceModulePath.resolve("pom.xml"); 157 new AddInternalDependenciesTransformer(internalDependencies).transform(newServicePom); 158 } 159 createNewModuleFromTemplate(Path templateModulePath, Path newServiceModule)160 private void createNewModuleFromTemplate(Path templateModulePath, Path newServiceModule) throws IOException { 161 FileUtils.copyDirectory(templateModulePath.toFile(), newServiceModule.toFile()); 162 } 163 replaceTemplatePlaceholders(Path newServiceModule)164 private void replaceTemplatePlaceholders(Path newServiceModule) throws IOException { 165 Files.walkFileTree(newServiceModule, new SimpleFileVisitor<Path>() { 166 @Override 167 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 168 replacePlaceholdersInFile(file); 169 return FileVisitResult.CONTINUE; 170 } 171 }); 172 } 173 replacePlaceholdersInFile(Path file)174 private void replacePlaceholdersInFile(Path file) throws IOException { 175 String fileContents = new String(Files.readAllBytes(file), UTF_8); 176 String newFileContents = replacePlaceholders(fileContents); 177 Files.write(file, newFileContents.getBytes(UTF_8)); 178 } 179 replacePlaceholders(String line)180 private String replacePlaceholders(String line) { 181 String[] searchList = { 182 "{{MVN_ARTIFACT_ID}}", 183 "{{MVN_NAME}}", 184 "{{MVN_VERSION}}", 185 "{{PROTOCOL}}" 186 }; 187 String[] replaceList = { 188 serviceModuleName, 189 mavenName(serviceId), 190 mavenProjectVersion, 191 serviceProtocol 192 }; 193 return StringUtils.replaceEach(line, searchList, replaceList); 194 } 195 mavenName(String serviceId)196 private String mavenName(String serviceId) { 197 return Stream.of(CodegenNamingUtils.splitOnWordBoundaries(serviceId)) 198 .map(StringUtils::capitalize) 199 .collect(Collectors.joining(" ")); 200 } 201 } 202 203 static class AddInternalDependenciesTransformer extends PomTransformer { 204 private final Set<String> internalDependencies; 205 AddInternalDependenciesTransformer(Set<String> internalDependencies)206 AddInternalDependenciesTransformer(Set<String> internalDependencies) { 207 this.internalDependencies = internalDependencies; 208 } 209 210 @Override updateDocument(Document doc)211 protected void updateDocument(Document doc) { 212 Node project = findChild(doc, "project"); 213 Node dependencies = findChild(project, "dependencies"); 214 for (String internalDependency : internalDependencies) { 215 dependencies.appendChild(sdkDependencyElement(doc, internalDependency)); 216 } 217 } 218 } 219 220 } 221