From e5fca0f45c368acde6142c92a5bbd70b4b90d475 Mon Sep 17 00:00:00 2001 From: Yan Zhou Date: Fri, 11 Aug 2023 11:30:35 -0700 Subject: [PATCH 01/14] Rewrite the Java Doc logic in Java 11 APIs and use multi-release jar to be backward compatible with Java 8 upstreams --- .github/workflows/build.yml | 2 +- .github/workflows/publish.yml | 2 +- CHANGELOG.md | 8 +- build.gradle | 7 +- build_script/restModel.gradle | 3 +- .../PegasusPluginCacheabilityTest.groovy | 2 +- .../PegasusPluginIntegrationTest.groovy | 6 +- .../gradle/tasks/GenerateRestClientTask.java | 6 +- gradle.properties | 2 +- restli-tools/build.gradle | 73 +++- .../tools/idlgen/DocletDocsProvider.java | 295 +++++++++++++ .../restli/tools/idlgen/DocletHelper.java | 77 ++++ .../restli/tools/idlgen/RestLiDoclet.java | 405 ++++++++++++++++++ 13 files changed, 875 insertions(+), 13 deletions(-) create mode 100644 restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java create mode 100644 restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletHelper.java create mode 100644 restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/RestLiDoclet.java diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fbeae961eb..28323131f4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - java: [8] # TODO: Add 11 once build issues are resolved + java: [11] # TODO: Add 11 once build issues are resolved name: Java ${{ matrix.java }} on ${{ matrix.os }} steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 032b9cee5a..7b1bd8d73c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - java: [8] + java: [11] name: Java ${{ matrix.java }} steps: - uses: actions/checkout@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index c2ee1b2ecc..62d28a2ecb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,11 +14,16 @@ and what APIs have changed, if applicable. ## [Unreleased] +<<<<<<< HEAD ## [29.45.0] - 2023-08-25 - Downgrade major version back to 29. Technically this is not semver-compatible but we feel that the impact should be less severe than the impact of bumping the major version. +======= +## [30.0.1] - 2023-08-22 +- Rewrite the Java Doc logic in Java 11 APIs and use multi-release jar to be backward compatible with Java 8 upstreams +>>>>>>> d4abb632f (Rewrite the Java Doc logic in Java 11 APIs and use multi-release jar to be backward compatible with Java 8 upstreams) ## [30.0.0] - 2023-08-15 - Remove resetTogglingStores functionality from LoadBalancerClientCli, which is incompatible with Java 17 @@ -5524,7 +5529,8 @@ patch operations can re-use these classes for generating patch messages. ## [0.14.1] -[Unreleased]: https://github.com/linkedin/rest.li/compare/v29.45.0...master +[Unreleased]: https://github.com/linkedin/rest.li/compare/v29.46.0...master +[29.46.0]: https://github.com/linkedin/rest.li/compare/v29.45.0...v29.46.0 [29.45.0]: https://github.com/linkedin/rest.li/compare/v30.0.0...v29.45.0 [30.0.0]: https://github.com/linkedin/rest.li/compare/v29.44.0...v30.0.0 [29.44.0]: https://github.com/linkedin/rest.li/compare/v29.43.11...v29.44.0 diff --git a/build.gradle b/build.gradle index 453c2a71a5..7b4992ca89 100644 --- a/build.gradle +++ b/build.gradle @@ -153,7 +153,12 @@ allprojects { throw new GradleScriptException("Pegasus required Java 8 or later to build, current version: ${JavaVersion.current()}", null) } // for all supported versions that we test build, fail the build if any compilation warnings are reported - compile.options.compilerArgs = ['-Xlint', '-Xlint:-path', '-Xlint:-static', '-Werror'] + compile.options.compilerArgs = ['-Xlint', '-Xlint:-path', '-Xlint:-static'] + // Set the default Java bytecode level to version 8 to ensure backward compatibility + if (JavaVersion.current() >= JavaVersion.VERSION_11) { + sourceCompatibility = 8 + targetCompatibility = 8 + } } tasks.withType(Javadoc) diff --git a/build_script/restModel.gradle b/build_script/restModel.gradle index d4a23865fa..27c4968e73 100644 --- a/build_script/restModel.gradle +++ b/build_script/restModel.gradle @@ -79,7 +79,8 @@ project.sourceSets.all { SourceSet sourceSet -> project.tasks[sourceSet.compileJavaTaskName].dependsOn(rootProject.ext.build.restModelGenerateTasks[sourceSet]) } - final Task jarTask = project.tasks[sourceSet.getTaskName('', 'jar')] + // Use 'jar' instead a custom task name for Java 11 sourceSet in the multi-release jar + final Task jarTask = project.tasks[sourceSet.getName().endsWith('11') ? 'jar' : sourceSet.getTaskName('', 'jar')] jarTask.from(inputParentDirPath) { include "${pegasusDirName}${File.separatorChar}**${File.separatorChar}*.pdsc" include "${pegasusDirName}${File.separatorChar}**${File.separatorChar}*.pdl" diff --git a/gradle-plugins/src/integTest/groovy/com/linkedin/pegasus/gradle/PegasusPluginCacheabilityTest.groovy b/gradle-plugins/src/integTest/groovy/com/linkedin/pegasus/gradle/PegasusPluginCacheabilityTest.groovy index dad8526c8d..2c4926798e 100644 --- a/gradle-plugins/src/integTest/groovy/com/linkedin/pegasus/gradle/PegasusPluginCacheabilityTest.groovy +++ b/gradle-plugins/src/integTest/groovy/com/linkedin/pegasus/gradle/PegasusPluginCacheabilityTest.groovy @@ -96,6 +96,6 @@ class PegasusPluginCacheabilityTest extends Specification { preparedSchema.exists() where: - gradleVersion << [ '4.0', '5.2.1', '5.6.4', '6.9', '7.0.2' ] + gradleVersion << [ '5.2.1', '5.6.4', '6.9', '7.0.2' ] } } diff --git a/gradle-plugins/src/integTest/groovy/com/linkedin/pegasus/gradle/PegasusPluginIntegrationTest.groovy b/gradle-plugins/src/integTest/groovy/com/linkedin/pegasus/gradle/PegasusPluginIntegrationTest.groovy index ea6002f4ea..bbd1dca7f3 100644 --- a/gradle-plugins/src/integTest/groovy/com/linkedin/pegasus/gradle/PegasusPluginIntegrationTest.groovy +++ b/gradle-plugins/src/integTest/groovy/com/linkedin/pegasus/gradle/PegasusPluginIntegrationTest.groovy @@ -34,7 +34,7 @@ class PegasusPluginIntegrationTest extends Specification { result.task(':mainDataTemplateJar').outcome == SUCCESS where: - gradleVersion << [ '4.0', '5.2.1', '5.6.4', '6.9', '7.0.2', '7.5.1' ] + gradleVersion << [ '5.2.1', '5.6.4', '6.9', '7.0.2', '7.5.1' ] } @Unroll @@ -102,7 +102,7 @@ class PegasusPluginIntegrationTest extends Specification { assertZipContains(dataTemplateArtifact, 'extensions/com/linkedin/LatLongExtensions.pdl') where: - gradleVersion << [ '4.0', '5.2.1', '5.6.4', '6.9', '7.0.2', '7.5.1' ] + gradleVersion << [ '5.2.1', '5.6.4', '6.9', '7.0.2', '7.5.1' ] } def 'mainCopySchema task will remove stale PDSC'() { @@ -262,7 +262,7 @@ class PegasusPluginIntegrationTest extends Specification { result.task(':impl:compileJava').outcome == SUCCESS where: - gradleVersion << [ '4.0', '5.2.1', '5.6.4', '6.9', '7.0.2', '7.5.1' ] + gradleVersion << [ '5.2.1', '5.6.4', '6.9', '7.0.2', '7.5.1' ] } private static boolean assertZipContains(File zip, String path) { diff --git a/gradle-plugins/src/main/java/com/linkedin/pegasus/gradle/tasks/GenerateRestClientTask.java b/gradle-plugins/src/main/java/com/linkedin/pegasus/gradle/tasks/GenerateRestClientTask.java index af0e9a5e94..b0e39db720 100644 --- a/gradle-plugins/src/main/java/com/linkedin/pegasus/gradle/tasks/GenerateRestClientTask.java +++ b/gradle-plugins/src/main/java/com/linkedin/pegasus/gradle/tasks/GenerateRestClientTask.java @@ -206,7 +206,9 @@ public void generate() }).assertNormalExitValue() ); - // Fluent API generator will not generate classes for schemas referenced from IDLs (eg, FooArray for list params). + // We are commenting out the following block because FluentApiGenerator is not actively being used and it is causing + // "FileNotFoundException: JAR entry a not found in restli-tools-30.0.1.jar" when using multi-release jar. +/* // Fluent API generator will not generate classes for schemas referenced from IDLs (eg, FooArray for list params). // These are already generated by the request builder generators and will be reused. if (!fluentApiFiles.isEmpty()) { @@ -229,7 +231,7 @@ public void generate() javaExecSpec.args("--targetDir", _destinationDir.getAbsolutePath()); javaExecSpec.args(sources); }).assertNormalExitValue(); - } + }*/ } diff --git a/gradle.properties b/gradle.properties index e8687a643d..4c225438cd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=29.45.0 +version=29.46.0 group=com.linkedin.pegasus org.gradle.configureondemand=true org.gradle.parallel=true diff --git a/restli-tools/build.gradle b/restli-tools/build.gradle index 05805febdb..8df74b388f 100644 --- a/restli-tools/build.gradle +++ b/restli-tools/build.gradle @@ -1,3 +1,41 @@ +plugins { + id "java-library" +} + +// This block is only supported and required when building with JDK11+ +if (JavaVersion.current() >= JavaVersion.VERSION_11) { + //We need a custom source set for JDK11+ classes + sourceSets { + java11 { + java { + srcDirs = ['src/main/java11'] + } + } + } + // This compile task is automatically generated by java-library plugin for custom JDK11 only source set + // We need to explicitly set code versions and override defaults + compileJava11Java { + sourceCompatibility = 11 + targetCompatibility = 11 + options.compilerArgs.addAll(['--release', '11']) + } + + jar { + // We package JDK11+ classes into a custom folder. + // JVM will load the class if version of the class is equal or less than version of JVM. + // Thus JDK8 or JDK9 will load default class from "com" folder and JDK11+ will load the custom folder + into('META-INF/versions/11') { + from sourceSets.java11.output + } + manifest { + attributes( + "Manifest-Version": "1.0", + "Multi-Release": true + ) + } + } +} + dependencies { compile project(':data') compile project(':r2-core') @@ -14,7 +52,13 @@ dependencies { compile externalDependency.commonsLang compile externalDependency.jacksonCore compile externalDependency.jacksonDataBind - compile externalDependency.jdkTools + // tools.jar, which is packaged by jdkTools, have been removed after Java 11 and most of the functionalities have been move to JDK internals. + // So using jdkTools after Java 11 will result in "file dose not exist" error. Depending on the error handling, there are 2 possible outcomes: + // 1. falling back to JDK internal implementations. + // 2. not falling back to JDK internal implementations and throwing a build error. This is the case for protobuf plugin. + if (JavaVersion.current() < JavaVersion.VERSION_11) { + compile externalDependency.jdkTools + } compile externalDependency.velocity testCompile externalDependency.mockito @@ -22,6 +66,33 @@ dependencies { testCompile externalDependency.junit testCompile externalDependency.commonsHttpClient testCompile externalDependency.javaparser + + if (JavaVersion.current() >= JavaVersion.VERSION_11) { + // Custom dependency set is required for JDK11+ only source set + java11Implementation files(sourceSets.main.output.classesDirs) + java11Compile project(':data') + java11Compile project(':r2-core') + java11Compile project(':li-jersey-uri') + java11Compile project(':generator') + java11Compile project(':pegasus-common') + java11Compile project(':restli-common') + java11Compile project(':restli-client') + java11Compile project(':restli-server') + java11Compile externalDependency.caffeine + java11Compile externalDependency.commonsIo + java11Compile externalDependency.codemodel + java11Compile externalDependency.commonsCli + java11Compile externalDependency.commonsLang + java11Compile externalDependency.jacksonCore + java11Compile externalDependency.jacksonDataBind + java11Compile externalDependency.velocity + + java11Compile externalDependency.mockito + java11Compile externalDependency.testng + java11Compile externalDependency.junit + java11Compile externalDependency.commonsHttpClient + java11Compile externalDependency.javaparser + } } apply from: "${buildScriptDirPath}/restModel.gradle" diff --git a/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java b/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java new file mode 100644 index 0000000000..5817e33b0e --- /dev/null +++ b/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java @@ -0,0 +1,295 @@ +/* + Copyright (c) 2012 LinkedIn Corp. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License 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.linkedin.restli.tools.idlgen; + + +import com.linkedin.restli.internal.server.model.ResourceModelEncoder.DocsProvider; +import com.linkedin.restli.server.annotations.ActionParam; +import com.linkedin.restli.server.annotations.QueryParam; + +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.FileVisitResult; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.sun.source.doctree.DocTree; +import com.sun.source.doctree.ReturnTree; +import com.sun.source.doctree.UnknownBlockTagTree; +import org.apache.commons.io.output.NullWriter; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; + + +/** + * Specialized {@link DocsProvider} whose documentation comes from the Javadoc Doclet {@link RestLiDoclet}. + * + * @author dellamag + */ +public class DocletDocsProvider implements DocsProvider +{ + private static final Logger log = LoggerFactory.getLogger(DocletDocsProvider.class); + + private final String _apiName; + private final String[] _classpath; + private final String[] _sourcePaths; + private final String[] _resourcePackages; + + private RestLiDoclet _doclet; + + public DocletDocsProvider(String apiName, + String[] classpath, + String[] sourcePaths, + String[] resourcePackages) + { + _apiName = apiName; + _classpath = classpath; + _sourcePaths = sourcePaths; + _resourcePackages = resourcePackages; + } + + @Override + public Set supportedFileExtensions() + { + return Collections.singleton(".java"); + } + + /** + * Recursively collect all Java file paths under the sourcePaths if packageNames is null or empty. Else, only + * collect the Java file paths whose package name starts with packageNames. + * + * @param sourcePaths source paths to be queried + * @param packageNames target package names to be matched + * @return list of Java file paths + */ + public static List collectSourceFiles(List sourcePaths, List packageNames) throws IOException { + List sourceFiles = new ArrayList<>(); + for (String sourcePath : sourcePaths) { + Path basePath = Paths.get(sourcePath); + if (!Files.exists(basePath)) { + continue; + } + Files.walkFileTree(basePath, new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + if (file.toString().endsWith(".java")) { + if (packageNames == null || packageNames.isEmpty()) { + sourceFiles.add(file.toString()); + } else { + String packageName = basePath.relativize(file.getParent()).toString().replace('/', '.'); + for (String targetPackageName : packageNames) { + if (packageName.startsWith(targetPackageName)) { + sourceFiles.add(file.toString()); + break; + } + } + } + } + return FileVisitResult.CONTINUE; + } + }); + } + return sourceFiles; + } + + @Override + public void registerSourceFiles(Collection sourceFileNames) + { + log.debug("Executing Javadoc tool..."); + final String flatClasspath; + if (_classpath == null) + { + flatClasspath = System.getProperty("java.class.path"); + } + else + { + flatClasspath = StringUtils.join(_classpath, ":"); + } + + final PrintWriter sysoutWriter = new PrintWriter(System.out, true); + final PrintWriter nullWriter = new PrintWriter(new NullWriter()); + + + List sourceFiles; + try + { + sourceFiles = collectSourceFiles(Arrays.asList(_sourcePaths), + _resourcePackages == null ? null : Arrays.asList(_resourcePackages)); + } + catch (IOException e) + { + throw new RuntimeException("Failed to collect source files", e); + } + + _doclet = RestLiDoclet.generateDoclet(_apiName, + sysoutWriter, + nullWriter, + nullWriter, + flatClasspath, + sourceFiles + ); + } + + @Override + public String getClassDoc(Class resourceClass) + { + final TypeElement doc = _doclet.getClassDoc(resourceClass); + if (doc == null) + { + return null; + } + return buildDoc(_doclet.getDocCommentStrForElement(doc)); + } + + public String getClassDeprecatedTag(Class resourceClass) { + TypeElement typeElement = _doclet.getClassDoc(resourceClass); + if (typeElement == null) { + return null; + } + return formatDeprecatedTags(typeElement); + } + + private String formatDeprecatedTags(Element element) { + List deprecatedTags = _doclet.getDeprecatedTags(element); + if (!deprecatedTags.isEmpty()) { + StringBuilder deprecatedText = new StringBuilder(); + for (int i = 0; i < deprecatedTags.size(); i++) { + deprecatedText.append(deprecatedTags.get(i)); + if (i < deprecatedTags.size() - 1) { + deprecatedText.append(" "); + } + } + return deprecatedText.toString(); + } else { + return null; + } + } + @Override + public String getMethodDoc(Method method) + { + final ExecutableElement doc = _doclet.getMethodDoc(method); + if (doc == null) + { + return null; + } + + return buildDoc(_doclet.getDocCommentStrForElement(doc)); + } + + @Override + public String getMethodDeprecatedTag(Method method) + { + final ExecutableElement doc = _doclet.getMethodDoc(method); + if (doc == null) + { + return null; + } + + return formatDeprecatedTags(doc); + } + + + @Override + public String getParamDoc(Method method, String name) + { + final ExecutableElement methodDoc = _doclet.getMethodDoc(method); + + if (methodDoc == null) + { + return null; + } + Map paramTags = _doclet.getParamTags(methodDoc); + for (VariableElement parameter : methodDoc.getParameters()) + { + for (AnnotationMirror annotationMirror : parameter.getAnnotationMirrors()) + { + if (isQueryParamAnnotation(annotationMirror) || isActionParamAnnotation(annotationMirror)) + { + for (Map.Entry entry : annotationMirror.getElementValues().entrySet()) + { + if ("value".equals(entry.getKey().getSimpleName().toString()) && name.equals(entry.getValue().getValue())) + { + return paramTags.get(parameter.getSimpleName().toString()); + } + } + } + } + } + + return null; + } + + @Override + public String getReturnDoc(Method method) + { + ExecutableElement methodElement = _doclet.getMethodDoc(method); + if (methodElement != null) { + for (DocTree docTree : _doclet.getDocCommentTreeForMethod(method).getBlockTags()) { + if (!docTree.toString().toLowerCase().startsWith("@return")) { + continue; + } + DocTree.Kind kind = docTree.getKind(); + if (kind == DocTree.Kind.RETURN) { + ReturnTree returnTree = (ReturnTree) docTree; + return buildDoc(returnTree.getDescription().toString()); + } else if (kind == DocTree.Kind.UNKNOWN_BLOCK_TAG) { + UnknownBlockTagTree unknownBlockTagTree = (UnknownBlockTagTree) docTree; + return buildDoc(unknownBlockTagTree.getContent().toString()); + } + } + } + return null; + } + + private static String buildDoc(String docText) + { + if (docText != null && !docText.isEmpty()) + { + return DocletHelper.processDocCommentStr(docText); + } + return null; + } + + private static boolean isQueryParamAnnotation(AnnotationMirror annotationMirror) + { + return QueryParam.class.getCanonicalName().equals(annotationMirror.getAnnotationType().toString()); + } + + private static boolean isActionParamAnnotation(AnnotationMirror annotationMirror) + { + return ActionParam.class.getCanonicalName().equals(annotationMirror.getAnnotationType().toString()); + } +} \ No newline at end of file diff --git a/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletHelper.java b/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletHelper.java new file mode 100644 index 0000000000..5581034aa4 --- /dev/null +++ b/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletHelper.java @@ -0,0 +1,77 @@ +/* + Copyright (c) 2012 LinkedIn Corp. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License 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.linkedin.restli.tools.idlgen; + +import com.sun.source.doctree.DocCommentTree; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +/** + * Helper class that defines generic util methods related to {@link jdk.javadoc.doclet.Doclet}. + * + * @author Yan Zhou + */ +public class DocletHelper { + /** + * Get the canonical name of the inputTypeStr, which does not include any reference to its formal type parameter + * when it comes to generic type. For example, the canonical name of the interface java.util.Set is java.util.Set. + * + * @param inputTypeStr class/method/variable type str + * @return canonical name of the inputTypeStr + */ + public static String getCanonicalName(String inputTypeStr) { + if (inputTypeStr == null) { + return null; + } + Pattern pattern = Pattern.compile("<.*>"); + Matcher matcher = pattern.matcher(inputTypeStr); + StringBuilder sb = new StringBuilder(); + int start = 0; + while (matcher.find()) { + sb.append(inputTypeStr.substring(start, matcher.start())); + start = matcher.end(); + } + sb.append(inputTypeStr.substring(start)); + return sb.toString(); + } + + /** + * When {@link DocCommentTree} return Java Doc comment string, they wrap certain chars with commas. For example, + *

will become ,

, This method serves to remove such redundant commas if any. + * + * @param inputCommentStr input Java Doc comment string generated by {@link DocCommentTree} + * @return processed string with redundant commas removed + */ + public static String processDocCommentStr(String inputCommentStr) { + if (inputCommentStr == null) { + return null; + } + Pattern pattern = Pattern.compile("(\\,)(<.*>|\\{@.*\\}|>|<)(\\,)?"); + Matcher matcher = pattern.matcher(inputCommentStr); + StringBuilder sb = new StringBuilder(); + int start = 0; + while (matcher.find()) { + sb.append(inputCommentStr.substring(start, matcher.start())); + int end = matcher.group(3) == null ? matcher.end() : matcher.end() - 1; + sb.append(inputCommentStr.substring(matcher.start() + 1, end).replace(",", "")); + start = matcher.end(); + } + sb.append(inputCommentStr.substring(start)); + return sb.toString(); + } +} \ No newline at end of file diff --git a/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/RestLiDoclet.java b/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/RestLiDoclet.java new file mode 100644 index 0000000000..a50824e26e --- /dev/null +++ b/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/RestLiDoclet.java @@ -0,0 +1,405 @@ +/* + Copyright (c) 2012 LinkedIn Corp. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License 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.linkedin.restli.tools.idlgen; + + +import com.sun.source.doctree.DocCommentTree; +import com.sun.source.doctree.DocTree; +import com.sun.source.doctree.ParamTree; +import com.sun.source.doctree.DeprecatedTree; +import jdk.javadoc.doclet.Doclet; +import jdk.javadoc.doclet.DocletEnvironment; +import jdk.javadoc.doclet.Reporter; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +import javax.tools.DocumentationTool; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; +import java.io.PrintWriter; +import java.lang.reflect.Method; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + + +/** + * Custom Javadoc processor that merges documentation into the restspec.json. The embedded Javadoc + * generator is basically a commandline tool wrapper and it runs in complete isolation from the rest + * of the application. Due to the fact that the Javadoc tool instantiates RestLiDoclet, we cannot + * cleanly integrate the output into the {@link RestLiResourceModelExporter} tool. Thus, we're just + * dumping the docs into a static Map which can be accessed by {@link RestLiResourceModelExporter}. + * + * This class supports multiple runs of Javadoc Doclet API {@link DocumentationTool}. + * Each run will be assigned an unique "Doclet ID", returned by + * {@link #generateDoclet(String, java.io.PrintWriter, java.io.PrintWriter, java.io.PrintWriter, String, List)}. + * The Doclet ID should be subsequently used to initialize {@link DocletDocsProvider}. + * + * This class is thread-safe. However, #generateJavadoc() will be synchronized. + * + * @author dellamag + */ +public class RestLiDoclet implements Doclet +{ + private static RestLiDoclet _currentDocLet = null; + private final DocInfo _docInfo; + private final DocletEnvironment _docEnv; + + /** + * Generate Javadoc and return the generated RestLiDoclet instance. + * This method is synchronized. + * + * @param programName Name of the program (for error messages). + * @param errWriter PrintWriter to receive error messages. + * @param warnWriter PrintWriter to receive warning messages. + * @param noticeWriter PrintWriter to receive notice messages. + * @param flatClassPath Flat path to classes to be used. + * @param sourceFiles List of Java source files to be analyzed. + * @return the generated RestLiDoclet instance. + * @throws IllegalArgumentException if Javadoc fails to generate docs. + */ + public static synchronized RestLiDoclet generateDoclet(String programName, + PrintWriter errWriter, + PrintWriter warnWriter, + PrintWriter noticeWriter, + String flatClassPath, + List sourceFiles + ) + { + noticeWriter.println("Generating Javadoc for " + programName); + + DocumentationTool docTool = ToolProvider.getSystemDocumentationTool(); + StandardJavaFileManager fileManager = docTool.getStandardFileManager(null, null, null); + Iterable fileObjects = fileManager.getJavaFileObjectsFromPaths( + sourceFiles.stream().map(Paths::get).collect(Collectors.toList())); + + // Set up the Javadoc task options + List taskOptions = new ArrayList<>(); + taskOptions.add("-classpath"); + taskOptions.add(flatClassPath); + + // Create and run the Javadoc task + DocumentationTool.DocumentationTask task = docTool.getTask(errWriter, + fileManager, diagnostic -> { + switch (diagnostic.getKind()) { + case ERROR: + errWriter.println(diagnostic.getMessage(Locale.getDefault())); + break; + case WARNING: + warnWriter.println(diagnostic.getMessage(Locale.getDefault())); + break; + case NOTE: + noticeWriter.println(diagnostic.getMessage(Locale.getDefault())); + break; + } + }, + RestLiDoclet.class, + taskOptions, + fileObjects); + + boolean success = task.call(); + if (!success) + { + throw new IllegalArgumentException("Javadoc generation failed"); + } + + return _currentDocLet; + } + + /** + * Entry point for Javadoc Doclet. + * + * @param docEnv {@link DocletEnvironment} passed in by Javadoc + * @return is successful or not + */ + @Override + public boolean run(DocletEnvironment docEnv) { + final DocInfo docInfo = new DocInfo(); + + // Iterate through the TypeElements (class and interface declarations) + for (Element element : docEnv.getIncludedElements()) { + if (element instanceof TypeElement) { + TypeElement typeElement = (TypeElement) element; + docInfo.setClassDoc(typeElement.getQualifiedName().toString(), typeElement); + + // Iterate through the methods of the TypeElement + for (Element enclosedElement : typeElement.getEnclosedElements()) { + if (enclosedElement instanceof ExecutableElement) { + ExecutableElement methodElement = (ExecutableElement) enclosedElement; + docInfo.setMethodDoc(MethodIdentity.create(methodElement), methodElement); + } + } + } + } + + _currentDocLet = new RestLiDoclet(docInfo, docEnv); + + return true; + } + + @Override + public void init(Locale locale, Reporter reporter) { + // no-ops + } + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Set getSupportedOptions() { + return Set.of(); + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latest(); + } + + private RestLiDoclet(DocInfo docInfo, DocletEnvironment docEnv) + { + _docInfo = docInfo; + _docEnv = docEnv; + } + + /** + * The reason why we create a public empty constructor is because JavadocTaskImpl in JDK 11 requires it when using reflection. + * Otherwise, there will be NoSuchMethodException: com.linkedin.restli.tools.idlgen.RestLiDoclet.() + */ + public RestLiDoclet() { + _docInfo = null; + _docEnv = null; + } + + /** + * Query Javadoc {@link TypeElement} for the specified resource class. + * + * @param resourceClass resource class to be queried + * @return corresponding {@link TypeElement} + */ + public TypeElement getClassDoc(Class resourceClass) + { + return _docInfo.getClassDoc(resourceClass.getCanonicalName()); + } + + /** + * Query Javadoc {@link ExecutableElement} for the specified Java method. + * + * @param method Java method to be queried + * @return corresponding {@link ExecutableElement} + */ + public ExecutableElement getMethodDoc(Method method) + { + final MethodIdentity methodId = MethodIdentity.create(method); + return _docInfo.getMethodDoc(methodId); + } + + private static class DocInfo + { + public TypeElement getClassDoc(String className) { + return _classNameToClassDoc.get(className); + } + + public ExecutableElement getMethodDoc(MethodIdentity methodId) { + return _methodIdToMethodDoc.get(methodId); + } + + public void setClassDoc(String className, TypeElement classDoc) { + _classNameToClassDoc.put(className, classDoc); + } + + public void setMethodDoc(MethodIdentity methodId, ExecutableElement methodDoc) { + _methodIdToMethodDoc.put(methodId, methodDoc); + } + + private final Map _classNameToClassDoc = new HashMap<>(); + private final Map _methodIdToMethodDoc = new HashMap<>(); + } + + private static class MethodIdentity + { + public static MethodIdentity create(Method method) + { + final List parameterTypeNames = new ArrayList<>(); + + // type parameters are not included in identity because of differences between reflection and Doclet: + // e.g. for Collection: + // reflection Type.toString() -> Collection + // Doclet Type.toString() -> Collection + for (Class paramClass: method.getParameterTypes()) + { + parameterTypeNames.add(paramClass.getCanonicalName()); + } + + return new MethodIdentity(method.getDeclaringClass().getName() + "." + method.getName(), parameterTypeNames); + } + + public static MethodIdentity create(ExecutableElement method) + { + final List parameterTypeNames = new ArrayList<>(); + for (VariableElement param : method.getParameters()) { + TypeMirror type = param.asType(); + parameterTypeNames.add(DocletHelper.getCanonicalName(type.toString())); + } + + return new MethodIdentity(method.getEnclosingElement().toString() + "." + method.getSimpleName().toString(), + parameterTypeNames); + } + + private MethodIdentity(String methodQualifiedName, List parameterTypeNames) + { + _methodQualifiedName = methodQualifiedName; + _parameterTypeNames = parameterTypeNames; + } + + @Override + public int hashCode() + { + return new HashCodeBuilder(17, 29). + append(_methodQualifiedName). + append(_parameterTypeNames). + toHashCode(); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + + if (obj == null) + { + return false; + } + + if (getClass() != obj.getClass()) + { + return false; + } + + final MethodIdentity other = (MethodIdentity) obj; + return new EqualsBuilder(). + append(_methodQualifiedName, other._methodQualifiedName). + append(_parameterTypeNames, other._parameterTypeNames). + isEquals(); + } + + private final String _methodQualifiedName; + private final List _parameterTypeNames; + } + + /** + * Get the list of deprecated tags for the specified element. + * + * @param element {@link Element} to be queried + * @return list of deprecated tags for the specified element + */ + public List getDeprecatedTags(Element element) { + List deprecatedTags = new ArrayList<>(); + DocCommentTree docCommentTree = getDocCommentTreeForElement(element); + if (docCommentTree == null) { + return deprecatedTags; + } + for (DocTree docTree :docCommentTree.getBlockTags()) { + if (docTree.getKind() == DocTree.Kind.DEPRECATED) { + DeprecatedTree deprecatedTree = (DeprecatedTree) docTree; + String deprecatedComment = deprecatedTree.getBody().toString(); + deprecatedTags.add(deprecatedComment); + } + } + return deprecatedTags; + } + + /** + * Get the map from param name to param comment for the specified executableElement. + * + * @param executableElement {@link ExecutableElement} to be queried + * @return map from param name to param comment for the specified executableElement + */ + public Map getParamTags(ExecutableElement executableElement) { + Map paramTags = new HashMap<>(); + DocCommentTree docCommentTree = getDocCommentTreeForElement(executableElement); + if (docCommentTree == null) { + return paramTags; + } + for (DocTree docTree : docCommentTree.getBlockTags()) { + if (docTree.getKind() == DocTree.Kind.PARAM) { + ParamTree paramTree = (ParamTree) docTree; + String paramName = paramTree.getName().toString(); + String paramComment = paramTree.getDescription().toString(); + if (paramComment != null) { + paramTags.put(paramName, paramComment); + } + } + } + return paramTags; + } + + /** + * Get the {@link DocCommentTree} for the specified element. + * + * @param element {@link Element} to be queried + * @return {@link DocCommentTree} for the specified element + */ + public DocCommentTree getDocCommentTreeForElement(Element element) { + return element == null ? null : _docEnv.getDocTrees().getDocCommentTree(element); + } + + /** + * Get the Doc Comment string for the specified element. + * + * @param element {@link Element} to be queried + * @return Doc Comment string for the specified element + */ + public String getDocCommentStrForElement(Element element) { + DocCommentTree docCommentTree = getDocCommentTreeForElement(element); + return docCommentTree == null ? null : docCommentTree.getFullBody().toString(); + } + + /** + * Get the {@link DocCommentTree} for the specified method. + * + * @param method {@link Method} to be queried + * @return {@link DocCommentTree} for the specified method + */ + public DocCommentTree getDocCommentTreeForMethod(Method method) { + TypeElement typeElement = getClassDoc(method.getDeclaringClass()); + if (typeElement == null) { + return null; + } + for (Element element : typeElement.getEnclosedElements()) { + if (element.getSimpleName().toString().equals(method.getName())) { + return getDocCommentTreeForElement(element); + } + } + return null; + } +} \ No newline at end of file From 012ded37ff7f4286a33e77fc8601e107216dbe7e Mon Sep 17 00:00:00 2001 From: Yan Zhou Date: Fri, 18 Aug 2023 15:49:30 -0700 Subject: [PATCH 02/14] Update github workflow to use Java 11 --- .github/workflows/build.yml | 1 - .github/workflows/publish.yml | 1 - build.gradle | 7 +++++++ d2-int-test/build.gradle | 4 ++++ d2/build.gradle | 4 ++++ restli-tools/build.gradle | 10 ++-------- 6 files changed, 17 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 28323131f4..e3c5dd3ef4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,6 @@ jobs: fetch-depth: 2 - uses: actions/setup-java@v2 with: - distribution: zulu java-version: ${{ matrix.java }} cache: gradle - run: ./.github/scripts/build.sh diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7b1bd8d73c..4df971058d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -19,7 +19,6 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-java@v2 with: - distribution: zulu java-version: ${{ matrix.java }} # Do NOT use caching, since we want to ensure published artifacts are fresh - run: ./.github/scripts/publish.sh diff --git a/build.gradle b/build.gradle index 7b4992ca89..eceb6c73b6 100644 --- a/build.gradle +++ b/build.gradle @@ -154,10 +154,17 @@ allprojects { } // for all supported versions that we test build, fail the build if any compilation warnings are reported compile.options.compilerArgs = ['-Xlint', '-Xlint:-path', '-Xlint:-static'] +<<<<<<< HEAD // Set the default Java bytecode level to version 8 to ensure backward compatibility if (JavaVersion.current() >= JavaVersion.VERSION_11) { sourceCompatibility = 8 targetCompatibility = 8 +======= + + if (JavaVersion.current() >= JavaVersion.VERSION_11 && !compile.getName().equals('compileJava11Java')) { + sourceCompatibility = '1.8' + targetCompatibility = '1.8' +>>>>>>> 03a3494ee (Update github workflow to use Java 11) } } diff --git a/d2-int-test/build.gradle b/d2-int-test/build.gradle index 8725d79df5..70f8eff963 100644 --- a/d2-int-test/build.gradle +++ b/d2-int-test/build.gradle @@ -11,6 +11,10 @@ dependencies { compile externalDependency.commonsIo compile externalDependency.commonsHttpClient compile externalDependency.zookeeper +<<<<<<< HEAD +======= + compile externalDependency.jdkTools +>>>>>>> 03a3494ee (Update github workflow to use Java 11) compile externalDependency.netty testCompile externalDependency.testng testCompile externalDependency.commonsIo diff --git a/d2/build.gradle b/d2/build.gradle index 6496c71137..e76f028e2f 100644 --- a/d2/build.gradle +++ b/d2/build.gradle @@ -20,6 +20,10 @@ dependencies { compile externalDependency.zookeeper compile externalDependency.jacksonCore compile externalDependency.jacksonDataBind +<<<<<<< HEAD +======= + compile externalDependency.jdkTools +>>>>>>> 03a3494ee (Update github workflow to use Java 11) compile externalDependency.zero_allocation_hashing compile externalDependency.xchart compileOnly externalDependency.findbugs diff --git a/restli-tools/build.gradle b/restli-tools/build.gradle index 8df74b388f..8e1e88bae8 100644 --- a/restli-tools/build.gradle +++ b/restli-tools/build.gradle @@ -4,7 +4,7 @@ plugins { // This block is only supported and required when building with JDK11+ if (JavaVersion.current() >= JavaVersion.VERSION_11) { - //We need a custom source set for JDK11+ classes + // We need a custom source set for JDK11+ classes sourceSets { java11 { java { @@ -52,13 +52,7 @@ dependencies { compile externalDependency.commonsLang compile externalDependency.jacksonCore compile externalDependency.jacksonDataBind - // tools.jar, which is packaged by jdkTools, have been removed after Java 11 and most of the functionalities have been move to JDK internals. - // So using jdkTools after Java 11 will result in "file dose not exist" error. Depending on the error handling, there are 2 possible outcomes: - // 1. falling back to JDK internal implementations. - // 2. not falling back to JDK internal implementations and throwing a build error. This is the case for protobuf plugin. - if (JavaVersion.current() < JavaVersion.VERSION_11) { - compile externalDependency.jdkTools - } + compile externalDependency.jdkTools compile externalDependency.velocity testCompile externalDependency.mockito From 9a1d94752bcfae118c8bc24df72c4a0b5a84de51 Mon Sep 17 00:00:00 2001 From: Yan Zhou Date: Tue, 22 Aug 2023 18:20:49 -0700 Subject: [PATCH 03/14] rebase master --- build.gradle | 7 ------- d2-int-test/build.gradle | 4 ---- d2/build.gradle | 4 ---- 3 files changed, 15 deletions(-) diff --git a/build.gradle b/build.gradle index eceb6c73b6..7b4992ca89 100644 --- a/build.gradle +++ b/build.gradle @@ -154,17 +154,10 @@ allprojects { } // for all supported versions that we test build, fail the build if any compilation warnings are reported compile.options.compilerArgs = ['-Xlint', '-Xlint:-path', '-Xlint:-static'] -<<<<<<< HEAD // Set the default Java bytecode level to version 8 to ensure backward compatibility if (JavaVersion.current() >= JavaVersion.VERSION_11) { sourceCompatibility = 8 targetCompatibility = 8 -======= - - if (JavaVersion.current() >= JavaVersion.VERSION_11 && !compile.getName().equals('compileJava11Java')) { - sourceCompatibility = '1.8' - targetCompatibility = '1.8' ->>>>>>> 03a3494ee (Update github workflow to use Java 11) } } diff --git a/d2-int-test/build.gradle b/d2-int-test/build.gradle index 70f8eff963..8725d79df5 100644 --- a/d2-int-test/build.gradle +++ b/d2-int-test/build.gradle @@ -11,10 +11,6 @@ dependencies { compile externalDependency.commonsIo compile externalDependency.commonsHttpClient compile externalDependency.zookeeper -<<<<<<< HEAD -======= - compile externalDependency.jdkTools ->>>>>>> 03a3494ee (Update github workflow to use Java 11) compile externalDependency.netty testCompile externalDependency.testng testCompile externalDependency.commonsIo diff --git a/d2/build.gradle b/d2/build.gradle index e76f028e2f..6496c71137 100644 --- a/d2/build.gradle +++ b/d2/build.gradle @@ -20,10 +20,6 @@ dependencies { compile externalDependency.zookeeper compile externalDependency.jacksonCore compile externalDependency.jacksonDataBind -<<<<<<< HEAD -======= - compile externalDependency.jdkTools ->>>>>>> 03a3494ee (Update github workflow to use Java 11) compile externalDependency.zero_allocation_hashing compile externalDependency.xchart compileOnly externalDependency.findbugs From 033487c524b7170465a88bfc8a2cb23443853dfb Mon Sep 17 00:00:00 2001 From: Yan Zhou Date: Fri, 25 Aug 2023 09:42:25 -0700 Subject: [PATCH 04/14] Add back distribution: zulu --- .github/workflows/build.yml | 1 + .github/workflows/publish.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e3c5dd3ef4..28323131f4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,6 +20,7 @@ jobs: fetch-depth: 2 - uses: actions/setup-java@v2 with: + distribution: zulu java-version: ${{ matrix.java }} cache: gradle - run: ./.github/scripts/build.sh diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4df971058d..7b1bd8d73c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -19,6 +19,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-java@v2 with: + distribution: zulu java-version: ${{ matrix.java }} # Do NOT use caching, since we want to ensure published artifacts are fresh - run: ./.github/scripts/publish.sh From ddbbe1b44b9d418aba156da775552447b6029255 Mon Sep 17 00:00:00 2001 From: Yan Zhou Date: Fri, 25 Aug 2023 18:20:47 -0700 Subject: [PATCH 05/14] update CHANGELOG.MD --- CHANGELOG.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62d28a2ecb..9ead2097d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,16 +14,14 @@ and what APIs have changed, if applicable. ## [Unreleased] -<<<<<<< HEAD +## [29.46.0] - 2023-08-25 +- Rewrite the Java Doc logic in Java 11 APIs and use multi-release jar to be backward compatible with Java 8 consumers + ## [29.45.0] - 2023-08-25 - Downgrade major version back to 29. Technically this is not semver-compatible but we feel that the impact should be less severe than the impact of bumping the major version. -======= -## [30.0.1] - 2023-08-22 -- Rewrite the Java Doc logic in Java 11 APIs and use multi-release jar to be backward compatible with Java 8 upstreams ->>>>>>> d4abb632f (Rewrite the Java Doc logic in Java 11 APIs and use multi-release jar to be backward compatible with Java 8 upstreams) ## [30.0.0] - 2023-08-15 - Remove resetTogglingStores functionality from LoadBalancerClientCli, which is incompatible with Java 17 From 1b70e14e3866004f879671c46e313e57cf6ed7f6 Mon Sep 17 00:00:00 2001 From: Yan Zhou Date: Tue, 29 Aug 2023 17:26:16 -0700 Subject: [PATCH 06/14] change the source set entrypoint to java17 --- build_script/restModel.gradle | 4 +- .../internal/common/URIDecoderUtils.java | 3 +- restli-tools/build.gradle | 57 ++++++++++--------- .../tools/idlgen/DocletDocsProvider.java | 2 +- .../restli/tools/idlgen/DocletHelper.java | 0 .../restli/tools/idlgen/RestLiDoclet.java | 2 +- 6 files changed, 36 insertions(+), 32 deletions(-) rename restli-tools/src/main/{java11 => java17}/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java (99%) rename restli-tools/src/main/{java11 => java17}/com/linkedin/restli/tools/idlgen/DocletHelper.java (100%) rename restli-tools/src/main/{java11 => java17}/com/linkedin/restli/tools/idlgen/RestLiDoclet.java (99%) diff --git a/build_script/restModel.gradle b/build_script/restModel.gradle index 27c4968e73..cfb0e55236 100644 --- a/build_script/restModel.gradle +++ b/build_script/restModel.gradle @@ -79,8 +79,8 @@ project.sourceSets.all { SourceSet sourceSet -> project.tasks[sourceSet.compileJavaTaskName].dependsOn(rootProject.ext.build.restModelGenerateTasks[sourceSet]) } - // Use 'jar' instead a custom task name for Java 11 sourceSet in the multi-release jar - final Task jarTask = project.tasks[sourceSet.getName().endsWith('11') ? 'jar' : sourceSet.getTaskName('', 'jar')] + // Use 'jar' instead a custom task name for Java 17 source set in the multi-release jar + final Task jarTask = project.tasks[sourceSet.getName().endsWith('17') ? 'jar' : sourceSet.getTaskName('', 'jar')] jarTask.from(inputParentDirPath) { include "${pegasusDirName}${File.separatorChar}**${File.separatorChar}*.pdsc" include "${pegasusDirName}${File.separatorChar}**${File.separatorChar}*.pdl" diff --git a/restli-common/src/main/java/com/linkedin/restli/internal/common/URIDecoderUtils.java b/restli-common/src/main/java/com/linkedin/restli/internal/common/URIDecoderUtils.java index 5962d0a44a..8cd29d63b7 100644 --- a/restli-common/src/main/java/com/linkedin/restli/internal/common/URIDecoderUtils.java +++ b/restli-common/src/main/java/com/linkedin/restli/internal/common/URIDecoderUtils.java @@ -67,6 +67,7 @@ package com.linkedin.restli.internal.common; import java.nio.ByteBuffer; +import java.nio.Buffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.util.Arrays; @@ -198,7 +199,7 @@ private static ByteBuffer decodeConsecutiveOctets(String s, int start) byte b = decodeOctet(s, i + 1); bb.put(b); } - bb.flip(); + ((Buffer)bb).flip(); return bb; } diff --git a/restli-tools/build.gradle b/restli-tools/build.gradle index 8e1e88bae8..19dcd7d66f 100644 --- a/restli-tools/build.gradle +++ b/restli-tools/build.gradle @@ -5,16 +5,20 @@ plugins { // This block is only supported and required when building with JDK11+ if (JavaVersion.current() >= JavaVersion.VERSION_11) { // We need a custom source set for JDK11+ classes + // Note that we use java 17 as the source set entry point to minimize the impact on existing Java 8 and Java 11 consumers. + // This is because the JVM of the consumer will pick up the highest bytecode version which is lower than or equal to the JVM version. + // So, for Java 8 and Java 11 consumers, Java 17 does not qualify since it is higher than 8 and 11, which means they will + // fall back to using the default Java 8 level bytecode. sourceSets { - java11 { + java17 { java { - srcDirs = ['src/main/java11'] + srcDirs = ['src/main/java17'] } } } // This compile task is automatically generated by java-library plugin for custom JDK11 only source set // We need to explicitly set code versions and override defaults - compileJava11Java { + compileJava17Java { sourceCompatibility = 11 targetCompatibility = 11 options.compilerArgs.addAll(['--release', '11']) @@ -24,8 +28,8 @@ if (JavaVersion.current() >= JavaVersion.VERSION_11) { // We package JDK11+ classes into a custom folder. // JVM will load the class if version of the class is equal or less than version of JVM. // Thus JDK8 or JDK9 will load default class from "com" folder and JDK11+ will load the custom folder - into('META-INF/versions/11') { - from sourceSets.java11.output + into('META-INF/versions/17') { + from sourceSets.java17.output } manifest { attributes( @@ -63,29 +67,28 @@ dependencies { if (JavaVersion.current() >= JavaVersion.VERSION_11) { // Custom dependency set is required for JDK11+ only source set - java11Implementation files(sourceSets.main.output.classesDirs) - java11Compile project(':data') - java11Compile project(':r2-core') - java11Compile project(':li-jersey-uri') - java11Compile project(':generator') - java11Compile project(':pegasus-common') - java11Compile project(':restli-common') - java11Compile project(':restli-client') - java11Compile project(':restli-server') - java11Compile externalDependency.caffeine - java11Compile externalDependency.commonsIo - java11Compile externalDependency.codemodel - java11Compile externalDependency.commonsCli - java11Compile externalDependency.commonsLang - java11Compile externalDependency.jacksonCore - java11Compile externalDependency.jacksonDataBind - java11Compile externalDependency.velocity + java17Compile project(':data') + java17Compile project(':r2-core') + java17Compile project(':li-jersey-uri') + java17Compile project(':generator') + java17Compile project(':pegasus-common') + java17Compile project(':restli-common') + java17Compile project(':restli-client') + java17Compile project(':restli-server') + java17Compile externalDependency.caffeine + java17Compile externalDependency.commonsIo + java17Compile externalDependency.codemodel + java17Compile externalDependency.commonsCli + java17Compile externalDependency.commonsLang + java17Compile externalDependency.jacksonCore + java17Compile externalDependency.jacksonDataBind + java17Compile externalDependency.velocity - java11Compile externalDependency.mockito - java11Compile externalDependency.testng - java11Compile externalDependency.junit - java11Compile externalDependency.commonsHttpClient - java11Compile externalDependency.javaparser + java17Compile externalDependency.mockito + java17Compile externalDependency.testng + java17Compile externalDependency.junit + java17Compile externalDependency.commonsHttpClient + java17Compile externalDependency.javaparser } } diff --git a/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java b/restli-tools/src/main/java17/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java similarity index 99% rename from restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java rename to restli-tools/src/main/java17/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java index 5817e33b0e..c6c9c7a65f 100644 --- a/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java +++ b/restli-tools/src/main/java17/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java @@ -278,7 +278,7 @@ private static String buildDoc(String docText) { if (docText != null && !docText.isEmpty()) { - return DocletHelper.processDocCommentStr(docText); + return docText; } return null; } diff --git a/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletHelper.java b/restli-tools/src/main/java17/com/linkedin/restli/tools/idlgen/DocletHelper.java similarity index 100% rename from restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletHelper.java rename to restli-tools/src/main/java17/com/linkedin/restli/tools/idlgen/DocletHelper.java diff --git a/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/RestLiDoclet.java b/restli-tools/src/main/java17/com/linkedin/restli/tools/idlgen/RestLiDoclet.java similarity index 99% rename from restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/RestLiDoclet.java rename to restli-tools/src/main/java17/com/linkedin/restli/tools/idlgen/RestLiDoclet.java index a50824e26e..b382cc6f3c 100644 --- a/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/RestLiDoclet.java +++ b/restli-tools/src/main/java17/com/linkedin/restli/tools/idlgen/RestLiDoclet.java @@ -265,7 +265,7 @@ public static MethodIdentity create(ExecutableElement method) final List parameterTypeNames = new ArrayList<>(); for (VariableElement param : method.getParameters()) { TypeMirror type = param.asType(); - parameterTypeNames.add(DocletHelper.getCanonicalName(type.toString())); + parameterTypeNames.add(type.toString()); } return new MethodIdentity(method.getEnclosingElement().toString() + "." + method.getSimpleName().toString(), From 18655b4b9124dc07d077b3db1f452bbbb9f6f212 Mon Sep 17 00:00:00 2001 From: Yan Zhou Date: Wed, 6 Sep 2023 11:57:24 -0700 Subject: [PATCH 07/14] revert sourceSet back to Java 11 and add getCompileClasspath() to restModelCodegenClasspath --- CHANGELOG.md | 2 +- build.gradle | 1 + build_script/restModel.gradle | 4 +- .../pegasus/gradle/PegasusPlugin.java | 2 +- ...asedFluentClientApiWithExecutionGroup.java | 223 --- .../TestParseqBasedFluentClientApi.java | 1363 ----------------- ...eqBasedFluentClientApiWithProjections.java | 509 ------ restli-tools/build.gradle | 59 +- .../tools/idlgen/DocletDocsProvider.java | 3 +- .../restli/tools/idlgen/DocletHelper.java | 0 .../restli/tools/idlgen/RestLiDoclet.java | 2 +- 11 files changed, 36 insertions(+), 2132 deletions(-) delete mode 100644 restli-int-test/src/test/java/com/linkedin/restli/examples/TestParSeqBasedFluentClientApiWithExecutionGroup.java delete mode 100644 restli-int-test/src/test/java/com/linkedin/restli/examples/TestParseqBasedFluentClientApi.java delete mode 100644 restli-int-test/src/test/java/com/linkedin/restli/examples/TestParseqBasedFluentClientApiWithProjections.java rename restli-tools/src/main/{java17 => java11}/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java (99%) rename restli-tools/src/main/{java17 => java11}/com/linkedin/restli/tools/idlgen/DocletHelper.java (100%) rename restli-tools/src/main/{java17 => java11}/com/linkedin/restli/tools/idlgen/RestLiDoclet.java (99%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ead2097d6..dd19a4f9a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ and what APIs have changed, if applicable. ## [Unreleased] -## [29.46.0] - 2023-08-25 +## [29.46.0] - 2023-09-05 - Rewrite the Java Doc logic in Java 11 APIs and use multi-release jar to be backward compatible with Java 8 consumers ## [29.45.0] - 2023-08-25 diff --git a/build.gradle b/build.gradle index 7b4992ca89..928145b102 100644 --- a/build.gradle +++ b/build.gradle @@ -231,6 +231,7 @@ subprojects { // so they don't conflict with "velocity-engine-core" module used // for versions >=2.0 all*.exclude group: 'org.apache.velocity', module: 'velocity' + all*.exclude group: 'org.projectlombok', module: 'lombok' } if (!(it.name in ['data-avro', 'restli-int-test'])) { diff --git a/build_script/restModel.gradle b/build_script/restModel.gradle index cfb0e55236..ee9f6c6318 100644 --- a/build_script/restModel.gradle +++ b/build_script/restModel.gradle @@ -79,8 +79,8 @@ project.sourceSets.all { SourceSet sourceSet -> project.tasks[sourceSet.compileJavaTaskName].dependsOn(rootProject.ext.build.restModelGenerateTasks[sourceSet]) } - // Use 'jar' instead a custom task name for Java 17 source set in the multi-release jar - final Task jarTask = project.tasks[sourceSet.getName().endsWith('17') ? 'jar' : sourceSet.getTaskName('', 'jar')] + // Use 'jar' instead a custom task name for Java 11 source set in the multi-release jar + final Task jarTask = project.tasks[sourceSet.getName().endsWith('11') ? 'jar' : sourceSet.getTaskName('', 'jar')] jarTask.from(inputParentDirPath) { include "${pegasusDirName}${File.separatorChar}**${File.separatorChar}*.pdsc" include "${pegasusDirName}${File.separatorChar}**${File.separatorChar}*.pdl" diff --git a/gradle-plugins/src/main/java/com/linkedin/pegasus/gradle/PegasusPlugin.java b/gradle-plugins/src/main/java/com/linkedin/pegasus/gradle/PegasusPlugin.java index 39c500f0ab..a87961c7eb 100644 --- a/gradle-plugins/src/main/java/com/linkedin/pegasus/gradle/PegasusPlugin.java +++ b/gradle-plugins/src/main/java/com/linkedin/pegasus/gradle/PegasusPlugin.java @@ -1268,7 +1268,7 @@ protected void configureRestModelGeneration(Project project, SourceSet sourceSet // generate the rest model FileCollection restModelCodegenClasspath = project.getConfigurations().getByName(PEGASUS_PLUGIN_CONFIGURATION) .plus(project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)) - .plus(sourceSet.getRuntimeClasspath()); + .plus(sourceSet.getRuntimeClasspath()).plus(sourceSet.getCompileClasspath()); String destinationDirPrefix = getGeneratedDirPath(project, sourceSet, REST_GEN_TYPE) + File.separatorChar; FileCollection restModelResolverPath = apiProject.files(getDataSchemaPath(project, sourceSet)) .plus(getDataModelConfig(apiProject, sourceSet)); diff --git a/restli-int-test/src/test/java/com/linkedin/restli/examples/TestParSeqBasedFluentClientApiWithExecutionGroup.java b/restli-int-test/src/test/java/com/linkedin/restli/examples/TestParSeqBasedFluentClientApiWithExecutionGroup.java deleted file mode 100644 index cd6d3f2fff..0000000000 --- a/restli-int-test/src/test/java/com/linkedin/restli/examples/TestParSeqBasedFluentClientApiWithExecutionGroup.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - Copyright (c) 2021 LinkedIn Corp. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License 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.linkedin.restli.examples; - -import com.linkedin.parseq.ParSeqUnitTestHelper; -import com.linkedin.parseq.Task; -import com.linkedin.parseq.batching.BatchingSupport; -import com.linkedin.parseq.trace.Trace; -import com.linkedin.restli.client.ExecutionGroup; -import com.linkedin.restli.client.ParSeqBasedCompletionStage; -import com.linkedin.restli.client.ParSeqRestliClient; -import com.linkedin.restli.client.ParSeqRestliClientBuilder; -import com.linkedin.restli.client.ParSeqRestliClientConfig; -import com.linkedin.restli.client.ParSeqRestliClientConfigBuilder; -import com.linkedin.restli.examples.greetings.api.Greeting; -import com.linkedin.restli.examples.greetings.client.GreetingsFluentClient; -import com.linkedin.restli.server.validation.RestLiValidationFilter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import org.testng.Assert; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - - -public class TestParSeqBasedFluentClientApiWithExecutionGroup extends RestLiIntegrationTest -{ - public static final String MESSAGE = "Create a new greeting"; - ParSeqUnitTestHelper _parSeqUnitTestHelper; - ParSeqRestliClient _parSeqRestliClient; - private final int TEST_BATCH_SIZE = 3; - private final int OVER_BATCH_SIZE_CALLS = TEST_BATCH_SIZE + 1; - private final int UNDER_BATCH_SIZE_CALLS = TEST_BATCH_SIZE - 1; - - @BeforeClass - void setUp() throws Exception - { - super.init(Arrays.asList(new RestLiValidationFilter())); - ParSeqRestliClientConfig config = new ParSeqRestliClientConfigBuilder().addBatchingEnabled("*.*/*.*", Boolean.TRUE) - .addMaxBatchSize("*.*/*.*", TEST_BATCH_SIZE) - .build(); - BatchingSupport batchingSupport = new BatchingSupport(); - _parSeqUnitTestHelper = new ParSeqUnitTestHelper(engineBuilder -> - { - engineBuilder.setPlanDeactivationListener(batchingSupport); - }); - _parSeqUnitTestHelper.setUp(); - _parSeqRestliClient = - new ParSeqRestliClientBuilder().setBatchingSupport(batchingSupport) // RestClient Registered Strategy - .setClient(getClient()).setConfig(config).build(); - } - - @AfterClass - void tearDown() throws Exception - { - if (_parSeqUnitTestHelper != null) - { - _parSeqUnitTestHelper.tearDown(); - } else - { - throw new RuntimeException( - "Tried to shut down Engine but it either has not even been created or has " + "already been shut down"); - } - super.shutdown(); - } - - /** - This test is to test if the batch method has been triggered. - - Note: Currently ParSeqRestClient only support "batch" for gets. - */ - @Test - public void testBatchGet() throws Exception - { - // Test 3+1 = 4 calls using the "runBatchOnClient" method in the fluent Client - GreetingsFluentClient greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - final List> results = new ArrayList<>(TEST_BATCH_SIZE + 1); - greetings.runBatchOnClient(() -> - { - try - { - for (int i = 0; i < OVER_BATCH_SIZE_CALLS; i++) - { - results.add(greetings.get((long) (i + 1))); - } - } catch (Exception e) - { - throw new RuntimeException(); - } - }); - for (CompletionStage stage : results) - { - stage.toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - } - assert (results.get(0) instanceof ParSeqBasedCompletionStage); - Task t = ((ParSeqBasedCompletionStage) results.get(0)).getTask(); - Assert.assertTrue(hasTask(String.format("greetings batch_get(reqs: %s, ids: %s)", TEST_BATCH_SIZE, TEST_BATCH_SIZE), - t.getTrace())); - Assert.assertEquals(findTaskOccurrenceInTrace("greetings batch_get", t.getTrace()), - OVER_BATCH_SIZE_CALLS / TEST_BATCH_SIZE); - Assert.assertEquals(findTaskOccurrenceInTrace("greetings get", t.getTrace()), OVER_BATCH_SIZE_CALLS + 1); - - // Test 3+1 = 4 calls generating a new ExecutionGroup and explicitly call the methods with ExecutionGroup - results.clear(); - ExecutionGroup eg = greetings.generateExecutionGroup(); - for (int i = 0; i < OVER_BATCH_SIZE_CALLS; i++) - { - results.add(greetings.get((long) (i + 1), eg)); - } - assert (results.get(0) instanceof ParSeqBasedCompletionStage); - t = ((ParSeqBasedCompletionStage) results.get(0)).getTask(); - Assert.assertFalse(t.isDone()); - Assert.assertFalse( - hasTask(String.format("greetings batch_get(reqs: %s, ids: %s)", TEST_BATCH_SIZE, TEST_BATCH_SIZE), - t.getTrace())); - eg.execute(); - for (CompletionStage stage : results) - { - stage.toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - } - Assert.assertTrue(hasTask(String.format("greetings batch_get(reqs: %s, ids: %s)", TEST_BATCH_SIZE, TEST_BATCH_SIZE), - t.getTrace())); - Assert.assertEquals(findTaskOccurrenceInTrace("greetings batch_get", t.getTrace()), - OVER_BATCH_SIZE_CALLS / TEST_BATCH_SIZE); - Assert.assertEquals(findTaskOccurrenceInTrace("greetings get", t.getTrace()), OVER_BATCH_SIZE_CALLS + 1); - - // Test 2( less than 3 ) calls using client's batchOn - results.clear(); - greetings.runBatchOnClient(() -> - { - try - { - for (int i = 0; i < UNDER_BATCH_SIZE_CALLS; i++) - { - results.add(greetings.get((long) (i + 1))); - } - } catch (Exception e) - { - throw new RuntimeException(); - } - }); - for (CompletionStage stage : results) - { - stage.toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - } - assert (results.get(0) instanceof ParSeqBasedCompletionStage); - t = ((ParSeqBasedCompletionStage) results.get(0)).getTask(); - Assert.assertTrue( - hasTask(String.format("greetings batch_get(reqs: %s, ids: %s)", TEST_BATCH_SIZE - 1, TEST_BATCH_SIZE - 1), - t.getTrace())); - - // Test 2( less than 3 ) calls execution group - results.clear(); - eg = greetings.generateExecutionGroup(); - for (int i = 0; i < UNDER_BATCH_SIZE_CALLS; i++) - { - results.add(greetings.get((long) (i + 1), eg)); - } - assert (results.get(0) instanceof ParSeqBasedCompletionStage); - t = ((ParSeqBasedCompletionStage) results.get(0)).getTask(); - Assert.assertFalse(t.isDone()); - Assert.assertFalse( - hasTask(String.format("greetings batch_get(reqs: %s, ids: %s)", TEST_BATCH_SIZE - 1, TEST_BATCH_SIZE - 1), - t.getTrace())); - eg.execute(); - for (CompletionStage stage : results) - { - stage.toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - } - Assert.assertTrue( - hasTask(String.format("greetings batch_get(reqs: %s, ids: %s)", TEST_BATCH_SIZE - 1, TEST_BATCH_SIZE - 1), - t.getTrace())); - - // Testing read the completion Stage within the runnable: Not allowed; should fail - results.clear(); - try - { - greetings.runBatchOnClient(() -> - { - try - { - results.add(greetings.get(1L)); - results.get(0).toCompletableFuture().get(500, TimeUnit.MILLISECONDS); - } catch (Exception e) - { - throw new RuntimeException(e); - } - }); - Assert.fail("Should fail: the CompletionStage cannot be read "); - } catch (Exception e) - { - Assert.assertTrue(e.getCause() instanceof TimeoutException); - } - } - - protected boolean hasTask(final String name, final Trace trace) - { - return trace.getTraceMap().values().stream().anyMatch(shallowTrace -> shallowTrace.getName().equals(name)); - } - - protected int findTaskOccurrenceInTrace(final String name, final Trace trace) - { - return (int) trace.getTraceMap() - .values() - .stream() - .filter(shallowTrace -> shallowTrace.getName().contains(name)) - .count(); - } -} \ No newline at end of file diff --git a/restli-int-test/src/test/java/com/linkedin/restli/examples/TestParseqBasedFluentClientApi.java b/restli-int-test/src/test/java/com/linkedin/restli/examples/TestParseqBasedFluentClientApi.java deleted file mode 100644 index bd76422ee5..0000000000 --- a/restli-int-test/src/test/java/com/linkedin/restli/examples/TestParseqBasedFluentClientApi.java +++ /dev/null @@ -1,1363 +0,0 @@ -/* - Copyright (c) 2021 LinkedIn Corp. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License 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.linkedin.restli.examples; - -import com.linkedin.data.template.BooleanArray; -import com.linkedin.data.template.StringMap; -import com.linkedin.parseq.ParSeqUnitTestHelper; -import com.linkedin.restli.client.ParSeqRestliClient; -import com.linkedin.restli.client.ParSeqRestliClientBuilder; -import com.linkedin.restli.client.ParSeqRestliClientConfigBuilder; -import com.linkedin.restli.client.RestLiResponseException; -import com.linkedin.restli.client.util.PatchGenerator; -import com.linkedin.restli.common.BatchCollectionResponse; -import com.linkedin.restli.common.BatchFinderCriteriaResult; -import com.linkedin.restli.common.CollectionResponse; -import com.linkedin.restli.common.ComplexResourceKey; -import com.linkedin.restli.common.CompoundKey; -import com.linkedin.restli.common.CreateIdEntityStatus; -import com.linkedin.restli.common.CreateIdStatus; -import com.linkedin.restli.common.EntityResponse; -import com.linkedin.restli.common.HttpStatus; -import com.linkedin.restli.common.IdEntityResponse; -import com.linkedin.restli.common.PatchRequest; -import com.linkedin.restli.common.UpdateEntityStatus; -import com.linkedin.restli.common.UpdateStatus; -import com.linkedin.restli.examples.custom.types.CustomLong; -import com.linkedin.restli.examples.greetings.api.Greeting; -import com.linkedin.restli.examples.greetings.api.GreetingCriteria; -import com.linkedin.restli.examples.greetings.api.Message; -import com.linkedin.restli.examples.greetings.api.MessageCriteria; -import com.linkedin.restli.examples.greetings.api.MessageCriteriaArray; -import com.linkedin.restli.examples.greetings.api.Tone; -import com.linkedin.restli.examples.greetings.api.TwoPartKey; -import com.linkedin.restli.examples.greetings.client.Actions; -import com.linkedin.restli.examples.greetings.client.ActionsFluentClient; -import com.linkedin.restli.examples.greetings.client.AssociationAltKey; -import com.linkedin.restli.examples.greetings.client.AssociationAltKeyFluentClient; -import com.linkedin.restli.examples.greetings.client.Associations; -import com.linkedin.restli.examples.greetings.client.AssociationsAssociationsFluentClient; -import com.linkedin.restli.examples.greetings.client.AssociationsAssociationsSubFluentClient; -import com.linkedin.restli.examples.greetings.client.AssociationsFluentClient; -import com.linkedin.restli.examples.greetings.client.AssociationsSubFluentClient; -import com.linkedin.restli.examples.greetings.client.Batchfinders; -import com.linkedin.restli.examples.greetings.client.BatchfindersFluentClient; -import com.linkedin.restli.examples.greetings.client.ComplexKeys; -import com.linkedin.restli.examples.greetings.client.ComplexKeysFluentClient; -import com.linkedin.restli.examples.greetings.client.ComplexKeysSubFluentClient; -import com.linkedin.restli.examples.greetings.client.CreateGreeting; -import com.linkedin.restli.examples.greetings.client.CreateGreetingFluentClient; -import com.linkedin.restli.examples.greetings.client.CustomTypes2; -import com.linkedin.restli.examples.greetings.client.CustomTypes2FluentClient; -import com.linkedin.restli.examples.greetings.client.CustomTypes3; -import com.linkedin.restli.examples.greetings.client.CustomTypes3FluentClient; -import com.linkedin.restli.examples.greetings.client.GreetingFluentClient; -import com.linkedin.restli.examples.greetings.client.Greetings; -import com.linkedin.restli.examples.greetings.client.GreetingsFluentClient; -import com.linkedin.restli.examples.greetings.client.PartialUpdateGreeting; -import com.linkedin.restli.examples.greetings.client.PartialUpdateGreetingFluentClient; -import com.linkedin.restli.examples.greetings.client.SubgreetingsFluentClient; -import com.linkedin.restli.examples.groups.api.TransferOwnershipRequest; -import com.linkedin.restli.examples.groups.client.Groups; -import com.linkedin.restli.examples.groups.client.GroupsFluentClient; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; -import org.testng.Assert; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; -import org.testng.collections.Sets; - - -public class TestParseqBasedFluentClientApi extends RestLiIntegrationTest -{ - public static final String MESSAGE = "Create a new greeting"; - ParSeqUnitTestHelper _parSeqUnitTestHelper; - ParSeqRestliClient _parSeqRestliClient; - - @BeforeClass - void setUp() throws Exception - { - super.init(); - _parSeqUnitTestHelper = new ParSeqUnitTestHelper(); - _parSeqUnitTestHelper.setUp(); - _parSeqRestliClient = new ParSeqRestliClientBuilder() - .setClient(getClient()) - .setConfig(new ParSeqRestliClientConfigBuilder().build()) - .build(); - } - - @AfterClass - void tearDown() throws Exception - { - if (_parSeqUnitTestHelper != null) - { - _parSeqUnitTestHelper.tearDown(); - } - else - { - throw new RuntimeException("Tried to shut down Engine but it either has not even been created or has " - + "already been shut down"); - } - super.shutdown(); - } - - @Test - public void testGetRequest() throws Exception - { - Greetings greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - CompletionStage result = greetings.get(1L); - CompletableFuture future = result.toCompletableFuture(); - Greeting greeting = future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertTrue(greeting.hasId()); - Assert.assertEquals((Long) 1L, greeting.getId()); - } - - @Test - public void testGetRequestFailure() throws Exception - { - Greetings greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - CompletionStage result = greetings.get(-1L); - CompletableFuture future = result.toCompletableFuture(); - try - { - future.get(5000, TimeUnit.MILLISECONDS); - Assert.fail("Expected failure"); - } catch (ExecutionException e) - { - Assert.assertEquals(((RestLiResponseException) e.getCause()).getStatus(), 404); - } - } - - - @Test - public void testBatchGetRequest() throws Exception - { - Greetings greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - Set ids = Sets.newHashSet(Arrays.asList(1L, 2L, 3L)); - CompletionStage>> result = greetings.batchGet(ids); - CompletableFuture>> future = result.toCompletableFuture(); - Map> resultMap = future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(resultMap.size(), ids.size()); - for (Long id : ids) - { - EntityResponse g = resultMap.get(id); - Assert.assertNotNull(g); - Assert.assertTrue(g.hasEntry()); - Assert.assertEquals(id, g.getEntity().getId()); - } - } - - @Test - public void testBatchGetRequestWithPartialErrors() throws Exception - { - Greetings greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - Set ids = Sets.newHashSet(Arrays.asList(-1L, -2L, 1L, 2L, 3L)); - CompletionStage>> result = greetings.batchGet(ids); - CompletableFuture>> future = result.toCompletableFuture(); - Map> resultMap = future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(resultMap.size(), ids.size()); - for (Long id : ids) - { - EntityResponse g = resultMap.get(id); - Assert.assertNotNull(g); - if (id > 0) - { - Assert.assertTrue(g.hasEntry()); - Assert.assertEquals(id, g.getEntity().getId()); - } - else - { - Assert.assertTrue(g.hasError()); - } - } - } - - @Test - public void testCreate() throws Exception - { - Greetings greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - CompletionStage result = greetings.create(getGreeting()); - CompletableFuture future = result.toCompletableFuture(); - Assert.assertNotNull(future.get(5000, TimeUnit.MILLISECONDS)); - } - - @Test - public void testCreateNullId() throws Exception - { - Greetings greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - CompletionStage result = greetings.create(getGreeting(), params -> params.setIsNullId(true)); - CompletableFuture future = result.toCompletableFuture(); - Assert.assertNull(future.get(5000, TimeUnit.MILLISECONDS)); - } - - @Test - public void testCreateReturnEntity() throws Exception - { - CreateGreeting greetings = new CreateGreetingFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - String msg = Double.toString(Math.random()); - CompletionStage> result = greetings.createAndGet(getGreeting(msg)); - CompletableFuture> future = result.toCompletableFuture(); - Assert.assertNotNull(future.get(5000, TimeUnit.MILLISECONDS)); - Assert.assertEquals(msg, future.get().getEntity().getMessage()); - } - - @Test - public void testCreateReturnEntityDisabled() throws Exception - { - CreateGreeting greetings = new CreateGreetingFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - CompletionStage result = greetings.create(getGreeting()); - CompletableFuture future = result.toCompletableFuture(); - Assert.assertNotNull(future.get(5000, TimeUnit.MILLISECONDS)); - } - - @Test - public void testBatchCreate() throws Exception - { - Greetings greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - CompletionStage>> result = greetings.batchCreate( - Arrays.asList(getGreeting(), getGreeting())); - CompletableFuture>> future = result.toCompletableFuture(); - List> ids = future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(ids.size(), 2); - } - - @Test - public void testBatchCreateReturnEntity() throws Exception - { - CreateGreeting greetings = new CreateGreetingFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - String msg1 = Double.toString(Math.random()); - String msg2 = Double.toString(Math.random()); - CompletionStage>> - result = greetings.batchCreateAndGet(Arrays.asList(getGreeting(msg1), getGreeting(msg2))); - CompletableFuture>> future = result.toCompletableFuture(); - List> entities = future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(entities.size(), 2); - Assert.assertNotNull(entities.get(0).getEntity()); - Assert.assertEquals(msg1, entities.get(0).getEntity().getMessage()); - Assert.assertNotNull(entities.get(1).getEntity()); - Assert.assertEquals(msg2, entities.get(1).getEntity().getMessage()); - } - - @Test - public void testBatchCreateReturnEntityDisabled() throws Exception - { - CreateGreeting greetings = new CreateGreetingFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - CompletionStage>> - result = greetings.batchCreate(Arrays.asList(getGreeting(), getGreeting())); - CompletableFuture>> future = result.toCompletableFuture(); - List> ids = future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(ids.size(), 2); - } - - @Test - public void testCreateAndThenBatchDelete() throws Exception - { - Greetings greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - // Create entities first so we don't delete those used by other tests. - CompletionStage>> - createResult = greetings.batchCreate(Arrays.asList(getGreeting(), getGreeting())); - - CompletionStage> - result = createResult.thenCompose(ids -> greetings.batchDelete( - Sets.newHashSet(ids.stream().map(CreateIdStatus::getKey).collect(Collectors.toList())))); - CompletableFuture> future = result.toCompletableFuture(); - Map ids = future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(ids.size(), 2); - for (UpdateStatus status : ids.values()) - { - Assert.assertEquals(status.getStatus().intValue(), 204); - } - } - - @Test - public void testCreateAndThenBatchDeleteWithFailures() throws Exception - { - Greetings greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - // Create entities first so we don't delete those used by other tests. - CompletionStage>> - createResult = greetings.batchCreate(Arrays.asList(getGreeting(), getGreeting())); - - CompletionStage> - result = createResult.thenCompose(ids -> - { - Set deleteIds = Sets.newHashSet( - ids.stream().map(CreateIdStatus::getKey).collect(Collectors.toList())); - deleteIds.add(-1L); - return greetings.batchDelete(deleteIds); - }); - CompletableFuture> future = result.toCompletableFuture(); - Map ids = future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(ids.size(), 3); - Assert.assertEquals(ids.remove(-1L).getStatus().intValue(), 404); - for (UpdateStatus status : ids.values()) - { - Assert.assertEquals(status.getStatus().intValue(), 204); - } - } - - @Test - public void testCreateAndThenDelete() throws Exception - { - Greetings greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - // Create entities first so we don't delete those used by other tests. - CompletionStage createResult = greetings.create(getGreeting()); - - CompletionStage result = createResult.thenCompose(greetings::delete); - CompletableFuture future = result.toCompletableFuture(); - future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertFalse(future.isCompletedExceptionally()); - } - - @Test - public void testDeleteFailure() throws Exception - { - Greetings greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - CompletionStage result = greetings.delete(-1L); - CompletableFuture future = result.toCompletableFuture(); - try - { - future.get(5000, TimeUnit.MILLISECONDS); - Assert.fail("Expected failure"); - } catch (ExecutionException e) - { - Assert.assertEquals(((RestLiResponseException) e.getCause()).getStatus(), 404); - } - } - - @Test - public void testPartialUpdateAfterCreateAndGet() throws Exception - { - Greetings greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - // Create a new greeting to use for partial update test. - CompletionStage createResult = greetings.create(getGreeting()); - CompletionStage updateResult = createResult.thenCompose(greetings::get).thenCompose(greeting -> - { - try - { - Greeting update = greeting.copy(); - greeting.setMessage("Partial update message"); - return greetings.partialUpdate(1L, PatchGenerator.diff(greeting, update)); - } catch (CloneNotSupportedException e) - { - throw new RuntimeException(e); - } - }); - CompletableFuture future = updateResult.toCompletableFuture(); - future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertFalse(future.isCompletedExceptionally()); - } - - @Test - public void testPartialUpdateAndGet() throws Exception - { - PartialUpdateGreeting greetings = new PartialUpdateGreetingFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - Greeting original = getGreeting(); - String message = "Edited message: fluent api test partialUpdateAndGet"; - Greeting update = getGreeting(message); - CompletionStage result = greetings.partialUpdateAndGet(21L, PatchGenerator.diff(original, update)); - CompletableFuture future = result.toCompletableFuture(); - Assert.assertEquals(future.get(5000, TimeUnit.MILLISECONDS).getMessage(), message); - } - - @Test - public void testPartialUpdateError() throws Exception - { - PartialUpdateGreeting greetings = new PartialUpdateGreetingFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - Greeting original = getGreeting(); - String message = "Edited message: fluent api test partialUpdateAndGet"; - Greeting update = getGreeting(message); - CompletionStage result = greetings.partialUpdateAndGet(-1L, PatchGenerator.diff(original, update)); - CompletableFuture future = result.toCompletableFuture(); - try - { - future.get(5000, TimeUnit.MILLISECONDS); - Assert.fail("Expected failure"); - } catch (ExecutionException e) - { - Assert.assertEquals(((RestLiResponseException) e.getCause()).getStatus(), 404); - } - } - - @Test - public void testBatchPartialUpdate() throws Exception - { - PartialUpdateGreeting greetings = new PartialUpdateGreetingFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - Map> inputs = new HashMap<>(); - Greeting original = getGreeting(); - String message = "Edited message: fluent api test partialUpdateAndGet"; - Greeting update = getGreeting(message); - inputs.put(21L, PatchGenerator.diff(original, update)); - inputs.put(22L, PatchGenerator.diff(original, update)); - CompletionStage> result = greetings.batchPartialUpdate(inputs); - CompletableFuture> future = result.toCompletableFuture(); - Assert.assertNotNull(future.get(5000, TimeUnit.MILLISECONDS)); - Assert.assertEquals(future.get().get(21L).getStatus().intValue(), 200); - Assert.assertEquals(future.get().get(22L).getStatus().intValue(), 200); - } - - @Test - public void testBatchPartialUpdateWithErrors() throws Exception - { - PartialUpdateGreeting greetings = new PartialUpdateGreetingFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - Map> inputs = new HashMap<>(); - Greeting original = getGreeting(); - String message = "Edited message: fluent api test partialUpdateAndGet"; - Greeting update = getGreeting(message); - inputs.put(21L, PatchGenerator.diff(original, update)); - inputs.put(-2L, PatchGenerator.diff(original, update)); - CompletionStage> result = greetings.batchPartialUpdate(inputs); - CompletableFuture> future = result.toCompletableFuture(); - Assert.assertNotNull(future.get(5000, TimeUnit.MILLISECONDS)); - Assert.assertEquals(future.get().get(21L).getStatus().intValue(), 200); - Assert.assertEquals(future.get().get(-2L).getStatus().intValue(), 404); - } - - @Test - public void testBatchPartialUpdateAndGet() throws Exception - { - PartialUpdateGreeting greetings = new PartialUpdateGreetingFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - Map> inputs = new HashMap<>(); - Greeting original = getGreeting(); - String message = "Edited message: fluent api test partialUpdateAndGet"; - Greeting update = getGreeting(message); - inputs.put(21L, PatchGenerator.diff(original, update)); - inputs.put(22L, PatchGenerator.diff(original, update)); - CompletionStage>> result = greetings.batchPartialUpdateAndGet(inputs); - CompletableFuture>> future = result.toCompletableFuture(); - Assert.assertNotNull(future.get(5000, TimeUnit.MILLISECONDS)); - Assert.assertEquals(future.get().get(21L).getEntity().getId().longValue(), 21L); - Assert.assertEquals(future.get().get(21L).getEntity().getMessage(), message); - Assert.assertEquals(future.get().get(22L).getEntity().getId().longValue(), 22L); - Assert.assertEquals(future.get().get(22L).getEntity().getMessage(), message); - } - - @Test - public void testBatchPartialUpdateAndGetWithErrors() throws Exception - { - PartialUpdateGreeting greetings = new PartialUpdateGreetingFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - Map> inputs = new HashMap<>(); - Greeting original = getGreeting(); - String message = "Edited message: fluent api test partialUpdateAndGet"; - Greeting update = getGreeting(message); - inputs.put(21L, PatchGenerator.diff(original, update)); - inputs.put(-2L, PatchGenerator.diff(original, update)); - CompletionStage>> result = greetings.batchPartialUpdateAndGet(inputs); - CompletableFuture>> future = result.toCompletableFuture(); - Assert.assertNotNull(future.get(5000, TimeUnit.MILLISECONDS)); - Assert.assertEquals(future.get().get(21L).getStatus().intValue(), 200); - Assert.assertEquals(future.get().get(21L).getEntity().getId().longValue(), 21L); - Assert.assertEquals(future.get().get(21L).getEntity().getMessage(), message); - Assert.assertEquals(future.get().get(-2L).getStatus().intValue(), 404); - Assert.assertFalse(future.get().get(-2L).hasEntity()); - } - - @Test - public void testBatchUpdate() throws Exception - { - Greetings greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - CompletionStage>> - createResult = greetings.batchCreate(Arrays.asList(getGreeting(), getGreeting())); - - final Map inputs = new HashMap<>(); - CompletionStage> result = createResult.thenCompose(idStatuses -> - { - for (CreateIdStatus idStatus : idStatuses) - { - inputs.put(idStatus.getKey(), getGreeting("Batch update test")); - } - return greetings.batchUpdate(inputs); - }); - CompletableFuture> future = result.toCompletableFuture(); - Map ids = future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(ids.size(), 2); - for (Long id : inputs.keySet()) - { - Assert.assertEquals(ids.get(id).getStatus().intValue(), 204); - } - } - - @Test - public void testBatchUpdateWithErrors() throws Exception - { - Greetings greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - final Map inputs = new HashMap<>(); - inputs.put(-6L, getGreeting()); - CompletionStage createResult = greetings.create(getGreeting()); - - CompletionStage> result = createResult.thenCompose(id -> - { - inputs.put(id, getGreeting("Batch update test")); - return greetings.batchUpdate(inputs); - }); - CompletableFuture> future = result.toCompletableFuture(); - Map ids = future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(ids.size(), 2); - Assert.assertEquals(ids.get(createResult.toCompletableFuture().get()).getStatus().intValue(), 204); - Assert.assertEquals(ids.get(-6L).getStatus().intValue(), 404); - } - - @Test - public void testUpdate() throws Exception - { - Greetings greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - CompletionStage createResult = greetings.create(getGreeting()); - final String message = "Update test"; - CompletionStage updateResult = createResult.thenCompose(id -> greetings.update(id, getGreeting(message))); - CompletionStage getResult = createResult.thenCombine(updateResult, (id, v) -> id).thenCompose(greetings::get); - - CompletableFuture future = getResult.toCompletableFuture(); - future.get(5000, TimeUnit.MILLISECONDS); - // Get the greeting to test updated value - Assert.assertEquals(future.get().getMessage(), message); - } - - @Test - public void testUpdateFailure() throws Exception - { - Greetings greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - CompletionStage result = greetings.update(-7L, getGreeting()); - CompletableFuture future = result.toCompletableFuture(); - try - { - future.get(5000, TimeUnit.MILLISECONDS); - Assert.fail("Expected failure"); - } catch (ExecutionException e) - { - Assert.assertEquals(((RestLiResponseException) e.getCause()).getStatus(), 404); - } - } - - @Test - public void testGetAll() throws Exception - { - Greetings greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - // Create some greetings with "GetAll" in message so they will be returned by getAll test method.. - CompletionStage>> createResult = greetings.batchCreate( - Arrays.asList(getGreeting("GetAll"), getGreeting("GetAll"))); - - CompletionStage> result = createResult.thenCompose(ids -> greetings.getAll()); - CompletableFuture> future = result.toCompletableFuture(); - List greetingList = future.get(5000, TimeUnit.MILLISECONDS).getElements(); - Assert.assertTrue(greetingList.size() >= 2); - for (Greeting greeting :greetingList) - { - Assert.assertTrue(greeting.getMessage().contains("GetAll")); - } - } - - @Test - public void testFinder() throws Exception - { - Greetings greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - CompletionStage> result = greetings.findBySearch(params -> params.setTone(Tone.FRIENDLY)); - CompletableFuture> future = result.toCompletableFuture(); - List greetingList = future.get(5000, TimeUnit.MILLISECONDS).getElements(); - Assert.assertTrue(greetingList.size() > 0); - for (Greeting greeting :greetingList) - { - Assert.assertEquals(greeting.getTone(), Tone.FRIENDLY); - } - } - - @Test - public void testBatchFinder() throws Exception - { - Batchfinders batchfinders = new BatchfindersFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - GreetingCriteria c1 = new GreetingCriteria().setId(1L).setTone(Tone.SINCERE); - GreetingCriteria c2 = new GreetingCriteria().setId(2L).setTone(Tone.FRIENDLY); - CompletionStage> result = batchfinders.findBySearchGreetings( - Arrays.asList(c1, c2), "hello world"); - CompletableFuture> future = result.toCompletableFuture(); - List> batchResult = future.get(5000, TimeUnit.MILLISECONDS).getResults(); - - List greetings1 = batchResult.get(0).getElements(); - Assert.assertTrue(greetings1.get(0).hasTone()); - Assert.assertEquals(greetings1.get(0).getTone(), Tone.SINCERE); - - List greetings2 = batchResult.get(1).getElements(); - Assert.assertTrue(greetings2.get(0).hasId()); - Assert.assertEquals(greetings2.get(0).getTone(), Tone.FRIENDLY); - } - - // ----- Test with Simple Resources ------ - @Test - public void testSimpleResourceUpdate() throws Exception - { - final String message = "Update test"; - com.linkedin.restli.examples.greetings.client.Greeting greeting = new GreetingFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - greeting.update(getGreeting(message)).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Greeting greetingEntity = greeting.get().toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(greetingEntity.getMessage(), message); - } - - @Test - public void testSimpleResourceDeleteMethodAndGetMethod() throws Exception - { - com.linkedin.restli.examples.greetings.client.Greeting greeting = new GreetingFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - greeting.delete().toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - try - { - Greeting greetingEntity = greeting.get().toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.fail("should fail since entity delete"); - } - catch (Exception e) - { - - } - final String message = "Test Get"; - greeting.update(getGreeting(message)).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Greeting greetingEntity = greeting.get().toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.assertTrue(greetingEntity.hasId()); - Assert.assertEquals(greetingEntity.getMessage(), message); - - } - - // ----- Test with Assocations Resources ------ - - private CompoundKey getAssociateResourceUrlKey(Associations client) - { - return client.generateAssociationsCompoundKey(StringTestKeys.URL, StringTestKeys.URL2); - } - - private CompoundKey getAssociateResourceSimpleKey(Associations client) - { - return client.generateAssociationsCompoundKey(StringTestKeys.SIMPLEKEY, StringTestKeys.SIMPLEKEY2); - } - - private Map getAssociateResourceMockDB(Associations client) - { - HashMap mapDB = new HashMap<>(); - CompoundKey urlKey = getAssociateResourceUrlKey(client); - CompoundKey simpleKey = getAssociateResourceSimpleKey(client); - mapDB.put(urlKey, new Message().setId(urlKey.getPartAsString("src") + " " + urlKey.getPartAsString("dest")) - .setMessage("I need some %20") - .setTone(Tone.SINCERE)); - mapDB.put(getAssociateResourceSimpleKey(client), - new Message().setId(simpleKey.getPartAsString("src") + " " + simpleKey.getPartAsString("dest")) - .setMessage("src1-dest1") - .setTone(Tone.SINCERE)); - return mapDB; - } - - @Test - public void testAssociateResourceGet() throws Exception - { - Associations associations = - new AssociationsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - Message response = associations.get(getAssociateResourceUrlKey(associations)) - .toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.assertTrue(response.hasId()); - } - - @Test - public void testAssociateResourceBatchGet() throws Exception - { - Associations associations = - new AssociationsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - Map> entityResponse = - associations.batchGet(getAssociateResourceMockDB(associations).keySet()) - .toCompletableFuture() - .get(5000, TimeUnit.MILLISECONDS); - for (CompoundKey id : getAssociateResourceMockDB(associations).keySet()) - { - Assert.assertTrue(entityResponse.containsKey(id)); - EntityResponse single = entityResponse.get(id); - Assert.assertEquals(single.getEntity(), getAssociateResourceMockDB(associations).get(id)); - } - } - - @Test - public void testAssociateResourceBatchUpdate() throws Exception - { - Associations associations = - new AssociationsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - Map ids = - associations.batchUpdate(getAssociateResourceMockDB(associations)) - .toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - - Assert.assertEquals(ids.size(), 2); - for (CompoundKey id : ids.keySet()) - { - Assert.assertEquals(ids.get(id).getStatus().intValue(), 204); - } - } - - @Test - public void testAssociateResourceBatchPartialUpdate() throws Exception - { - Associations associations = - new AssociationsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - Map> patches = new HashMap<>(); - patches.put(getAssociateResourceUrlKey(associations), new PatchRequest<>()); - patches.put(getAssociateResourceSimpleKey(associations), new PatchRequest<>()); - - Map ids = - associations.batchPartialUpdate(patches).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - - for (CompoundKey id : ids.keySet()) - { - Assert.assertEquals(ids.get(id).getStatus().intValue(), 204); - } - } - - @Test - public void testAssociationFinderUsingAssocKey() throws Exception - { - AssociationsFluentClient associations = - new AssociationsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - CollectionResponse messages = - associations.findByAssocKeyFinder(AssociationResourceHelpers.URL_COMPOUND_KEY.getPartAsString("src")).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.assertTrue(messages.getElements().size() > 0); - for (Message message : messages.getElements()) - { - Assert.assertEquals(message.getId(), AssociationResourceHelpers.URL_MESSAGE.getId()); - } - } - - @Test - public void testAssociationFinderUsingCustomAssocKey() throws Exception - { - CustomTypes3 customTypesResource3 = - new CustomTypes3FluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - CollectionResponse greetings = - customTypesResource3.findByDateOnly(new Date()).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.assertTrue(greetings.getElements().size() > 0); - } - - @Test - public void testAssociationBatchFinderUsingAssocKey() throws Exception - { - AssociationsFluentClient associations = - new AssociationsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - MessageCriteriaArray criteriaArray = new MessageCriteriaArray(); - criteriaArray.add(new MessageCriteria().setTone(Tone.FRIENDLY)); - criteriaArray.add(new MessageCriteria().setTone(Tone.INSULTING)); - BatchCollectionResponse messages = - associations.findBySearchMessages(AssociationResourceHelpers.URL_COMPOUND_KEY.getPartAsString("src"), - criteriaArray).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - - Assert.assertEquals(messages.getResults().size(), 2); - BatchFinderCriteriaResult friendly = messages.getResults().get(0); - Assert.assertFalse(friendly.isError()); - for (Message message : friendly.getElements()) - { - Assert.assertEquals(message.getTone(), Tone.FRIENDLY); - } - - BatchFinderCriteriaResult insulting = messages.getResults().get(1); - Assert.assertTrue(insulting.isError()); - Assert.assertEquals( (int) insulting.getError().getStatus(), 404); - } - - @Test public void testAssociateResourceSpreadKeyAPI() throws Exception - { - // Use AssocationAltKeyResource and AltKeyDataProvider - - // Get - AssociationAltKey client = new AssociationAltKeyFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - String msgKey = "c"; - Long longKey = 3L; - Greeting res = client.get(longKey, msgKey).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(res.getTone(), Tone.FRIENDLY); - Assert.assertEquals(res.getMessage(), msgKey); - - // Update - String newMsg = "aa"; - res.setMessage(newMsg); - client.update(longKey, msgKey, res).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - res = client.get(longKey, msgKey).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(res.getMessage(), newMsg); - // PartialUpdate - Tone updatedTone = Tone.SINCERE; - res.setTone(updatedTone); - Greeting original = client.get(longKey, msgKey).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - PatchRequest patch = PatchGenerator.diff(original, res); - client.partialUpdate(longKey, msgKey, patch).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); // Only tone differ - res = client.get(longKey, msgKey).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(res.getMessage(), newMsg); - Assert.assertEquals(res.getTone(), updatedTone); - - // Delete - try - { - // that resource implementation does not allow deletion - client.delete(longKey, msgKey).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - } - catch (ExecutionException e) - { - Assert.assertEquals(((RestLiResponseException) e.getCause()).getStatus(), 404); - } - - // Action - Assert.assertEquals(client.testAction(longKey, msgKey) - .toCompletableFuture().get(5000, TimeUnit.MILLISECONDS), "Hello!"); - } - - // ----- Test with Sub Resources ------ - // These tests is to verify subresources methods in FluentClient subresources - // works as expected as non-subresources. - - @Test public void testSubResource_getSubClientFromParent() throws Exception - { - com.linkedin.restli.examples.greetings.client.Greeting - greetingClient = new GreetingFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - Greeting greeting = greetingClient.subgreetingsOf() - .subsubgreetingOf(1L) - .get() - .toCompletableFuture() - .get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(greeting.getId().longValue(), 10L); // value should be (pathKey * 10) - } - - /** - * Test {@link com.linkedin.restli.examples.greetings.server.CollectionUnderSimpleResource} - * A complete set of request tests were tested in {@link TestSimpleResourceHierarchy} - */ - @Test public void testSubResource_noPathKey() throws Exception - { - SubgreetingsFluentClient - subgreetingsFluentClient = new SubgreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - CompletableFuture future = subgreetingsFluentClient.get(1L).toCompletableFuture(); - Greeting greeting = future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertTrue(greeting.hasId()); - Assert.assertEquals((Long) 1L, greeting.getId()); - - List ids = Arrays.asList(1L, 2L, 3L, 4L); - Map> response = - subgreetingsFluentClient.batchGet(new HashSet<>(ids)).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(response.size(), ids.size()); - - // Update - String updatedMessage = "updated"; - greeting.setMessage(updatedMessage); - CompletionStage updateStage = subgreetingsFluentClient.update(1L, greeting).thenRun(() -> { - try{ - Assert.assertEquals( - subgreetingsFluentClient.get(1L).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS), - greeting - ); - } - catch (Exception e) - { - Assert.fail("Unexpected error"); - } - }); - updateStage.toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.assertFalse(updateStage.toCompletableFuture().isCompletedExceptionally()); - - // Partial update - Greeting update = greeting.copy(); - String partialUpdateMessage = "Partial update message"; - update.setMessage(partialUpdateMessage); - CompletionStage partialUpdateResult = subgreetingsFluentClient.partialUpdate(1L, PatchGenerator.diff(greeting, update)); - partialUpdateResult.toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(subgreetingsFluentClient.get(1L).toCompletableFuture().get(500, TimeUnit.MILLISECONDS).getMessage(), partialUpdateMessage); - Assert.assertFalse(partialUpdateResult.toCompletableFuture().isCompletedExceptionally()); - - - // create - String msg = Double.toString(Math.random()); - CompletionStage result = subgreetingsFluentClient.create(getGreeting(msg)); - CompletableFuture createFuture = result.toCompletableFuture(); - Long createdId = createFuture.get(5000, TimeUnit.MILLISECONDS); - Assert.assertTrue(subgreetingsFluentClient.get(createdId).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS).getMessage().equals(msg)); - - // batch create - String msg1 = Double.toString(Math.random()); - String msg2 = Double.toString(Math.random()); - CompletionStage>> - batchCreateStage = subgreetingsFluentClient.batchCreate(Arrays.asList(getGreeting(msg1), getGreeting(msg2))); - CompletableFuture>> batchCreateFuture = batchCreateStage.toCompletableFuture(); - List> createdList = batchCreateFuture.get(5000, TimeUnit.MILLISECONDS); - CompletionStage>> batchGetStage = - subgreetingsFluentClient.batchGet(createdList.stream().map(CreateIdStatus::getKey).collect(Collectors.toSet())); - - Map> entities = batchGetStage.toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(entities.size(), 2); - } - - /** - * Test {@link com.linkedin.restli.examples.greetings.server.SimpleResourceUnderCollectionResource} - * A complete set of request tests were tested in {@link TestSimpleResourceHierarchy} - */ - @Test public void testSubResource_oneLayerPathKey() throws Exception - { - // Get - com.linkedin.restli.examples.greetings.client.Greeting.Subgreetings.Subsubgreeting - subsubClient = new GreetingFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()).subgreetingsOf() - .subsubgreetingOf(1L); - Greeting greeting = subsubClient.get().toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(greeting.getId().longValue(), 10L); // value should be (pathKey * 10) - - // Update - Greeting updateGreeting = new Greeting(); - updateGreeting.setMessage("Message1"); - updateGreeting.setTone(Tone.INSULTING); - updateGreeting.setId(1L); - subsubClient.update(updateGreeting).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(subsubClient.get().toCompletableFuture().get(5000, TimeUnit.MILLISECONDS).getTone(), Tone.INSULTING); - - // Partial Update - Greeting partialUpdateGreeting = new Greeting(); - partialUpdateGreeting.setMessage("Message1"); - partialUpdateGreeting.setTone(Tone.SINCERE); - partialUpdateGreeting.setId(1L); - PatchRequest patch = PatchGenerator.diffEmpty(partialUpdateGreeting); - subsubClient.partialUpdate(patch).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(subsubClient.get().toCompletableFuture().get(5000, TimeUnit.MILLISECONDS).getTone(), Tone.SINCERE); - - // Delete - subsubClient.delete().toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - try - { - subsubClient.get().toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.fail("Sub resource call without path key should fail"); - } - catch (Exception e) - { - Assert.assertTrue(e.getCause() instanceof RestLiResponseException); - Assert.assertEquals(((RestLiResponseException)e.getCause()).getStatus(), HttpStatus.S_404_NOT_FOUND.getCode()); - } - - } - - /** - * Test {@link com.linkedin.restli.examples.greetings.server.CollectionOfCollectionOfCollectionOfSimpleResource} - */ - @Test public void testSubResource_twoLayersPathKeys() throws Exception - { - com.linkedin.restli.examples.greetings.client.Greeting.Subgreetings.GreetingsOfgreetingsOfgreeting.GreetingsOfgreetingsOfgreetingsOfgreeting - gggs = - new GreetingFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()) - .subgreetingsOf() - .greetingsOfgreetingsOfgreetingOf(100L).greetingsOfgreetingsOfgreetingsOfgreetingOf(1000L); - - Greeting response = gggs.get(10L).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.assertTrue(response.getId() == 1110); - } - - /** - * Test {@link com.linkedin.restli.examples.greetings.server.AssociationsSubResource} - * - * A complete set of request tests were tested in {@link TestAssociationsResource} - */ - @Test public void testSubResource_associationKey() throws Exception - { - //AssociationsSub - String src = "src"; - String dest = "dest"; - String subKey = "subKey"; - CompoundKey key1 = new AssociationsFluentClient.Key().setSrc(src).setDest(dest); - AssociationsSubFluentClient - subFluentClient = new AssociationsSubFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - Message response = subFluentClient.withAssociationsId(key1).get(subKey).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(response.getId(), "src"); - Assert.assertEquals(response.getMessage(), "dest"); - - // Repeat using sub client generated from parent - Associations.AssociationsSub - subFluentClient2 = new AssociationsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()).associationsSubOf(key1); - response = subFluentClient2.get(subKey).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(response.getId(), "src"); - Assert.assertEquals(response.getMessage(), "dest"); - } - - /** - * Test {@link com.linkedin.restli.examples.greetings.server.AssociationsAssociationsSubResource} - */ - @Test public void testSubResource_twoLayerAssociationPathKey() throws Exception - { - //AssociationsAssociations - String src = "src"; - String anotherSrc = "anotherSrc"; - String dest = "dest"; - String anotherDest = "anotherDest"; - String subKey = "subKey"; - CompoundKey key1 = new AssociationsFluentClient.Key().setSrc(src).setDest(dest); - CompoundKey key2 = - new AssociationsAssociationsFluentClient.Key() - .setAnotherSrc(anotherSrc) - .setAnotherDest(anotherDest); - AssociationsAssociationsSubFluentClient - subFluentClient = new AssociationsAssociationsSubFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - Message response = subFluentClient.withAssociationsId(key1) - .withAssociationsAssociationsId(key2) - .get(subKey) - .toCompletableFuture() - .get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(response.getId(), src+anotherSrc+subKey); - Assert.assertEquals(response.getMessage(), dest+anotherDest); - - // Repeat using sub client generated from parent - Associations.AssociationsAssociations.AssociationsAssociationsSub - subFluentClient2 = new AssociationsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()) - .associationsAssociationsOf(key1).associationsAssociationsSubOf(key2); - - response = subFluentClient2 - .get(subKey) - .toCompletableFuture() - .get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(response.getId(), src+anotherSrc+subKey); - Assert.assertEquals(response.getMessage(), dest+anotherDest); - } - - /** - * Test {@link com.linkedin.restli.server.resources.ComplexKeyResource} - * - * A complete set of request tests were tested in {@link TestComplexKeysResource} - */ - @Test public void testSubResource_complexKeyParentResource() throws Exception - { - TwoPartKey key = new TwoPartKey(); - key.setMajor("a"); - key.setMinor("b"); - TwoPartKey param = new TwoPartKey(); - param.setMajor("c"); - param.setMinor("d"); - ComplexResourceKey complexKey = new ComplexResourceKey<>(key, param); - ComplexKeysSubFluentClient - subFluentClient = new ComplexKeysSubFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - subFluentClient.withKeys(complexKey); - TwoPartKey response = subFluentClient.get("stringKey").toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(response.getMajor(), "aANDc"); - Assert.assertEquals(response.getMinor(), "bANDd"); - - // Repeat using sub client generated from parent - - ComplexKeys.ComplexKeysSub - subFluentClient2 = new ComplexKeysFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()).complexKeysSubOf(complexKey); - response = subFluentClient2.get("stringKey").toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(response.getMajor(), "aANDc"); - Assert.assertEquals(response.getMinor(), "bANDd"); - - } - - // ----- Tests with actions ------ - @Test public void testCollectionEntityActionWithReturn() throws Exception - { - Greetings greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - Greeting newGreeting; - newGreeting = greetings.updateTone(1L, - param -> param.setNewTone(Tone.SINCERE).setDelOld(false)) - .toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.assertNotNull(newGreeting); - Assert.assertEquals(newGreeting.getId().longValue(), 1L); - Assert.assertEquals(newGreeting.getTone(), Tone.SINCERE); - - newGreeting = greetings.updateTone(1L, param -> param.setNewTone(Tone.INSULTING)).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.assertNotNull(newGreeting); - Assert.assertEquals(newGreeting.getId().longValue(), 1L); - Assert.assertEquals(newGreeting.getTone(), Tone.INSULTING); - } - - @Test public void testCollectionEntityActionWithNoReturn() throws Exception - { - // TestGroupsResource - String testEmail = "test@test.com"; - TransferOwnershipRequest ownershipRequest = new TransferOwnershipRequest(); - ownershipRequest.setNewOwnerContactEmail(testEmail); - int testId = 9999; - ownershipRequest.setNewOwnerMemberID(testId); - Groups groupsFluentClient = new GroupsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - CompletableFuture response = - groupsFluentClient.transferOwnership(1, param -> param.setRequest(ownershipRequest)) - .toCompletableFuture(); - response.get(5000, TimeUnit.MILLISECONDS); - assert(!response.isCompletedExceptionally()); - } - - @Test public void testCollectionActionWithReturn() throws Exception - { - Greetings greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - Assert.assertTrue(greetings.purge().toCompletableFuture().get(5000, TimeUnit.MILLISECONDS) == 100); - } - - @Test public void testCollectionActionWithNoReturn() throws Exception - { - Greetings greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - CompletableFuture stage = greetings.anotherAction(param -> param.setBitfield(new BooleanArray()) - .setRequest(new TransferOwnershipRequest()) - .setSomeString("") - .setStringMap(new StringMap())).toCompletableFuture(); - Assert.assertNull(stage.get(5000, TimeUnit.MILLISECONDS)); - assert(!stage.isCompletedExceptionally()); - } - - @Test(expectedExceptions = {RestLiResponseException.class}) - public void testCollectionActionWithException() throws Throwable - { - Greetings greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - CompletableFuture stage = greetings.exceptionTest().toCompletableFuture(); - try - { - stage.get(5000, TimeUnit.MILLISECONDS); - Assert.fail("expected exception"); - } - catch (Exception e) - { - assert(stage.isCompletedExceptionally()); - throw e.getCause(); - } - } - - @Test public void testActionSetActionWithReturn() throws Exception - { - Actions actionsFluentClient = new ActionsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - Assert.assertTrue(actionsFluentClient.ultimateAnswer().toCompletableFuture().get(5000, TimeUnit.MILLISECONDS) == 42); - } - - @Test public void testActionSetActionWithNoReturn() throws Exception - { - Actions actionsFluentClient = new ActionsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - Assert.assertNull(actionsFluentClient.returnVoid().toCompletableFuture().get(5000, TimeUnit.MILLISECONDS)); - - } - - @Test public void testActionSetActionWithTypeRef() throws Exception - { - // This end point use typeref for both ActionParam and Action methods' return value - Actions actionsFluentClient = new ActionsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - Assert.assertEquals(actionsFluentClient.customTypeRef(new CustomLong(500L)).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS), new CustomLong(500L)); - } - - // ----- Test TypeRef cases ------ - - @Test public void testTypeRef_keyTypeRef() throws Exception - { - CustomTypes2 customTypes2FluentClient = new CustomTypes2FluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - Assert.assertEquals(customTypes2FluentClient.get(new CustomLong(1L)) - .toCompletableFuture() - .get(5000, TimeUnit.MILLISECONDS) - .getId() - .longValue(), 1L); - } - - // ----- Testing ComplexKeysResource ------ - private static ComplexResourceKey getComplexKey(String major, String minor) - { - return new ComplexResourceKey<>( - new TwoPartKey().setMajor(major).setMinor(minor), - new TwoPartKey()); - } - - private static List> getBatchComplexKeys() - { - List> ids = - new ArrayList<>(); - ComplexResourceKey key1 = getComplexKey(StringTestKeys.SIMPLEKEY, StringTestKeys.SIMPLEKEY2); - ComplexResourceKey key2 = getComplexKey(StringTestKeys.URL, StringTestKeys.URL2); - ComplexResourceKey key3 = getComplexKey(StringTestKeys.ERROR, StringTestKeys.ERROR); - ids.add(key1); - ids.add(key2); - ids.add(key3); - - return ids; - } - - // ComplexKeysResource: Test "Get", "create", "batchGet" - @Test public void testComplexKey_get() throws Exception - { - ComplexKeys complexKeyClient = new ComplexKeysFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - ComplexResourceKey testKeys = getComplexKey( - StringTestKeys.SIMPLEKEY, StringTestKeys.SIMPLEKEY2); - - Message result = complexKeyClient.get(testKeys).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - - - Assert.assertTrue(result.hasId()); - Assert.assertEquals(result.getMessage(), StringTestKeys.SIMPLEKEY + " " + StringTestKeys.SIMPLEKEY2); - } - - @Test public void testComplexKey_batchGet() throws Exception - { - List> ids = getBatchComplexKeys(); - ComplexKeys complexKeyClient = new ComplexKeysFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - Map, EntityResponse> resultMap = - complexKeyClient.batchGet(new HashSet<>(ids)).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - - Assert.assertEquals(resultMap.size(), 3); - Assert.assertNotNull(resultMap.get(ids.get(0)).getEntity()); - Assert.assertNotNull(resultMap.get(ids.get(1)).getEntity()); - Assert.assertNotNull(resultMap.get(ids.get(2)).getError()); - } - - @Test public void testComplexKey_createThenGet() throws Exception - { - final String messageText = "newMessage"; - Message message = new Message(); - message.setMessage(messageText); - - ComplexKeys complexKeyClient = new ComplexKeysFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - ComplexResourceKey result = - complexKeyClient.create(message).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - - Assert.assertEquals(result, getComplexKey(messageText, messageText)); - Assert.assertEquals(complexKeyClient.get(result).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS).getMessage(), messageText); - } - - @Test public void testComplexKey_batchUpdate() throws Exception - { - ComplexKeys complexKeyClient = new ComplexKeysFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - final String messageText = StringTestKeys.SIMPLEKEY + " " + StringTestKeys.SIMPLEKEY2; - final Message message = new Message(); - message.setId(StringTestKeys.SIMPLEKEY + " " + StringTestKeys.SIMPLEKEY2); - message.setMessage(messageText); - message.setTone(Tone.INSULTING); - final String messageText2 = StringTestKeys.URL + " " + StringTestKeys.URL2; - final Message message2 = new Message(); - message2.setId(StringTestKeys.URL + " " + StringTestKeys.URL2); - message2.setMessage(messageText2); - message2.setTone(Tone.INSULTING); - - final Map, Message> inputs = new HashMap<>(); - ComplexResourceKey key1 = getComplexKey(StringTestKeys.SIMPLEKEY, StringTestKeys.SIMPLEKEY2); - ComplexResourceKey key2 = getComplexKey(StringTestKeys.URL, StringTestKeys.URL2); - inputs.put(key1, message); - inputs.put(key2, message2); - complexKeyClient.batchUpdate(inputs).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Map, EntityResponse> result = - complexKeyClient.batchGet(new HashSet<>(Arrays.asList(key1, key2))) - .toCompletableFuture() - .get(5000, TimeUnit.MILLISECONDS); - - Assert.assertNotNull(result.get(key1)); - Assert.assertNotNull(result.get(key2)); - Assert.assertEquals(result.get(key1).getEntity().getTone(), Tone.INSULTING); - Assert.assertEquals(result.get(key2).getEntity().getTone(), Tone.INSULTING); - } - - @Test public void testComplexKey_partialUpdate() throws Exception - { - ComplexKeys complexKeyClient = new ComplexKeysFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - Message message = new Message(); - message.setTone(Tone.FRIENDLY); - - PatchRequest patch = PatchGenerator.diffEmpty(message); - - final Map, PatchRequest> inputs = - new HashMap<>(); - ComplexResourceKey key1 = getComplexKey(StringTestKeys.SIMPLEKEY, StringTestKeys.SIMPLEKEY2); - ComplexResourceKey key2 = getComplexKey(StringTestKeys.URL, StringTestKeys.URL2); - inputs.put(key1, patch); - inputs.put(key2, patch); - - Map, UpdateStatus> result = - complexKeyClient.batchPartialUpdate(inputs).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - - // Update return valid result - Assert.assertEquals(result.get(key1).getStatus().intValue(), 204); - Assert.assertEquals(result.get(key2).getStatus().intValue(), 204); - - Map, EntityResponse> getResult = - complexKeyClient.batchGet(new HashSet<>(Arrays.asList(key1, key2))) - .toCompletableFuture() - .get(5000, TimeUnit.MILLISECONDS); - - Assert.assertEquals(getResult.get(key1).getEntity().getTone(), Tone.FRIENDLY); - Assert.assertEquals(getResult.get(key2).getEntity().getTone(), Tone.FRIENDLY); - } - - @Test public void testComplexKey_batchDelete() throws Exception - { - String messageText = "m1"; - Message message = new Message(); - message.setMessage(messageText); - - ComplexKeys complexKeyClient = new ComplexKeysFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - ComplexResourceKey createResponse = - complexKeyClient.create(message).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - - String messageText2 = "m2"; - message.setMessage(messageText2); - - createResponse = complexKeyClient.create(message).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - - ComplexResourceKey key1 = getComplexKey(messageText, messageText); - ComplexResourceKey key2 = getComplexKey(messageText2, messageText2); - - ArrayList> ids = new ArrayList<>(); - ids.add(key1); - ids.add(key2); - Map, UpdateStatus> deleteResponse = - complexKeyClient.batchDelete(new HashSet<>(ids)).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - - Assert.assertEquals(deleteResponse.size(), ids.size()); - Assert.assertEquals(deleteResponse.get(key1).getStatus().intValue(), 204); - Assert.assertEquals(deleteResponse.get(key2).getStatus().intValue(), 204); - Map, EntityResponse> getResponse = - complexKeyClient.batchGet(new HashSet<>(ids)).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(getResponse.get(key1).getError().getStatus().intValue(), 404); - Assert.assertEquals(getResponse.get(key2).getError().getStatus().intValue(), 404); - - } - - @Test public void testComplexKey_entityAction() throws Exception - { - ComplexResourceKey key = getComplexKey("major", "minor"); - ComplexKeys complexKeyClient = new ComplexKeysFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - Integer entity = complexKeyClient.entityAction(key).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(entity.longValue(), 1L); - } - - // ----- utils used for testing ------ - - private Greeting getGreeting() - { - return getGreeting(MESSAGE); - } - - private Greeting getGreeting(String message) - { - return new Greeting().setId(100L).setMessage(message).setTone(Tone.FRIENDLY); - } -} diff --git a/restli-int-test/src/test/java/com/linkedin/restli/examples/TestParseqBasedFluentClientApiWithProjections.java b/restli-int-test/src/test/java/com/linkedin/restli/examples/TestParseqBasedFluentClientApiWithProjections.java deleted file mode 100644 index 18e4f4b1ad..0000000000 --- a/restli-int-test/src/test/java/com/linkedin/restli/examples/TestParseqBasedFluentClientApiWithProjections.java +++ /dev/null @@ -1,509 +0,0 @@ -/* - Copyright (c) 2021 LinkedIn Corp. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License 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.linkedin.restli.examples; - -import com.linkedin.parseq.ParSeqUnitTestHelper; -import com.linkedin.restli.client.ParSeqRestliClient; -import com.linkedin.restli.client.ParSeqRestliClientBuilder; -import com.linkedin.restli.client.ParSeqRestliClientConfigBuilder; -import com.linkedin.restli.client.RestLiResponseException; -import com.linkedin.restli.client.util.PatchGenerator; -import com.linkedin.restli.common.BatchCollectionResponse; -import com.linkedin.restli.common.BatchFinderCriteriaResult; -import com.linkedin.restli.common.CollectionMetadata; -import com.linkedin.restli.common.CollectionResponse; -import com.linkedin.restli.common.CreateIdEntityStatus; -import com.linkedin.restli.common.CreateIdStatus; -import com.linkedin.restli.common.EntityResponse; -import com.linkedin.restli.common.IdEntityResponse; -import com.linkedin.restli.common.PatchRequest; -import com.linkedin.restli.common.UpdateEntityStatus; -import com.linkedin.restli.examples.greetings.api.Greeting; -import com.linkedin.restli.examples.greetings.api.GreetingCriteria; -import com.linkedin.restli.examples.greetings.api.Tone; -import com.linkedin.restli.examples.greetings.api.ValidationDemo; -import com.linkedin.restli.examples.greetings.api.myItem; -import com.linkedin.restli.examples.greetings.api.myRecord; -import com.linkedin.restli.examples.greetings.client.AutoValidationWithProjection; -import com.linkedin.restli.examples.greetings.client.AutoValidationWithProjectionFluentClient; -import com.linkedin.restli.examples.greetings.client.Batchfinders; -import com.linkedin.restli.examples.greetings.client.BatchfindersFluentClient; -import com.linkedin.restli.examples.greetings.client.CreateGreeting; -import com.linkedin.restli.examples.greetings.client.CreateGreetingFluentClient; -import com.linkedin.restli.examples.greetings.client.Greetings; -import com.linkedin.restli.examples.greetings.client.GreetingsFluentClient; -import com.linkedin.restli.examples.greetings.client.ManualProjections; -import com.linkedin.restli.examples.greetings.client.ManualProjectionsFluentClient; -import com.linkedin.restli.examples.greetings.client.PagingMetadataProjections; -import com.linkedin.restli.examples.greetings.client.PagingMetadataProjectionsFluentClient; -import com.linkedin.restli.examples.greetings.client.PartialUpdateGreeting; -import com.linkedin.restli.examples.greetings.client.PartialUpdateGreetingFluentClient; -import com.linkedin.restli.server.validation.RestLiValidationFilter; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import org.testng.Assert; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; -import org.testng.collections.Sets; - - -/** - * Test fluent client bindings with projections. - */ -public class TestParseqBasedFluentClientApiWithProjections extends RestLiIntegrationTest -{ - public static final String MESSAGE = "Create a new greeting"; - ParSeqUnitTestHelper _parSeqUnitTestHelper; - ParSeqRestliClient _parSeqRestliClient; - - @BeforeClass - void setUp() throws Exception - { - super.init(Arrays.asList(new RestLiValidationFilter())); - _parSeqUnitTestHelper = new ParSeqUnitTestHelper(); - _parSeqUnitTestHelper.setUp(); - _parSeqRestliClient = new ParSeqRestliClientBuilder() - .setClient(getClient()) - .setConfig(new ParSeqRestliClientConfigBuilder().build()) - .build(); - } - - @AfterClass - void tearDown() throws Exception - { - if (_parSeqUnitTestHelper != null) - { - _parSeqUnitTestHelper.tearDown(); - } - else - { - throw new RuntimeException("Tried to shut down Engine but it either has not even been created or has " - + "already been shut down"); - } - super.shutdown(); - } - - // Test Get request with simple projection - @Test - public void testGetWithProjection() throws Exception - { - ManualProjections projections = new ManualProjectionsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - CompletionStage result = projections.get(1L, - optionalParams -> optionalParams.withMask(mask -> mask.withMessage())); - CompletableFuture future = result.toCompletableFuture(); - Greeting greeting = future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertFalse(greeting.hasId()); - Assert.assertFalse(greeting.hasTone()); - Assert.assertTrue(greeting.hasMessage()); - } - - @Test - public void testBatchGetWithProjection() throws Exception - { - Greetings greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - Set ids = Sets.newHashSet(Arrays.asList(1L, 2L, 3L)); - CompletionStage>> result = greetings.batchGet(ids, - optionalParams -> optionalParams.withMask(mask -> mask.withTone())); - CompletableFuture>> future = result.toCompletableFuture(); - Map> resultMap = future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(resultMap.size(), ids.size()); - for (Long id : ids) - { - EntityResponse g = resultMap.get(id); - Assert.assertNotNull(g); - Assert.assertTrue(g.hasEntry()); - Assert.assertFalse(g.getEntity().hasId()); - Assert.assertFalse(g.getEntity().hasMessage()); - Assert.assertTrue(g.getEntity().hasTone()); - } - } - - @Test - public void testCreateAndGetWithProjection() throws Exception - { - CreateGreeting greetings = new CreateGreetingFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - String msg = Double.toString(Math.random()); - CompletionStage> result = greetings.createAndGet(getGreeting(msg), - optionalParams -> optionalParams.withMask(mask -> mask.withMessage())); - CompletableFuture> future = result.toCompletableFuture(); - Assert.assertNotNull(future.get(5000, TimeUnit.MILLISECONDS)); - Assert.assertFalse(future.get().getEntity().hasId()); - Assert.assertFalse(future.get().getEntity().hasTone()); - Assert.assertEquals(msg, future.get().getEntity().getMessage()); - } - - @Test - public void testBatchCreateAndGetWithProjection() throws Exception - { - CreateGreeting greetings = new CreateGreetingFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - String msg1 = Double.toString(Math.random()); - String msg2 = Double.toString(Math.random()); - CompletionStage>> - result = greetings.batchCreateAndGet(Arrays.asList(getGreeting(msg1), getGreeting(msg2)), - optionalParams -> optionalParams.withMask(mask -> mask.withId())); - CompletableFuture>> future = result.toCompletableFuture(); - List> entities = future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertEquals(entities.size(), 2); - Assert.assertNotNull(entities.get(0).getEntity()); - Assert.assertFalse(entities.get(0).getEntity().hasMessage()); - Assert.assertTrue(entities.get(0).getEntity().hasId()); - Assert.assertNotNull(entities.get(1).getEntity()); - Assert.assertFalse(entities.get(1).getEntity().hasMessage()); - Assert.assertTrue(entities.get(1).getEntity().hasId()); - } - - @Test - public void testPartialUpdateAndGetWithProjection() throws Exception - { - PartialUpdateGreeting - greetings = new PartialUpdateGreetingFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - Greeting original = getGreeting(); - String message = "Edited message: fluent api test partialUpdateAndGet"; - Greeting update = getGreeting(message); - CompletionStage result = greetings.partialUpdateAndGet(21L, PatchGenerator.diff(original, update), - optionalParams -> optionalParams.withMask(mask -> mask.withId())); - CompletableFuture future = result.toCompletableFuture(); - Greeting greeting = future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertFalse(greeting.hasMessage()); - Assert.assertTrue(greeting.hasId()); - } - - @Test - public void testBatchPartialUpdateAndGetWithProjection() throws Exception - { - PartialUpdateGreeting greetings = new PartialUpdateGreetingFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - Map> inputs = new HashMap<>(); - Greeting original = getGreeting(); - String message = "Edited message: fluent api test partialUpdateAndGet"; - Greeting update = getGreeting(message); - inputs.put(21L, PatchGenerator.diff(original, update)); - inputs.put(22L, PatchGenerator.diff(original, update)); - CompletionStage>> result = greetings.batchPartialUpdateAndGet(inputs, - optionalParams -> optionalParams.withMask(mask -> mask.withId().withMessage().withTone())); - CompletableFuture>> future = result.toCompletableFuture(); - Assert.assertNotNull(future.get(5000, TimeUnit.MILLISECONDS)); - Assert.assertEquals(future.get().get(21L).getEntity().getId().longValue(), 21L); - Assert.assertEquals(future.get().get(21L).getEntity().getMessage(), message); - Assert.assertEquals(future.get().get(22L).getEntity().getId().longValue(), 22L); - Assert.assertEquals(future.get().get(22L).getEntity().getMessage(), message); - } - - @Test - public void testGetAllWithFieldProjection() throws Exception - { - Greetings greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - // Create some greetings with "GetAll" in message so they will be returned by getAll test method.. - CompletionStage>> createResult = greetings.batchCreate( - Arrays.asList(getGreeting("GetAll").setId(200L), getGreeting("GetAll").setId(201L))); - - CompletionStage> result = createResult.thenCompose(ids -> greetings.getAll( - optionalParams -> optionalParams.withMask(mask -> mask.withMessage()))); - CompletableFuture> future = result.toCompletableFuture(); - List greetingList = future.get(5000, TimeUnit.MILLISECONDS).getElements(); - Assert.assertTrue(greetingList.size() >= 2); - for (Greeting greeting :greetingList) - { - Assert.assertFalse(greeting.hasId()); - Assert.assertTrue(greeting.getMessage().contains("GetAll")); - } - } - - @Test - public void testGetAllWithPagingProjection() throws Exception - { - PagingMetadataProjections - greetings = new PagingMetadataProjectionsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - CompletionStage> result = greetings.getAll( - params -> params.withPagingMask(pageMask -> pageMask.withTotal().withStart()) - ); - CompletableFuture> future = result.toCompletableFuture(); - CollectionResponse greetingResult = future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertTrue(greetingResult.hasPaging()); - Assert.assertFalse(greetingResult.getPaging().hasCount()); - Assert.assertTrue(greetingResult.getPaging().hasStart()); - Assert.assertEquals((int) greetingResult.getPaging().getStart(), 0); - Assert.assertFalse(greetingResult.getPaging().hasLinks()); - Assert.assertTrue(greetingResult.getPaging().getTotal() > 0); - - // Same request without projection. - result = greetings.getAll(); - future = result.toCompletableFuture(); - greetingResult = future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertTrue(greetingResult.hasPaging()); - Assert.assertTrue(greetingResult.getPaging().hasCount()); - Assert.assertTrue(greetingResult.getPaging().hasStart()); - Assert.assertTrue(greetingResult.getPaging().hasLinks()); - // The resource applies manual projection and returns total only when it is explicitly projected. So no total field here. - } - - @Test - public void testGetAllWithMetadataProjection() throws Exception - { - PagingMetadataProjections greetings = new PagingMetadataProjectionsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - CompletionStage> result = greetings.getAll( - params -> params.withMetadataMask(metadataMask -> metadataMask.withMessage()) - ); - CompletableFuture> future = result.toCompletableFuture(); - CollectionResponse greetingResult = future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertNotNull(greetingResult.getMetadataRaw()); - Greeting metadata = new Greeting(greetingResult.getMetadataRaw()); - Assert.assertTrue(metadata.hasMessage()); - Assert.assertFalse(metadata.hasTone()); - - // Same request without projection. - result = greetings.findByMetadataAutomaticPagingFullyAutomatic(); - future = result.toCompletableFuture(); - greetingResult = future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertNotNull(greetingResult.getMetadataRaw()); - metadata = new Greeting(greetingResult.getMetadataRaw()); - Assert.assertTrue(metadata.hasMessage()); - Assert.assertTrue(metadata.hasTone()); - } - - @Test - public void testFinderWithProjection() throws Exception - { - Greetings greetings = new GreetingsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - CompletionStage> result = greetings.findBySearch( - params -> params.setTone(Tone.FRIENDLY) - .withMask(fieldsMask -> fieldsMask.withTone().withMessage()) - ); - CompletableFuture> future = result.toCompletableFuture(); - List greetingList = future.get(5000, TimeUnit.MILLISECONDS).getElements(); - Assert.assertTrue(greetingList.size() > 0); - for (Greeting greeting : greetingList) - { - Assert.assertEquals(greeting.getTone(), Tone.FRIENDLY); - Assert.assertFalse(greeting.hasId()); - Assert.assertTrue(greeting.hasMessage()); - } - } - - @Test - public void testFinderWithPagingProjection() throws Exception - { - PagingMetadataProjections greetings = new PagingMetadataProjectionsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - CompletionStage> result = greetings.findByMetadataAutomaticPagingFullyAutomatic( - params -> params.withPagingMask(pageMask -> pageMask.withTotal()) - ); - CompletableFuture> future = result.toCompletableFuture(); - CollectionResponse greetingResult = future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertTrue(greetingResult.hasPaging()); - Assert.assertFalse(greetingResult.getPaging().hasCount()); - Assert.assertFalse(greetingResult.getPaging().hasStart()); - Assert.assertFalse(greetingResult.getPaging().hasLinks()); - Assert.assertTrue(greetingResult.getPaging().hasTotal()); - Assert.assertTrue(greetingResult.getPaging().getTotal() > 0); - - // Same request without projection. - result = greetings.findByMetadataAutomaticPagingFullyAutomatic(); - future = result.toCompletableFuture(); - greetingResult = future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertTrue(greetingResult.hasPaging()); - Assert.assertTrue(greetingResult.getPaging().hasCount()); - Assert.assertTrue(greetingResult.getPaging().hasStart()); - Assert.assertTrue(greetingResult.getPaging().hasLinks()); - Assert.assertTrue(greetingResult.getPaging().hasTotal()); - Assert.assertTrue(greetingResult.getPaging().getTotal() > 0); - } - - @Test - public void testFinderWithMetadataProjection() throws Exception - { - PagingMetadataProjections greetings = new PagingMetadataProjectionsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - CompletionStage> result = greetings.findByMetadataAutomaticPagingFullyAutomatic( - params -> params.withMetadataMask(metadataMask -> metadataMask.withId()) - ); - CompletableFuture> future = result.toCompletableFuture(); - CollectionResponse greetingResult = future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertNotNull(greetingResult.getMetadataRaw()); - Greeting metadata = new Greeting(greetingResult.getMetadataRaw()); - Assert.assertFalse(metadata.hasMessage()); - Assert.assertFalse(metadata.hasTone()); - Assert.assertTrue(metadata.hasId()); - - // Same request without projection. - result = greetings.findByMetadataAutomaticPagingFullyAutomatic(); - future = result.toCompletableFuture(); - greetingResult = future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertNotNull(greetingResult.getMetadataRaw()); - metadata = new Greeting(greetingResult.getMetadataRaw()); - Assert.assertTrue(metadata.hasMessage()); - Assert.assertTrue(metadata.hasTone()); - Assert.assertTrue(metadata.hasId()); - } - - @Test - public void testBatchFinderWithProjection() throws Exception - { - Batchfinders batchFinders = new BatchfindersFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - GreetingCriteria c1 = new GreetingCriteria().setId(1L).setTone(Tone.SINCERE); - GreetingCriteria c2 = new GreetingCriteria().setId(2L).setTone(Tone.FRIENDLY); - CompletionStage> result = batchFinders.findBySearchGreetings( - Arrays.asList(c1, c2), "hello world", - params -> params.withMask(mask -> mask.withTone().withId())); - CompletableFuture> future = result.toCompletableFuture(); - List> batchResult = future.get(5000, TimeUnit.MILLISECONDS).getResults(); - - List greetings1 = batchResult.get(0).getElements(); - Assert.assertTrue(greetings1.get(0).hasTone()); - Assert.assertEquals(greetings1.get(0).getTone(), Tone.SINCERE); - Assert.assertTrue(greetings1.get(0).hasId()); - Assert.assertFalse(greetings1.get(0).hasMessage()); - - List greetings2 = batchResult.get(1).getElements(); - Assert.assertTrue(greetings2.get(0).hasId()); - Assert.assertEquals(greetings2.get(0).getTone(), Tone.FRIENDLY); - Assert.assertTrue(greetings2.get(0).hasId()); - Assert.assertFalse(greetings2.get(0).hasMessage()); - } - - @Test - public void testBatchFinderWithPagingProjection() throws Exception - { - Batchfinders batchFinders = new BatchfindersFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - GreetingCriteria c1 = new GreetingCriteria().setId(1L).setTone(Tone.SINCERE); - GreetingCriteria c2 = new GreetingCriteria().setId(2L).setTone(Tone.FRIENDLY); - CompletionStage> result = batchFinders.findBySearchGreetings( - Arrays.asList(c1, c2), "hello world", - params -> params.withPagingMask(mask -> mask.withStart()) - .withMask(fieldMask -> fieldMask.withId().withTone()) - ); - CompletableFuture> future = result.toCompletableFuture(); - List> batchResult = future.get(5000, TimeUnit.MILLISECONDS).getResults(); - - for (BatchFinderCriteriaResult singleCriteria : batchResult) - { - CollectionMetadata paging = singleCriteria.getPaging(); - Assert.assertTrue(paging.hasStart()); - Assert.assertFalse(paging.hasCount()); - Assert.assertFalse(paging.hasTotal()); - Assert.assertFalse(paging.hasLinks()); - } - - // Same request without paging mask (setting field mask to avoid validation error on server) - result = batchFinders.findBySearchGreetings(Arrays.asList(c1, c2), "hello world", - params -> params.withMask(fieldMask -> fieldMask.withId().withTone()) - ); - future = result.toCompletableFuture(); - batchResult = future.get(5000, TimeUnit.MILLISECONDS).getResults(); - - for (BatchFinderCriteriaResult singleCriteria : batchResult) - { - CollectionMetadata paging = singleCriteria.getPaging(); - Assert.assertTrue(paging.hasStart()); - Assert.assertTrue(paging.hasCount()); - Assert.assertTrue(paging.hasTotal()); - Assert.assertTrue(paging.hasLinks()); - } - } - - // Test Get request without projection - @Test - public void testGetFull() throws Exception - { - ManualProjections projections = new ManualProjectionsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - CompletionStage result = projections.get(1L); - CompletableFuture future = result.toCompletableFuture(); - Greeting greeting = future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertTrue(greeting.hasId()); - Assert.assertTrue(greeting.hasTone()); - Assert.assertTrue(greeting.hasMessage()); - } - - // Test using optional parameter that disables projection - @Test - public void testGetWithProjectionDisabled() throws Exception - { - ManualProjections projections = new ManualProjectionsFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - CompletionStage result = projections.get(1L, - optionalParams -> optionalParams.withMask(mask -> mask.withMessage()) - .setIgnoreProjection(true)); - CompletableFuture future = result.toCompletableFuture(); - Greeting greeting = future.get(5000, TimeUnit.MILLISECONDS); - // these fields would have been excluded by the framework if automatic projection was enabled - Assert.assertTrue(greeting.hasId()); - Assert.assertTrue(greeting.hasTone()); - } - - @Test - public void testValidationWithNoProjection() throws Exception - { - AutoValidationWithProjection - validationDemos = new AutoValidationWithProjectionFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - CompletionStage result = validationDemos.get(1); - try { - CompletableFuture future = result.toCompletableFuture(); - future.get(5000, TimeUnit.MILLISECONDS); - Assert.fail("Request should have failed validation"); - } catch (ExecutionException e) { - Assert.assertTrue(e.getCause() instanceof RestLiResponseException); - RestLiResponseException responseException = (RestLiResponseException) e.getCause(); - Assert.assertEquals(responseException.getServiceErrorMessage(), - TestRestLiValidationWithProjection.EXPECTED_VALIDATION_DEMO_FAILURE_MESSAGE); - } - } - - @Test - public void testValidationWithOnlyValidFieldsProjected() throws Exception - { - AutoValidationWithProjection validationDemos = new AutoValidationWithProjectionFluentClient(_parSeqRestliClient, _parSeqUnitTestHelper.getEngine()); - - CompletionStage result = validationDemos.get(1, - optionalParams -> optionalParams.withMask(mask -> mask.withStringB() - .withIncludedB() - .withUnionFieldWithInlineRecord(m1 -> m1.withMyRecord(myRecord.ProjectionMask::withFoo2)) - .withArrayWithInlineRecord(itemMask -> itemMask.withItems(myItem.ProjectionMask::withBar1)) - .withMapWithTyperefs(m -> m.withValues(Greeting.ProjectionMask::withId)) - .withValidationDemoNext(ValidationDemo.ProjectionMask::withIntB))); - CompletableFuture future = result.toCompletableFuture(); - ValidationDemo validationDemo = future.get(5000, TimeUnit.MILLISECONDS); - Assert.assertNotNull(validationDemo); - } - - private Greeting getGreeting() - { - return getGreeting(MESSAGE); - } - - private Greeting getGreeting(String message) - { - return new Greeting().setMessage(message).setTone(Tone.FRIENDLY); - } -} diff --git a/restli-tools/build.gradle b/restli-tools/build.gradle index 19dcd7d66f..1cd3c817b1 100644 --- a/restli-tools/build.gradle +++ b/restli-tools/build.gradle @@ -5,20 +5,16 @@ plugins { // This block is only supported and required when building with JDK11+ if (JavaVersion.current() >= JavaVersion.VERSION_11) { // We need a custom source set for JDK11+ classes - // Note that we use java 17 as the source set entry point to minimize the impact on existing Java 8 and Java 11 consumers. - // This is because the JVM of the consumer will pick up the highest bytecode version which is lower than or equal to the JVM version. - // So, for Java 8 and Java 11 consumers, Java 17 does not qualify since it is higher than 8 and 11, which means they will - // fall back to using the default Java 8 level bytecode. sourceSets { - java17 { + java11 { java { - srcDirs = ['src/main/java17'] + srcDirs = ['src/main/java11'] } } } // This compile task is automatically generated by java-library plugin for custom JDK11 only source set // We need to explicitly set code versions and override defaults - compileJava17Java { + compileJava11Java { sourceCompatibility = 11 targetCompatibility = 11 options.compilerArgs.addAll(['--release', '11']) @@ -28,8 +24,8 @@ if (JavaVersion.current() >= JavaVersion.VERSION_11) { // We package JDK11+ classes into a custom folder. // JVM will load the class if version of the class is equal or less than version of JVM. // Thus JDK8 or JDK9 will load default class from "com" folder and JDK11+ will load the custom folder - into('META-INF/versions/17') { - from sourceSets.java17.output + into('META-INF/versions/11') { + from sourceSets.java11.output } manifest { attributes( @@ -67,29 +63,30 @@ dependencies { if (JavaVersion.current() >= JavaVersion.VERSION_11) { // Custom dependency set is required for JDK11+ only source set - java17Compile project(':data') - java17Compile project(':r2-core') - java17Compile project(':li-jersey-uri') - java17Compile project(':generator') - java17Compile project(':pegasus-common') - java17Compile project(':restli-common') - java17Compile project(':restli-client') - java17Compile project(':restli-server') - java17Compile externalDependency.caffeine - java17Compile externalDependency.commonsIo - java17Compile externalDependency.codemodel - java17Compile externalDependency.commonsCli - java17Compile externalDependency.commonsLang - java17Compile externalDependency.jacksonCore - java17Compile externalDependency.jacksonDataBind - java17Compile externalDependency.velocity + java11Implementation files(sourceSets.main.output.classesDirs) + java11Compile project(':data') + java11Compile project(':r2-core') + java11Compile project(':li-jersey-uri') + java11Compile project(':generator') + java11Compile project(':pegasus-common') + java11Compile project(':restli-common') + java11Compile project(':restli-client') + java11Compile project(':restli-server') + java11Compile externalDependency.caffeine + java11Compile externalDependency.commonsIo + java11Compile externalDependency.codemodel + java11Compile externalDependency.commonsCli + java11Compile externalDependency.commonsLang + java11Compile externalDependency.jacksonCore + java11Compile externalDependency.jacksonDataBind + java11Compile externalDependency.velocity - java17Compile externalDependency.mockito - java17Compile externalDependency.testng - java17Compile externalDependency.junit - java17Compile externalDependency.commonsHttpClient - java17Compile externalDependency.javaparser + java11Compile externalDependency.mockito + java11Compile externalDependency.testng + java11Compile externalDependency.junit + java11Compile externalDependency.commonsHttpClient + java11Compile externalDependency.javaparser } } -apply from: "${buildScriptDirPath}/restModel.gradle" +apply from: "${buildScriptDirPath}/restModel.gradle" \ No newline at end of file diff --git a/restli-tools/src/main/java17/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java b/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java similarity index 99% rename from restli-tools/src/main/java17/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java rename to restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java index c6c9c7a65f..e84975d209 100644 --- a/restli-tools/src/main/java17/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java +++ b/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java @@ -278,7 +278,8 @@ private static String buildDoc(String docText) { if (docText != null && !docText.isEmpty()) { - return docText; +// return docText; + return DocletHelper.processDocCommentStr(docText); } return null; } diff --git a/restli-tools/src/main/java17/com/linkedin/restli/tools/idlgen/DocletHelper.java b/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletHelper.java similarity index 100% rename from restli-tools/src/main/java17/com/linkedin/restli/tools/idlgen/DocletHelper.java rename to restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletHelper.java diff --git a/restli-tools/src/main/java17/com/linkedin/restli/tools/idlgen/RestLiDoclet.java b/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/RestLiDoclet.java similarity index 99% rename from restli-tools/src/main/java17/com/linkedin/restli/tools/idlgen/RestLiDoclet.java rename to restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/RestLiDoclet.java index b382cc6f3c..a50824e26e 100644 --- a/restli-tools/src/main/java17/com/linkedin/restli/tools/idlgen/RestLiDoclet.java +++ b/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/RestLiDoclet.java @@ -265,7 +265,7 @@ public static MethodIdentity create(ExecutableElement method) final List parameterTypeNames = new ArrayList<>(); for (VariableElement param : method.getParameters()) { TypeMirror type = param.asType(); - parameterTypeNames.add(type.toString()); + parameterTypeNames.add(DocletHelper.getCanonicalName(type.toString())); } return new MethodIdentity(method.getEnclosingElement().toString() + "." + method.getSimpleName().toString(), From f4394e8c254cd5b7e263f718e4c46cc548641282 Mon Sep 17 00:00:00 2001 From: Yan Zhou Date: Wed, 6 Sep 2023 12:01:52 -0700 Subject: [PATCH 08/14] remove lombok hacks used in debugging --- build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle b/build.gradle index 928145b102..7b4992ca89 100644 --- a/build.gradle +++ b/build.gradle @@ -231,7 +231,6 @@ subprojects { // so they don't conflict with "velocity-engine-core" module used // for versions >=2.0 all*.exclude group: 'org.apache.velocity', module: 'velocity' - all*.exclude group: 'org.projectlombok', module: 'lombok' } if (!(it.name in ['data-avro', 'restli-int-test'])) { From b5f08dbd7556579517bba5c8af776ba9c0f1ecc6 Mon Sep 17 00:00:00 2001 From: Yan Zhou Date: Mon, 11 Sep 2023 14:19:23 -0700 Subject: [PATCH 09/14] workaround the lombok issue and convert docTreeList to string representation --- build_script/restModel.gradle | 2 +- .../pegasus/gradle/PegasusPlugin.java | 2 +- .../tools/idlgen/DocletDocsProvider.java | 87 +++++++------------ .../restli/tools/idlgen/DocletHelper.java | 32 +++---- .../restli/tools/idlgen/RestLiDoclet.java | 63 ++++++-------- 5 files changed, 69 insertions(+), 117 deletions(-) diff --git a/build_script/restModel.gradle b/build_script/restModel.gradle index ee9f6c6318..82a41b3fff 100644 --- a/build_script/restModel.gradle +++ b/build_script/restModel.gradle @@ -79,7 +79,7 @@ project.sourceSets.all { SourceSet sourceSet -> project.tasks[sourceSet.compileJavaTaskName].dependsOn(rootProject.ext.build.restModelGenerateTasks[sourceSet]) } - // Use 'jar' instead a custom task name for Java 11 source set in the multi-release jar + // Use 'jar' instead of a custom task name for Java 11 source set in the multi-release jar final Task jarTask = project.tasks[sourceSet.getName().endsWith('11') ? 'jar' : sourceSet.getTaskName('', 'jar')] jarTask.from(inputParentDirPath) { include "${pegasusDirName}${File.separatorChar}**${File.separatorChar}*.pdsc" diff --git a/gradle-plugins/src/main/java/com/linkedin/pegasus/gradle/PegasusPlugin.java b/gradle-plugins/src/main/java/com/linkedin/pegasus/gradle/PegasusPlugin.java index a87961c7eb..39c500f0ab 100644 --- a/gradle-plugins/src/main/java/com/linkedin/pegasus/gradle/PegasusPlugin.java +++ b/gradle-plugins/src/main/java/com/linkedin/pegasus/gradle/PegasusPlugin.java @@ -1268,7 +1268,7 @@ protected void configureRestModelGeneration(Project project, SourceSet sourceSet // generate the rest model FileCollection restModelCodegenClasspath = project.getConfigurations().getByName(PEGASUS_PLUGIN_CONFIGURATION) .plus(project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)) - .plus(sourceSet.getRuntimeClasspath()).plus(sourceSet.getCompileClasspath()); + .plus(sourceSet.getRuntimeClasspath()); String destinationDirPrefix = getGeneratedDirPath(project, sourceSet, REST_GEN_TYPE) + File.separatorChar; FileCollection restModelResolverPath = apiProject.files(getDataSchemaPath(project, sourceSet)) .plus(getDataModelConfig(apiProject, sourceSet)); diff --git a/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java b/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java index e84975d209..1d6c69b8fe 100644 --- a/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java +++ b/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java @@ -59,8 +59,7 @@ * * @author dellamag */ -public class DocletDocsProvider implements DocsProvider -{ +public class DocletDocsProvider implements DocsProvider { private static final Logger log = LoggerFactory.getLogger(DocletDocsProvider.class); private final String _apiName; @@ -73,8 +72,7 @@ public class DocletDocsProvider implements DocsProvider public DocletDocsProvider(String apiName, String[] classpath, String[] sourcePaths, - String[] resourcePackages) - { + String[] resourcePackages) { _apiName = apiName; _classpath = classpath; _sourcePaths = sourcePaths; @@ -82,8 +80,7 @@ public DocletDocsProvider(String apiName, } @Override - public Set supportedFileExtensions() - { + public Set supportedFileExtensions() { return Collections.singleton(".java"); } @@ -126,31 +123,25 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { } @Override - public void registerSourceFiles(Collection sourceFileNames) - { + public void registerSourceFiles(Collection sourceFileNames) { log.debug("Executing Javadoc tool..."); final String flatClasspath; - if (_classpath == null) - { + if (_classpath == null) { flatClasspath = System.getProperty("java.class.path"); } - else - { + else { flatClasspath = StringUtils.join(_classpath, ":"); } final PrintWriter sysoutWriter = new PrintWriter(System.out, true); final PrintWriter nullWriter = new PrintWriter(new NullWriter()); - List sourceFiles; - try - { + try { sourceFiles = collectSourceFiles(Arrays.asList(_sourcePaths), _resourcePackages == null ? null : Arrays.asList(_resourcePackages)); } - catch (IOException e) - { + catch (IOException e) { throw new RuntimeException("Failed to collect source files", e); } @@ -164,11 +155,9 @@ public void registerSourceFiles(Collection sourceFileNames) } @Override - public String getClassDoc(Class resourceClass) - { + public String getClassDoc(Class resourceClass) { final TypeElement doc = _doclet.getClassDoc(resourceClass); - if (doc == null) - { + if (doc == null) { return null; } return buildDoc(_doclet.getDocCommentStrForElement(doc)); @@ -198,11 +187,9 @@ private String formatDeprecatedTags(Element element) { } } @Override - public String getMethodDoc(Method method) - { + public String getMethodDoc(Method method) { final ExecutableElement doc = _doclet.getMethodDoc(method); - if (doc == null) - { + if (doc == null) { return null; } @@ -210,38 +197,28 @@ public String getMethodDoc(Method method) } @Override - public String getMethodDeprecatedTag(Method method) - { + public String getMethodDeprecatedTag(Method method) { final ExecutableElement doc = _doclet.getMethodDoc(method); - if (doc == null) - { + if (doc == null) { return null; } - return formatDeprecatedTags(doc); } @Override - public String getParamDoc(Method method, String name) - { + public String getParamDoc(Method method, String name) { final ExecutableElement methodDoc = _doclet.getMethodDoc(method); - if (methodDoc == null) - { + if (methodDoc == null) { return null; } Map paramTags = _doclet.getParamTags(methodDoc); - for (VariableElement parameter : methodDoc.getParameters()) - { - for (AnnotationMirror annotationMirror : parameter.getAnnotationMirrors()) - { - if (isQueryParamAnnotation(annotationMirror) || isActionParamAnnotation(annotationMirror)) - { - for (Map.Entry entry : annotationMirror.getElementValues().entrySet()) - { - if ("value".equals(entry.getKey().getSimpleName().toString()) && name.equals(entry.getValue().getValue())) - { + for (VariableElement parameter : methodDoc.getParameters()) { + for (AnnotationMirror annotationMirror : parameter.getAnnotationMirrors()) { + if (isQueryParamAnnotation(annotationMirror) || isActionParamAnnotation(annotationMirror)) { + for (Map.Entry entry : annotationMirror.getElementValues().entrySet()) { + if ("value".equals(entry.getKey().getSimpleName().toString()) && name.equals(entry.getValue().getValue())) { return paramTags.get(parameter.getSimpleName().toString()); } } @@ -253,8 +230,7 @@ public String getParamDoc(Method method, String name) } @Override - public String getReturnDoc(Method method) - { + public String getReturnDoc(Method method) { ExecutableElement methodElement = _doclet.getMethodDoc(method); if (methodElement != null) { for (DocTree docTree : _doclet.getDocCommentTreeForMethod(method).getBlockTags()) { @@ -264,33 +240,28 @@ public String getReturnDoc(Method method) DocTree.Kind kind = docTree.getKind(); if (kind == DocTree.Kind.RETURN) { ReturnTree returnTree = (ReturnTree) docTree; - return buildDoc(returnTree.getDescription().toString()); + return buildDoc(DocletHelper.convertDocTreeListToStr(returnTree.getDescription())); } else if (kind == DocTree.Kind.UNKNOWN_BLOCK_TAG) { UnknownBlockTagTree unknownBlockTagTree = (UnknownBlockTagTree) docTree; - return buildDoc(unknownBlockTagTree.getContent().toString()); + return buildDoc(DocletHelper.convertDocTreeListToStr(unknownBlockTagTree.getContent())); } } } return null; } - private static String buildDoc(String docText) - { - if (docText != null && !docText.isEmpty()) - { -// return docText; - return DocletHelper.processDocCommentStr(docText); + private static String buildDoc(String docText) { + if (docText != null && !docText.isEmpty()) { + return docText; } return null; } - private static boolean isQueryParamAnnotation(AnnotationMirror annotationMirror) - { + private static boolean isQueryParamAnnotation(AnnotationMirror annotationMirror) { return QueryParam.class.getCanonicalName().equals(annotationMirror.getAnnotationType().toString()); } - private static boolean isActionParamAnnotation(AnnotationMirror annotationMirror) - { + private static boolean isActionParamAnnotation(AnnotationMirror annotationMirror) { return ActionParam.class.getCanonicalName().equals(annotationMirror.getAnnotationType().toString()); } } \ No newline at end of file diff --git a/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletHelper.java b/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletHelper.java index 5581034aa4..471d88408d 100644 --- a/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletHelper.java +++ b/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletHelper.java @@ -17,8 +17,12 @@ package com.linkedin.restli.tools.idlgen; import com.sun.source.doctree.DocCommentTree; +import com.sun.source.doctree.DocTree; +import java.util.ArrayList; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; /** @@ -51,27 +55,15 @@ public static String getCanonicalName(String inputTypeStr) { } /** - * When {@link DocCommentTree} return Java Doc comment string, they wrap certain chars with commas. For example, - *

will become ,

, This method serves to remove such redundant commas if any. + * Return the string representation of a list of {@link DocTree}. * - * @param inputCommentStr input Java Doc comment string generated by {@link DocCommentTree} - * @return processed string with redundant commas removed + * @param docTreeList a list of {@link DocTree} + * @return string representation of the docTreeList */ - public static String processDocCommentStr(String inputCommentStr) { - if (inputCommentStr == null) { - return null; - } - Pattern pattern = Pattern.compile("(\\,)(<.*>|\\{@.*\\}|>|<)(\\,)?"); - Matcher matcher = pattern.matcher(inputCommentStr); - StringBuilder sb = new StringBuilder(); - int start = 0; - while (matcher.find()) { - sb.append(inputCommentStr.substring(start, matcher.start())); - int end = matcher.group(3) == null ? matcher.end() : matcher.end() - 1; - sb.append(inputCommentStr.substring(matcher.start() + 1, end).replace(",", "")); - start = matcher.end(); - } - sb.append(inputCommentStr.substring(start)); - return sb.toString(); + public static String convertDocTreeListToStr(List docTreeList) { + List docTreeStrList = docTreeList.stream().map( + docTree -> {return docTree.toString();} + ).collect(Collectors.toList()); + return String.join("", docTreeStrList); } } \ No newline at end of file diff --git a/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/RestLiDoclet.java b/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/RestLiDoclet.java index a50824e26e..3697b7268e 100644 --- a/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/RestLiDoclet.java +++ b/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/RestLiDoclet.java @@ -64,8 +64,7 @@ * * @author dellamag */ -public class RestLiDoclet implements Doclet -{ +public class RestLiDoclet implements Doclet { private static RestLiDoclet _currentDocLet = null; private final DocInfo _docInfo; private final DocletEnvironment _docEnv; @@ -89,8 +88,7 @@ public static synchronized RestLiDoclet generateDoclet(String programName, PrintWriter noticeWriter, String flatClassPath, List sourceFiles - ) - { + ) { noticeWriter.println("Generating Javadoc for " + programName); DocumentationTool docTool = ToolProvider.getSystemDocumentationTool(); @@ -102,6 +100,12 @@ public static synchronized RestLiDoclet generateDoclet(String programName, List taskOptions = new ArrayList<>(); taskOptions.add("-classpath"); taskOptions.add(flatClassPath); + // When the DocumentationTool API is used to generate JavaDoc, it attempts to compile the source files and + // search for classes generated by Lombok annotations such as builders, getters, and setters. However, these generated + // classes are not passed to the DocumentationTool API, resulting in a "cannot find symbol" error. + // Since we don't need to generate JavaDoc for these classes, we can simply ignore the errors per: + // https://stackoverflow.com/questions/38621202/ignore-minor-errors-using-javadoc + taskOptions.add("--ignore-source-errors"); // Create and run the Javadoc task DocumentationTool.DocumentationTask task = docTool.getTask(errWriter, @@ -123,8 +127,7 @@ public static synchronized RestLiDoclet generateDoclet(String programName, fileObjects); boolean success = task.call(); - if (!success) - { + if (!success) { throw new IllegalArgumentException("Javadoc generation failed"); } @@ -182,8 +185,7 @@ public SourceVersion getSupportedSourceVersion() { return SourceVersion.latest(); } - private RestLiDoclet(DocInfo docInfo, DocletEnvironment docEnv) - { + private RestLiDoclet(DocInfo docInfo, DocletEnvironment docEnv) { _docInfo = docInfo; _docEnv = docEnv; } @@ -203,8 +205,7 @@ public RestLiDoclet() { * @param resourceClass resource class to be queried * @return corresponding {@link TypeElement} */ - public TypeElement getClassDoc(Class resourceClass) - { + public TypeElement getClassDoc(Class resourceClass) { return _docInfo.getClassDoc(resourceClass.getCanonicalName()); } @@ -214,14 +215,12 @@ public TypeElement getClassDoc(Class resourceClass) * @param method Java method to be queried * @return corresponding {@link ExecutableElement} */ - public ExecutableElement getMethodDoc(Method method) - { + public ExecutableElement getMethodDoc(Method method) { final MethodIdentity methodId = MethodIdentity.create(method); return _docInfo.getMethodDoc(methodId); } - private static class DocInfo - { + private static class DocInfo { public TypeElement getClassDoc(String className) { return _classNameToClassDoc.get(className); } @@ -242,26 +241,22 @@ public void setMethodDoc(MethodIdentity methodId, ExecutableElement methodDoc) { private final Map _methodIdToMethodDoc = new HashMap<>(); } - private static class MethodIdentity - { - public static MethodIdentity create(Method method) - { + private static class MethodIdentity { + public static MethodIdentity create(Method method) { final List parameterTypeNames = new ArrayList<>(); // type parameters are not included in identity because of differences between reflection and Doclet: // e.g. for Collection: // reflection Type.toString() -> Collection // Doclet Type.toString() -> Collection - for (Class paramClass: method.getParameterTypes()) - { + for (Class paramClass: method.getParameterTypes()) { parameterTypeNames.add(paramClass.getCanonicalName()); } return new MethodIdentity(method.getDeclaringClass().getName() + "." + method.getName(), parameterTypeNames); } - public static MethodIdentity create(ExecutableElement method) - { + public static MethodIdentity create(ExecutableElement method) { final List parameterTypeNames = new ArrayList<>(); for (VariableElement param : method.getParameters()) { TypeMirror type = param.asType(); @@ -272,15 +267,13 @@ public static MethodIdentity create(ExecutableElement method) parameterTypeNames); } - private MethodIdentity(String methodQualifiedName, List parameterTypeNames) - { + private MethodIdentity(String methodQualifiedName, List parameterTypeNames) { _methodQualifiedName = methodQualifiedName; _parameterTypeNames = parameterTypeNames; } @Override - public int hashCode() - { + public int hashCode() { return new HashCodeBuilder(17, 29). append(_methodQualifiedName). append(_parameterTypeNames). @@ -288,20 +281,16 @@ public int hashCode() } @Override - public boolean equals(Object obj) - { - if (this == obj) - { + public boolean equals(Object obj) { + if (this == obj) { return true; } - if (obj == null) - { + if (obj == null) { return false; } - if (getClass() != obj.getClass()) - { + if (getClass() != obj.getClass()) { return false; } @@ -331,7 +320,7 @@ public List getDeprecatedTags(Element element) { for (DocTree docTree :docCommentTree.getBlockTags()) { if (docTree.getKind() == DocTree.Kind.DEPRECATED) { DeprecatedTree deprecatedTree = (DeprecatedTree) docTree; - String deprecatedComment = deprecatedTree.getBody().toString(); + String deprecatedComment = DocletHelper.convertDocTreeListToStr(deprecatedTree.getBody()); deprecatedTags.add(deprecatedComment); } } @@ -354,7 +343,7 @@ public Map getParamTags(ExecutableElement executableElement) { if (docTree.getKind() == DocTree.Kind.PARAM) { ParamTree paramTree = (ParamTree) docTree; String paramName = paramTree.getName().toString(); - String paramComment = paramTree.getDescription().toString(); + String paramComment = DocletHelper.convertDocTreeListToStr(paramTree.getDescription()); if (paramComment != null) { paramTags.put(paramName, paramComment); } @@ -381,7 +370,7 @@ public DocCommentTree getDocCommentTreeForElement(Element element) { */ public String getDocCommentStrForElement(Element element) { DocCommentTree docCommentTree = getDocCommentTreeForElement(element); - return docCommentTree == null ? null : docCommentTree.getFullBody().toString(); + return docCommentTree == null ? null : DocletHelper.convertDocTreeListToStr(docCommentTree.getFullBody()); } /** From df229802d0ff00c31eb5caee740511110e494e89 Mon Sep 17 00:00:00 2001 From: Yan Zhou Date: Tue, 12 Sep 2023 11:03:11 -0700 Subject: [PATCH 10/14] add more comments --- build.gradle | 4 ++++ .../com/linkedin/restli/internal/common/URIDecoderUtils.java | 2 ++ .../java11/com/linkedin/restli/tools/idlgen/RestLiDoclet.java | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7b4992ca89..ec40e7c87b 100644 --- a/build.gradle +++ b/build.gradle @@ -158,6 +158,10 @@ allprojects { if (JavaVersion.current() >= JavaVersion.VERSION_11) { sourceCompatibility = 8 targetCompatibility = 8 + // Ideally we should uncomment the line below to set the release option to Java 8 to restrict the use of new APIs after Java 8. + // However, com.sun.javadoc APIs were moved from tools.jar in Java 8 to JDK internals in Java 11. + // Therefore, if we set the release option to Java 8, we will not be able to use the APIs in JDK internals when compiling in Java 11. + // compile.options.compilerArgs.addAll(['--release', '8']) } } diff --git a/restli-common/src/main/java/com/linkedin/restli/internal/common/URIDecoderUtils.java b/restli-common/src/main/java/com/linkedin/restli/internal/common/URIDecoderUtils.java index 8cd29d63b7..4436e0d9e2 100644 --- a/restli-common/src/main/java/com/linkedin/restli/internal/common/URIDecoderUtils.java +++ b/restli-common/src/main/java/com/linkedin/restli/internal/common/URIDecoderUtils.java @@ -199,6 +199,8 @@ private static ByteBuffer decodeConsecutiveOctets(String s, int start) byte b = decodeOctet(s, i + 1); bb.put(b); } + // Fix java.lang.NoSuchMethodError: java.nio.ByteBuffer.flip()Ljava/nio/ByteBuffer based on the suggestions from + // https://stackoverflow.com/questions/61267495/exception-in-thread-main-java-lang-nosuchmethoderror-java-nio-bytebuffer-flip ((Buffer)bb).flip(); return bb; } diff --git a/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/RestLiDoclet.java b/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/RestLiDoclet.java index 3697b7268e..8a664c63f2 100644 --- a/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/RestLiDoclet.java +++ b/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/RestLiDoclet.java @@ -103,7 +103,7 @@ public static synchronized RestLiDoclet generateDoclet(String programName, // When the DocumentationTool API is used to generate JavaDoc, it attempts to compile the source files and // search for classes generated by Lombok annotations such as builders, getters, and setters. However, these generated // classes are not passed to the DocumentationTool API, resulting in a "cannot find symbol" error. - // Since we don't need to generate JavaDoc for these classes, we can simply ignore the errors per: + // Since we don't need to generate JavaDoc for these classes, we can ignore the errors based on the suggestions from // https://stackoverflow.com/questions/38621202/ignore-minor-errors-using-javadoc taskOptions.add("--ignore-source-errors"); From d4414863097af51bf66d26aca06034f31fd2f398 Mon Sep 17 00:00:00 2001 From: Yan Zhou Date: Thu, 14 Sep 2023 11:03:06 -0700 Subject: [PATCH 11/14] Update the license year and class doc --- .../linkedin/restli/tools/idlgen/DocletDocsProvider.java | 7 +++++-- .../com/linkedin/restli/tools/idlgen/RestLiDoclet.java | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java b/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java index 1d6c69b8fe..fff8ea13fb 100644 --- a/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java +++ b/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java @@ -1,5 +1,5 @@ /* - Copyright (c) 2012 LinkedIn Corp. + Copyright (c) 2023 LinkedIn Corp. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -55,9 +55,12 @@ /** + * This file is using Java 11 APIs to implement the same logic as its Java 8 counterpart located in + * restli-tools/src/main/java/com/linkedin/restli/tools/idlgen/DocletDocsProvider.java + * * Specialized {@link DocsProvider} whose documentation comes from the Javadoc Doclet {@link RestLiDoclet}. * - * @author dellamag + * @author Yan Zhou */ public class DocletDocsProvider implements DocsProvider { private static final Logger log = LoggerFactory.getLogger(DocletDocsProvider.class); diff --git a/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/RestLiDoclet.java b/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/RestLiDoclet.java index 8a664c63f2..ad4fea7eaa 100644 --- a/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/RestLiDoclet.java +++ b/restli-tools/src/main/java11/com/linkedin/restli/tools/idlgen/RestLiDoclet.java @@ -49,6 +49,9 @@ /** + * This file is using Java 11 APIs to implement the same logic as its Java 8 counterpart located in + * restli-tools/src/main/java/com/linkedin/restli/tools/idlgen/RestLiDoclet.java + * * Custom Javadoc processor that merges documentation into the restspec.json. The embedded Javadoc * generator is basically a commandline tool wrapper and it runs in complete isolation from the rest * of the application. Due to the fact that the Javadoc tool instantiates RestLiDoclet, we cannot @@ -62,7 +65,7 @@ * * This class is thread-safe. However, #generateJavadoc() will be synchronized. * - * @author dellamag + * @author Yan Zhou */ public class RestLiDoclet implements Doclet { private static RestLiDoclet _currentDocLet = null; From 9ac744bf23171c04e554a12a9bd8380b8a9fc4ab Mon Sep 17 00:00:00 2001 From: Yan Zhou Date: Tue, 19 Sep 2023 14:51:10 -0700 Subject: [PATCH 12/14] Try to resolve GITHUB stale info issue by using integer rather than a list when specifiying Java version --- .github/workflows/build.yml | 4 ++-- .github/workflows/publish.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 28323131f4..486c59a249 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,8 +10,8 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest] - java: [11] # TODO: Add 11 once build issues are resolved + os: ubuntu-latest + java: 11 name: Java ${{ matrix.java }} on ${{ matrix.os }} steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7b1bd8d73c..95c9395773 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - java: [11] + java: 11 name: Java ${{ matrix.java }} steps: - uses: actions/checkout@v2 From caa22e70ba7d08107bfed1778e4566f41d15f427 Mon Sep 17 00:00:00 2001 From: Yan Zhou Date: Tue, 19 Sep 2023 15:43:53 -0700 Subject: [PATCH 13/14] Try to reset by commenting out build.sh and build.yml --- .github/scripts/build.sh | 168 ++++++++++++++++++------------------ .github/workflows/build.yml | 54 ++++++------ 2 files changed, 111 insertions(+), 111 deletions(-) diff --git a/.github/scripts/build.sh b/.github/scripts/build.sh index c081929b1f..40a00f7c37 100755 --- a/.github/scripts/build.sh +++ b/.github/scripts/build.sh @@ -1,84 +1,84 @@ -#!/usr/bin/env bash - -# Ensure that this is being run in CI by GitHub Actions -if [ "$CI" != "true" ] || [ "$GITHUB_ACTIONS" != "true" ]; then - echo "This script should only be run in CI by GitHub Actions." - exit 2 -fi - -# Ensure that the script is being run from the root project directory -PROPERTIES_FILE='gradle.properties' -if [ ! -f "$PROPERTIES_FILE" ]; then - echo "Could not find $PROPERTIES_FILE, are you sure this is being run from the root project directory?" - echo "PWD: ${PWD}" - exit 1 -fi - -# Determine the current version -VERSION=$(awk 'BEGIN { FS = "=" }; $1 == "version" { print $2 }' $PROPERTIES_FILE | awk '{ print $1 }') -if [ -z "$VERSION" ]; then - echo "Could not read the version from $PROPERTIES_FILE, please fix it and try again." - exit 1 -fi - -# Determine if the version is a release candidate version -RELEASE_CANDIDATE=false -if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then - RELEASE_CANDIDATE=true -fi - -# TODO: Is this needed on GitHub Actions? Travis aborts after 10 minutes of no output, not sure about GA -# while sleep 9m; do echo "[Ping] Keeping Travis job alive ($((SECONDS / 60)) minutes)"; done & -# WAITER_PID=$! - -# For PR builds only... -if [ ! -z "$GITHUB_HEAD_REF" ] && [ ! -z "$GITHUB_BASE_REF" ]; then - # Fetch the PR base ref so it can be used to compute diffs - git fetch origin ${GITHUB_BASE_REF}:${GITHUB_BASE_REF} - # If the project version is being bumped in this PR, assert that the changelog contains an entry for it - if (! $RELEASE_CANDIDATE) && - (git diff ${GITHUB_BASE_REF}...HEAD -- gradle.properties | grep -F "+version=$VERSION" > /dev/null) && - ! ( (cat CHANGELOG.md | grep -F "## [$VERSION] -" > /dev/null) && - (cat CHANGELOG.md | grep -F "[$VERSION]: https" > /dev/null) ); then - echo "This change bumps the project version to $VERSION, but no changelog entry could be found for this version!" - echo 'Please update CHANGELOG.md using the changelog helper script.' - echo 'For more info, run: ./scripts/update-changelog --help' - exit 1 - fi - # Skip module-specific tests if its module dependencies haven't been touched - CONDITIONAL_TESTING_MODULES='d2 r2-int-test restli-int-test' - echo "This is a PR build, so testing will be conditional for these subprojects: [${CONDITIONAL_TESTING_MODULES// /,}]" - # If any Gradle file was touched, run all tests just to be safe - if (git diff ${GITHUB_BASE_REF}...HEAD --name-only | grep '\.gradle' > /dev/null); then - echo "This PR touches a file matching *.gradle, so tests will be run for all subprojects." - else - # Have to prime the comma-separated list with a dummy value because list construction in bash is hard... - EXTRA_ARGS="${EXTRA_ARGS} -Ppegasus.skipTestsForSubprojects=primer" - # For all the following modules (which have lengthy tests), determine if they can be skipped - for MODULE in $CONDITIONAL_TESTING_MODULES; do - echo "Checking test dependencies for subproject $MODULE..." - MODULE_DEPENDENCIES="$(./scripts/get-module-dependencies $MODULE testRuntimeClasspath | tr '\n' ' ')" - # Create regex to capture lines in the diff's paths, e.g. 'a b c' -> '^\(a\|b\|c\)/' - PATH_MATCHING_REGEX="^\\($(echo $MODULE_DEPENDENCIES | sed -z 's/ \+/\\|/g;s/\\|$/\n/g')\\)/" - if [ ! -z "$PATH_MATCHING_REGEX" ] && ! (git diff ${GITHUB_BASE_REF}...HEAD --name-only | grep "$PATH_MATCHING_REGEX" > /dev/null); then - echo "Computed as... [${MODULE_DEPENDENCIES// /,}]" - echo "None of $MODULE's module dependencies have been touched, skipping tests for $MODULE." - EXTRA_ARGS="${EXTRA_ARGS},$MODULE" - else - echo "Some of $MODULE's module dependencies have been touched, tests for $MODULE will remain enabled." - fi - done - fi -fi - -# Run the actual build -./gradlew build $EXTRA_ARGS -EXIT_CODE=$? - -# TODO: Figure out if this can be removed as well for GitHub Actions -# Kill the waiter job -# kill $WAITER_PID - -if [ $EXIT_CODE != 0 ]; then - exit 1 -fi +##!/usr/bin/env bash +# +## Ensure that this is being run in CI by GitHub Actions +#if [ "$CI" != "true" ] || [ "$GITHUB_ACTIONS" != "true" ]; then +# echo "This script should only be run in CI by GitHub Actions." +# exit 2 +#fi +# +## Ensure that the script is being run from the root project directory +#PROPERTIES_FILE='gradle.properties' +#if [ ! -f "$PROPERTIES_FILE" ]; then +# echo "Could not find $PROPERTIES_FILE, are you sure this is being run from the root project directory?" +# echo "PWD: ${PWD}" +# exit 1 +#fi +# +## Determine the current version +#VERSION=$(awk 'BEGIN { FS = "=" }; $1 == "version" { print $2 }' $PROPERTIES_FILE | awk '{ print $1 }') +#if [ -z "$VERSION" ]; then +# echo "Could not read the version from $PROPERTIES_FILE, please fix it and try again." +# exit 1 +#fi +# +## Determine if the version is a release candidate version +#RELEASE_CANDIDATE=false +#if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then +# RELEASE_CANDIDATE=true +#fi +# +## TODO: Is this needed on GitHub Actions? Travis aborts after 10 minutes of no output, not sure about GA +## while sleep 9m; do echo "[Ping] Keeping Travis job alive ($((SECONDS / 60)) minutes)"; done & +## WAITER_PID=$! +# +## For PR builds only... +#if [ ! -z "$GITHUB_HEAD_REF" ] && [ ! -z "$GITHUB_BASE_REF" ]; then +# # Fetch the PR base ref so it can be used to compute diffs +# git fetch origin ${GITHUB_BASE_REF}:${GITHUB_BASE_REF} +# # If the project version is being bumped in this PR, assert that the changelog contains an entry for it +# if (! $RELEASE_CANDIDATE) && +# (git diff ${GITHUB_BASE_REF}...HEAD -- gradle.properties | grep -F "+version=$VERSION" > /dev/null) && +# ! ( (cat CHANGELOG.md | grep -F "## [$VERSION] -" > /dev/null) && +# (cat CHANGELOG.md | grep -F "[$VERSION]: https" > /dev/null) ); then +# echo "This change bumps the project version to $VERSION, but no changelog entry could be found for this version!" +# echo 'Please update CHANGELOG.md using the changelog helper script.' +# echo 'For more info, run: ./scripts/update-changelog --help' +# exit 1 +# fi +# # Skip module-specific tests if its module dependencies haven't been touched +# CONDITIONAL_TESTING_MODULES='d2 r2-int-test restli-int-test' +# echo "This is a PR build, so testing will be conditional for these subprojects: [${CONDITIONAL_TESTING_MODULES// /,}]" +# # If any Gradle file was touched, run all tests just to be safe +# if (git diff ${GITHUB_BASE_REF}...HEAD --name-only | grep '\.gradle' > /dev/null); then +# echo "This PR touches a file matching *.gradle, so tests will be run for all subprojects." +# else +# # Have to prime the comma-separated list with a dummy value because list construction in bash is hard... +# EXTRA_ARGS="${EXTRA_ARGS} -Ppegasus.skipTestsForSubprojects=primer" +# # For all the following modules (which have lengthy tests), determine if they can be skipped +# for MODULE in $CONDITIONAL_TESTING_MODULES; do +# echo "Checking test dependencies for subproject $MODULE..." +# MODULE_DEPENDENCIES="$(./scripts/get-module-dependencies $MODULE testRuntimeClasspath | tr '\n' ' ')" +# # Create regex to capture lines in the diff's paths, e.g. 'a b c' -> '^\(a\|b\|c\)/' +# PATH_MATCHING_REGEX="^\\($(echo $MODULE_DEPENDENCIES | sed -z 's/ \+/\\|/g;s/\\|$/\n/g')\\)/" +# if [ ! -z "$PATH_MATCHING_REGEX" ] && ! (git diff ${GITHUB_BASE_REF}...HEAD --name-only | grep "$PATH_MATCHING_REGEX" > /dev/null); then +# echo "Computed as... [${MODULE_DEPENDENCIES// /,}]" +# echo "None of $MODULE's module dependencies have been touched, skipping tests for $MODULE." +# EXTRA_ARGS="${EXTRA_ARGS},$MODULE" +# else +# echo "Some of $MODULE's module dependencies have been touched, tests for $MODULE will remain enabled." +# fi +# done +# fi +#fi +# +## Run the actual build +#./gradlew build $EXTRA_ARGS +#EXIT_CODE=$? +# +## TODO: Figure out if this can be removed as well for GitHub Actions +## Kill the waiter job +## kill $WAITER_PID +# +#if [ $EXIT_CODE != 0 ]; then +# exit 1 +#fi diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 486c59a249..8553bf197c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,27 +1,27 @@ -name: Build and Test -on: - pull_request: - branches: [master] - push: - branches: [master] -jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: ubuntu-latest - java: 11 - name: Java ${{ matrix.java }} on ${{ matrix.os }} - steps: - - uses: actions/checkout@v2 - with: - # Need to fetch 2 commits for the PR (base commit and head merge commit) so we can compute the diff - fetch-depth: 2 - - uses: actions/setup-java@v2 - with: - distribution: zulu - java-version: ${{ matrix.java }} - cache: gradle - - run: ./.github/scripts/build.sh - +#name: Build and Test +#on: +# pull_request: +# branches: [master] +# push: +# branches: [master] +#jobs: +# build: +# runs-on: ${{ matrix.os }} +# strategy: +# fail-fast: false +# matrix: +# os: ubuntu-latest +# java: 11 +# name: Java ${{ matrix.java }} on ${{ matrix.os }} +# steps: +# - uses: actions/checkout@v2 +# with: +# # Need to fetch 2 commits for the PR (base commit and head merge commit) so we can compute the diff +# fetch-depth: 2 +# - uses: actions/setup-java@v2 +# with: +# distribution: zulu +# java-version: ${{ matrix.java }} +# cache: gradle +# - run: ./.github/scripts/build.sh +# From d437341ba98eefb4e89342b1a1cee9e3f2fb7cae Mon Sep 17 00:00:00 2001 From: Yan Zhou Date: Tue, 19 Sep 2023 17:01:04 -0700 Subject: [PATCH 14/14] Add back Java 8 in build.yml to try to unblock build in github --- .github/scripts/build.sh | 168 ++++++++++++++++++------------------ .github/workflows/build.yml | 53 ++++++------ 2 files changed, 110 insertions(+), 111 deletions(-) diff --git a/.github/scripts/build.sh b/.github/scripts/build.sh index 40a00f7c37..c081929b1f 100755 --- a/.github/scripts/build.sh +++ b/.github/scripts/build.sh @@ -1,84 +1,84 @@ -##!/usr/bin/env bash -# -## Ensure that this is being run in CI by GitHub Actions -#if [ "$CI" != "true" ] || [ "$GITHUB_ACTIONS" != "true" ]; then -# echo "This script should only be run in CI by GitHub Actions." -# exit 2 -#fi -# -## Ensure that the script is being run from the root project directory -#PROPERTIES_FILE='gradle.properties' -#if [ ! -f "$PROPERTIES_FILE" ]; then -# echo "Could not find $PROPERTIES_FILE, are you sure this is being run from the root project directory?" -# echo "PWD: ${PWD}" -# exit 1 -#fi -# -## Determine the current version -#VERSION=$(awk 'BEGIN { FS = "=" }; $1 == "version" { print $2 }' $PROPERTIES_FILE | awk '{ print $1 }') -#if [ -z "$VERSION" ]; then -# echo "Could not read the version from $PROPERTIES_FILE, please fix it and try again." -# exit 1 -#fi -# -## Determine if the version is a release candidate version -#RELEASE_CANDIDATE=false -#if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then -# RELEASE_CANDIDATE=true -#fi -# -## TODO: Is this needed on GitHub Actions? Travis aborts after 10 minutes of no output, not sure about GA -## while sleep 9m; do echo "[Ping] Keeping Travis job alive ($((SECONDS / 60)) minutes)"; done & -## WAITER_PID=$! -# -## For PR builds only... -#if [ ! -z "$GITHUB_HEAD_REF" ] && [ ! -z "$GITHUB_BASE_REF" ]; then -# # Fetch the PR base ref so it can be used to compute diffs -# git fetch origin ${GITHUB_BASE_REF}:${GITHUB_BASE_REF} -# # If the project version is being bumped in this PR, assert that the changelog contains an entry for it -# if (! $RELEASE_CANDIDATE) && -# (git diff ${GITHUB_BASE_REF}...HEAD -- gradle.properties | grep -F "+version=$VERSION" > /dev/null) && -# ! ( (cat CHANGELOG.md | grep -F "## [$VERSION] -" > /dev/null) && -# (cat CHANGELOG.md | grep -F "[$VERSION]: https" > /dev/null) ); then -# echo "This change bumps the project version to $VERSION, but no changelog entry could be found for this version!" -# echo 'Please update CHANGELOG.md using the changelog helper script.' -# echo 'For more info, run: ./scripts/update-changelog --help' -# exit 1 -# fi -# # Skip module-specific tests if its module dependencies haven't been touched -# CONDITIONAL_TESTING_MODULES='d2 r2-int-test restli-int-test' -# echo "This is a PR build, so testing will be conditional for these subprojects: [${CONDITIONAL_TESTING_MODULES// /,}]" -# # If any Gradle file was touched, run all tests just to be safe -# if (git diff ${GITHUB_BASE_REF}...HEAD --name-only | grep '\.gradle' > /dev/null); then -# echo "This PR touches a file matching *.gradle, so tests will be run for all subprojects." -# else -# # Have to prime the comma-separated list with a dummy value because list construction in bash is hard... -# EXTRA_ARGS="${EXTRA_ARGS} -Ppegasus.skipTestsForSubprojects=primer" -# # For all the following modules (which have lengthy tests), determine if they can be skipped -# for MODULE in $CONDITIONAL_TESTING_MODULES; do -# echo "Checking test dependencies for subproject $MODULE..." -# MODULE_DEPENDENCIES="$(./scripts/get-module-dependencies $MODULE testRuntimeClasspath | tr '\n' ' ')" -# # Create regex to capture lines in the diff's paths, e.g. 'a b c' -> '^\(a\|b\|c\)/' -# PATH_MATCHING_REGEX="^\\($(echo $MODULE_DEPENDENCIES | sed -z 's/ \+/\\|/g;s/\\|$/\n/g')\\)/" -# if [ ! -z "$PATH_MATCHING_REGEX" ] && ! (git diff ${GITHUB_BASE_REF}...HEAD --name-only | grep "$PATH_MATCHING_REGEX" > /dev/null); then -# echo "Computed as... [${MODULE_DEPENDENCIES// /,}]" -# echo "None of $MODULE's module dependencies have been touched, skipping tests for $MODULE." -# EXTRA_ARGS="${EXTRA_ARGS},$MODULE" -# else -# echo "Some of $MODULE's module dependencies have been touched, tests for $MODULE will remain enabled." -# fi -# done -# fi -#fi -# -## Run the actual build -#./gradlew build $EXTRA_ARGS -#EXIT_CODE=$? -# -## TODO: Figure out if this can be removed as well for GitHub Actions -## Kill the waiter job -## kill $WAITER_PID -# -#if [ $EXIT_CODE != 0 ]; then -# exit 1 -#fi +#!/usr/bin/env bash + +# Ensure that this is being run in CI by GitHub Actions +if [ "$CI" != "true" ] || [ "$GITHUB_ACTIONS" != "true" ]; then + echo "This script should only be run in CI by GitHub Actions." + exit 2 +fi + +# Ensure that the script is being run from the root project directory +PROPERTIES_FILE='gradle.properties' +if [ ! -f "$PROPERTIES_FILE" ]; then + echo "Could not find $PROPERTIES_FILE, are you sure this is being run from the root project directory?" + echo "PWD: ${PWD}" + exit 1 +fi + +# Determine the current version +VERSION=$(awk 'BEGIN { FS = "=" }; $1 == "version" { print $2 }' $PROPERTIES_FILE | awk '{ print $1 }') +if [ -z "$VERSION" ]; then + echo "Could not read the version from $PROPERTIES_FILE, please fix it and try again." + exit 1 +fi + +# Determine if the version is a release candidate version +RELEASE_CANDIDATE=false +if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then + RELEASE_CANDIDATE=true +fi + +# TODO: Is this needed on GitHub Actions? Travis aborts after 10 minutes of no output, not sure about GA +# while sleep 9m; do echo "[Ping] Keeping Travis job alive ($((SECONDS / 60)) minutes)"; done & +# WAITER_PID=$! + +# For PR builds only... +if [ ! -z "$GITHUB_HEAD_REF" ] && [ ! -z "$GITHUB_BASE_REF" ]; then + # Fetch the PR base ref so it can be used to compute diffs + git fetch origin ${GITHUB_BASE_REF}:${GITHUB_BASE_REF} + # If the project version is being bumped in this PR, assert that the changelog contains an entry for it + if (! $RELEASE_CANDIDATE) && + (git diff ${GITHUB_BASE_REF}...HEAD -- gradle.properties | grep -F "+version=$VERSION" > /dev/null) && + ! ( (cat CHANGELOG.md | grep -F "## [$VERSION] -" > /dev/null) && + (cat CHANGELOG.md | grep -F "[$VERSION]: https" > /dev/null) ); then + echo "This change bumps the project version to $VERSION, but no changelog entry could be found for this version!" + echo 'Please update CHANGELOG.md using the changelog helper script.' + echo 'For more info, run: ./scripts/update-changelog --help' + exit 1 + fi + # Skip module-specific tests if its module dependencies haven't been touched + CONDITIONAL_TESTING_MODULES='d2 r2-int-test restli-int-test' + echo "This is a PR build, so testing will be conditional for these subprojects: [${CONDITIONAL_TESTING_MODULES// /,}]" + # If any Gradle file was touched, run all tests just to be safe + if (git diff ${GITHUB_BASE_REF}...HEAD --name-only | grep '\.gradle' > /dev/null); then + echo "This PR touches a file matching *.gradle, so tests will be run for all subprojects." + else + # Have to prime the comma-separated list with a dummy value because list construction in bash is hard... + EXTRA_ARGS="${EXTRA_ARGS} -Ppegasus.skipTestsForSubprojects=primer" + # For all the following modules (which have lengthy tests), determine if they can be skipped + for MODULE in $CONDITIONAL_TESTING_MODULES; do + echo "Checking test dependencies for subproject $MODULE..." + MODULE_DEPENDENCIES="$(./scripts/get-module-dependencies $MODULE testRuntimeClasspath | tr '\n' ' ')" + # Create regex to capture lines in the diff's paths, e.g. 'a b c' -> '^\(a\|b\|c\)/' + PATH_MATCHING_REGEX="^\\($(echo $MODULE_DEPENDENCIES | sed -z 's/ \+/\\|/g;s/\\|$/\n/g')\\)/" + if [ ! -z "$PATH_MATCHING_REGEX" ] && ! (git diff ${GITHUB_BASE_REF}...HEAD --name-only | grep "$PATH_MATCHING_REGEX" > /dev/null); then + echo "Computed as... [${MODULE_DEPENDENCIES// /,}]" + echo "None of $MODULE's module dependencies have been touched, skipping tests for $MODULE." + EXTRA_ARGS="${EXTRA_ARGS},$MODULE" + else + echo "Some of $MODULE's module dependencies have been touched, tests for $MODULE will remain enabled." + fi + done + fi +fi + +# Run the actual build +./gradlew build $EXTRA_ARGS +EXIT_CODE=$? + +# TODO: Figure out if this can be removed as well for GitHub Actions +# Kill the waiter job +# kill $WAITER_PID + +if [ $EXIT_CODE != 0 ]; then + exit 1 +fi diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8553bf197c..711d172cd1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,27 +1,26 @@ -#name: Build and Test -#on: -# pull_request: -# branches: [master] -# push: -# branches: [master] -#jobs: -# build: -# runs-on: ${{ matrix.os }} -# strategy: -# fail-fast: false -# matrix: -# os: ubuntu-latest -# java: 11 -# name: Java ${{ matrix.java }} on ${{ matrix.os }} -# steps: -# - uses: actions/checkout@v2 -# with: -# # Need to fetch 2 commits for the PR (base commit and head merge commit) so we can compute the diff -# fetch-depth: 2 -# - uses: actions/setup-java@v2 -# with: -# distribution: zulu -# java-version: ${{ matrix.java }} -# cache: gradle -# - run: ./.github/scripts/build.sh -# +name: Build and Test +on: + pull_request: + branches: [master] + push: + branches: [master] +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + java: [8, 11] # TODO: Add 11 once build issues are resolved + name: Java ${{ matrix.java }} on ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + with: + # Need to fetch 2 commits for the PR (base commit and head merge commit) so we can compute the diff + fetch-depth: 2 + - uses: actions/setup-java@v2 + with: + distribution: zulu + java-version: ${{ matrix.java }} + cache: gradle + - run: ./.github/scripts/build.sh \ No newline at end of file