diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4bd8c5e68..55593c973 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -51,6 +51,7 @@ jobs: Copy-Item secring.gpg components/serialization/json/ -Verbose Copy-Item secring.gpg components/serialization/multipart/ -Verbose Copy-Item secring.gpg components/http/okHttp/ -Verbose + Copy-Item secring.gpg components/bundle/ -Verbose shell: pwsh working-directory: ./ - name: Build with Gradle @@ -96,6 +97,7 @@ jobs: Copy-Item secring.gpg components/serialization/json/ -Verbose Copy-Item secring.gpg components/serialization/multipart/ -Verbose Copy-Item secring.gpg components/http/okHttp/ -Verbose + Copy-Item secring.gpg components/bundle/ -Verbose shell: pwsh working-directory: ./ - name: Build with Gradle @@ -122,6 +124,9 @@ jobs: - name: Publish Release okHttp run: ./gradlew --no-daemon :components:http:okHttp:$PUBLISH_TASK -PmavenCentralSnapshotArtifactSuffix="" working-directory: ./ + - name: Publish Release bundle + run: ./gradlew --no-daemon :components:bundle:$PUBLISH_TASK -PmavenCentralSnapshotArtifactSuffix="" + working-directory: ./ - name: Release uses: anton-yurchenko/git-release@v6.0 env: diff --git a/CHANGELOG.md b/CHANGELOG.md index be7bfce6d..02d6a85bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.2.0] - 2024-07-25 + +### Changed + +- Adds bundle package for Kiota [#1420](https://github.com/microsoft/kiota-java/issues/1420). + ## [1.1.14] - 2024-06-10 ### Changed diff --git a/components/bundle/.gitignore b/components/bundle/.gitignore new file mode 100644 index 000000000..20a548b24 --- /dev/null +++ b/components/bundle/.gitignore @@ -0,0 +1,10 @@ +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build + + +.settings +.project +.classpath \ No newline at end of file diff --git a/components/bundle/android/.gitignore b/components/bundle/android/.gitignore new file mode 100644 index 000000000..f06dfad69 --- /dev/null +++ b/components/bundle/android/.gitignore @@ -0,0 +1,2 @@ +.gradle +build \ No newline at end of file diff --git a/components/bundle/android/AndroidManifest.xml b/components/bundle/android/AndroidManifest.xml new file mode 100644 index 000000000..2932f8879 --- /dev/null +++ b/components/bundle/android/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + diff --git a/components/bundle/android/build.gradle b/components/bundle/android/build.gradle new file mode 100644 index 000000000..e4135d1b9 --- /dev/null +++ b/components/bundle/android/build.gradle @@ -0,0 +1,73 @@ +buildscript { + repositories { + google() + gradlePluginPortal() + maven { + url "https://plugins.gradle.org/m2/" + } + maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } + } + + dependencies { + classpath "com.gradle:gradle-enterprise-gradle-plugin:3.17.5" + classpath "com.android.tools.build:gradle:8.5.1" + classpath "com.github.ben-manes:gradle-versions-plugin:0.51.0" + } +} + +repositories { + google() + gradlePluginPortal() +} + +apply plugin: "com.android.library" +apply plugin: "com.github.ben-manes.versions" + +android { + namespace "com.microsoft.kiota.bundle" + compileSdkVersion 35 + + defaultConfig { + versionCode 1 + versionName "1.0" + minSdkVersion 26 + targetSdkVersion 35 + } + + buildTypes { + release { + minifyEnabled false + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + lintOptions { + textOutput "stdout" + checkAllWarnings true + warningsAsErrors true + disable "UnusedResources" // Unused will be removed on release + disable "IconExpectedSize" // Using the material icons provided from Google + disable "GoogleAppIndexingApiWarning" // We might want to index our app later + disable "InvalidPackage" // Butterknife, Okio and Realm + disable "ResourceType" // Annotation binding + disable "GradleDependency" + disable "NewerVersionAvailable" + disable "DuplicatePlatformClasses" // xpp3 added by azure-identity + } + sourceSets { + main { + java.srcDirs = ['../src/main/java'] + res.srcDirs = ['../src/main/java'] + manifest.srcFile 'AndroidManifest.xml' + } + androidTest { + setRoot '../src/test' + } + } +} + +apply from: "../gradle/dependencies.gradle" diff --git a/components/bundle/android/gradle.properties b/components/bundle/android/gradle.properties new file mode 100644 index 000000000..3b7dc4668 --- /dev/null +++ b/components/bundle/android/gradle.properties @@ -0,0 +1 @@ +mavenArtifactId = microsoft-kiota-bundle diff --git a/components/bundle/build.gradle b/components/bundle/build.gradle new file mode 100644 index 000000000..a08c0788c --- /dev/null +++ b/components/bundle/build.gradle @@ -0,0 +1,227 @@ +plugins { + // Apply the java-library plugin to add support for Java Library + id 'java-library' + id 'eclipse' + id 'maven-publish' + id 'signing' + id 'jacoco' + id 'com.github.spotbugs' + id 'com.diffplug.spotless' +} + +java { + modularity.inferModulePath = true + withSourcesJar() + withJavadocJar() +} + +test { + useJUnitPlatform() + finalizedBy jacocoTestReport // report is always generated after tests run +} + +jacocoTestReport { + dependsOn test // tests are required to run before generating the report + reports.xml.required = true +} + +jacoco { + toolVersion = "0.8.11" +} + +spotbugsMain { + excludeFilter = file("spotBugsExcludeFilter.xml") + reports { + html { + required + outputLocation = file("$buildDir/reports/spotbugs/main/spotbugs.html") + stylesheet = 'fancy-hist.xsl' + } + } +} + +spotbugsTest { + excludeFilter = file("spotBugsExcludeFilter.xml") + reports { + html { + required + outputLocation = file("$buildDir/reports/spotbugs/test/spotbugs.html") + stylesheet = 'fancy-hist.xsl' + } + } +} + +sourceSets { + main { + java { + exclude 'pom.xml' + } + } +} + +// In this section you declare where to find the dependencies of your project +repositories { + // You can declare any Maven/Ivy/file repository here. + mavenCentral() +} + +apply from: "gradle/dependencies.gradle" + +def pomConfig = { + licenses { + license([:]) { + name "MIT License" + url "http://opensource.org/licenses/MIT" + distribution "repo" + } + } +} + +//Publishing tasks- +//Maven Central Snapshot: publishMavenPublicationToMavenRepository +//Maven Central Release: publishmavenPublicationToMaven2Repository + +tasks.jar { + manifest { + attributes('Automatic-Module-Name': project.property('mavenGroupId')) + } +} + +publishing { + + publications { + maven(MavenPublication) { + customizePom(pom) + groupId project.property('mavenGroupId') + artifactId project.property('mavenArtifactId') + version "${mavenMajorVersion}.${mavenMinorVersion}.${mavenPatchVersion}${mavenCentralSnapshotArtifactSuffix}" + from components.java + pom.withXml { + def pomFile = file("${project.buildDir}/generated-pom.xml") + writeTo(pomFile) + } + } + } + repositories { + maven { + url = 'https://oss.sonatype.org/content/repositories/snapshots' + name = 'sonatypeSnapshot' + + credentials { + if (project.rootProject.file('local.properties').exists()) { + Properties properties = new Properties() + properties.load(project.rootProject.file('local.properties').newDataInputStream()) + username = properties.getProperty('sonatypeUsername') + password = properties.getProperty('sonatypePassword') + } + } + } + + maven { + url = 'https://oss.sonatype.org/service/local/staging/deploy/maven2' + name = 'sonatype' + + credentials { + if (project.rootProject.file('local.properties').exists()) { + Properties properties = new Properties() + properties.load(project.rootProject.file('local.properties').newDataInputStream()) + username = properties.getProperty('sonatypeUsername') + password = properties.getProperty('sonatypePassword') + } + } + } + } +} + +signing { + sign publishing.publications.maven +} +tasks.withType(Sign)*.enabled = mavenCentralPublishingEnabled.toBoolean() + +def fixAscNames = { name -> + if(name.contains('pom')) { + "${project.property('mavenArtifactId')}-${mavenMajorVersion}.${mavenMinorVersion}.${mavenPatchVersion}.pom.asc" + } else { + name.replace('microsoft-kiota-bundle', "${project.property('mavenArtifactId')}-${mavenMajorVersion}.${mavenMinorVersion}.${mavenPatchVersion}") + } +} + +compileJava { + options.compilerArgs << "-parameters" + sourceCompatibility = '1.8' + targetCompatibility = '1.8' +} + +def getVersionCode() { + return mavenMajorVersion.toInteger() * 10000 + mavenMinorVersion.toInteger() * 100 + mavenPatchVersion.toInteger() +} + +def getVersionName() { + return "${mavenMajorVersion}.${mavenMinorVersion}.${mavenPatchVersion}${mavenArtifactSuffix}" +} + +artifacts { + archives jar +} + +def customizePom(pom) { + pom.withXml { + def root = asNode() + + root.dependencies.removeAll { dep -> + dep.scope == "test" + } + + root.children().last() + { + resolveStrategy = Closure.DELEGATE_FIRST + + description 'Microsoft Kiota-Bundle' + name 'Microsoft Kiota-Java Bundle' + url 'https://github.com/microsoft/kiota-java' + organization { + name 'Microsoft' + url 'https://github.com/microsoft/kiota-java' + } + issueManagement { + system 'GitHub' + url 'https://github.com/microsoft/kiota-java/issues' + } + licenses { + license { + name "MIT License" + url "http://opensource.org/licenses/MIT" + distribution "repo" + } + } + scm { + url 'https://github.com/microsoft/kiota-java' + connection 'scm:git:git://github.com/microsoft/kiota-java.git' + developerConnection 'scm:git:ssh://git@github.com:microsoft/kiota-java.git' + } + developers { + developer { + name 'Microsoft' + } + } + } + } +} + +gradle.taskGraph.whenReady { taskGraph -> + if (project.rootProject.file('local.properties').exists()) { + Properties properties = new Properties() + properties.load(project.rootProject.file('local.properties').newDataInputStream()) + tasks.withType(Sign)*.enabled = (properties.containsKey('enableSigning')) ? properties.getProperty('enableSigning').toBoolean() : false + allprojects { ext."signing.keyId" = properties.getProperty('signing.keyId') } + allprojects { ext."signing.secretKeyRingFile" = properties.getProperty('signing.secretKeyRingFile') } + allprojects { ext."signing.password" = properties.getProperty('signing.password') } + } +} + +model { + tasks.generatePomFileForMavenPublication { + destination = file("${project.buildDir}/generated-pom.xml") + } +} + +apply from: file('../../spotless.groovy') diff --git a/components/bundle/gradle.properties b/components/bundle/gradle.properties new file mode 100644 index 000000000..3b7dc4668 --- /dev/null +++ b/components/bundle/gradle.properties @@ -0,0 +1 @@ +mavenArtifactId = microsoft-kiota-bundle diff --git a/components/bundle/gradle/dependencies.gradle b/components/bundle/gradle/dependencies.gradle new file mode 100644 index 000000000..cf254bc4b --- /dev/null +++ b/components/bundle/gradle/dependencies.gradle @@ -0,0 +1,18 @@ +dependencies { + // Use JUnit Jupiter API for testing. + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.3' + testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.3' + + // Use JUnit Jupiter Engine for testing. + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.mockito:mockito-inline:5.2.0' + + implementation 'jakarta.annotation:jakarta.annotation-api:2.1.1' + + api project(':components:abstractions') + api project(':components:http:okHttp') + implementation project(':components:serialization:json') + implementation project(':components:serialization:text') + implementation project(':components:serialization:multipart') + implementation project(':components:serialization:form') +} diff --git a/components/bundle/java-8/.gitignore b/components/bundle/java-8/.gitignore new file mode 100644 index 000000000..7f6823bcc --- /dev/null +++ b/components/bundle/java-8/.gitignore @@ -0,0 +1,2 @@ +.gradle +build/ diff --git a/components/bundle/java-8/build.gradle b/components/bundle/java-8/build.gradle new file mode 100644 index 000000000..6ad52d9f9 --- /dev/null +++ b/components/bundle/java-8/build.gradle @@ -0,0 +1,28 @@ +plugins { + // Apply the java-library plugin to add support for Java Library + id 'java-library' + id 'eclipse' +} + +repositories { + mavenCentral() +} + +sourceSets { + main { + java { + srcDirs = ['../src'] + exclude 'test/**' + } + } +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } + withSourcesJar() + withJavadocJar() +} + +apply from: "../gradle/dependencies.gradle" diff --git a/components/bundle/java-8/gradle.properties b/components/bundle/java-8/gradle.properties new file mode 100644 index 000000000..3b7dc4668 --- /dev/null +++ b/components/bundle/java-8/gradle.properties @@ -0,0 +1 @@ +mavenArtifactId = microsoft-kiota-bundle diff --git a/components/bundle/spotBugsExcludeFilter.xml b/components/bundle/spotBugsExcludeFilter.xml new file mode 100644 index 000000000..ebca482dc --- /dev/null +++ b/components/bundle/spotBugsExcludeFilter.xml @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/components/bundle/src/main/java/com/microsoft/kiota/bundle/DefaultRequestAdapter.java b/components/bundle/src/main/java/com/microsoft/kiota/bundle/DefaultRequestAdapter.java new file mode 100644 index 000000000..45fe5bd74 --- /dev/null +++ b/components/bundle/src/main/java/com/microsoft/kiota/bundle/DefaultRequestAdapter.java @@ -0,0 +1,100 @@ +package com.microsoft.kiota.bundle; + +import com.microsoft.kiota.ApiClientBuilder; +import com.microsoft.kiota.authentication.AuthenticationProvider; +import com.microsoft.kiota.http.ObservabilityOptions; +import com.microsoft.kiota.http.OkHttpRequestAdapter; +import com.microsoft.kiota.serialization.*; + +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; + +import okhttp3.Call; + +/** RequestAdapter implementation for Kiota Bundle */ +public class DefaultRequestAdapter extends OkHttpRequestAdapter { + + /** + * Instantiates a DefaultRequestAdapter with the provided authentication provider. + * @param authenticationProvider the authentication provider to use for authenticating requests. + */ + public DefaultRequestAdapter(@Nonnull final AuthenticationProvider authenticationProvider) { + this(authenticationProvider, null); + } + + /** + * Instantiates a new DefaultRequestAdapter with the provided authentication provider, and the parse node factory. + * @param authenticationProvider the authentication provider to use for authenticating requests. + * @param parseNodeFactory the parse node factory to use for parsing responses. + */ + @SuppressWarnings("LambdaLast") + public DefaultRequestAdapter( + @Nonnull final AuthenticationProvider authenticationProvider, + @Nullable final ParseNodeFactory parseNodeFactory) { + this(authenticationProvider, parseNodeFactory, null); + } + + /** + * Instantiates a new DefaultRequestAdapter with the provided authentication provider, parse node factory, and the serialization writer factory. + * @param authenticationProvider the authentication provider to use for authenticating requests. + * @param parseNodeFactory the parse node factory to use for parsing responses. + * @param serializationWriterFactory the serialization writer factory to use for serializing requests. + */ + @SuppressWarnings("LambdaLast") + public DefaultRequestAdapter( + @Nonnull final AuthenticationProvider authenticationProvider, + @Nullable final ParseNodeFactory parseNodeFactory, + @Nullable final SerializationWriterFactory serializationWriterFactory) { + this(authenticationProvider, parseNodeFactory, serializationWriterFactory, null); + } + + /** + * Instantiates a new DefaultRequestAdapter with the provided authentication provider, parse node factory, serialization writer factory, and the http client. + * @param authenticationProvider the authentication provider to use for authenticating requests. + * @param parseNodeFactory the parse node factory to use for parsing responses. + * @param serializationWriterFactory the serialization writer factory to use for serializing requests. + * @param client the http client to use for sending requests. + */ + @SuppressWarnings("LambdaLast") + public DefaultRequestAdapter( + @Nonnull final AuthenticationProvider authenticationProvider, + @Nullable final ParseNodeFactory parseNodeFactory, + @Nullable final SerializationWriterFactory serializationWriterFactory, + @Nullable final Call.Factory client) { + this(authenticationProvider, parseNodeFactory, serializationWriterFactory, client, null); + } + + /** + * Instantiates a new DefaultRequestAdapter with the provided authentication provider, parse node factory, serialization writer factory, http client and observability options. + * @param authenticationProvider the authentication provider to use for authenticating requests. + * @param parseNodeFactory the parse node factory to use for parsing responses. + * @param serializationWriterFactory the serialization writer factory to use for serializing requests. + * @param client the http client to use for sending requests. + * @param observabilityOptions the observability options to use for sending requests. + */ + @SuppressWarnings("LambdaLast") + public DefaultRequestAdapter( + @Nonnull final AuthenticationProvider authenticationProvider, + @Nullable final ParseNodeFactory parseNodeFactory, + @Nullable final SerializationWriterFactory serializationWriterFactory, + @Nullable final Call.Factory client, + @Nullable final ObservabilityOptions observabilityOptions) { + super( + authenticationProvider, + parseNodeFactory, + serializationWriterFactory, + client, + observabilityOptions); + setupDefaults(); + } + + private void setupDefaults() { + ApiClientBuilder.registerDefaultSerializer(JsonSerializationWriterFactory::new); + ApiClientBuilder.registerDefaultSerializer(TextSerializationWriterFactory::new); + ApiClientBuilder.registerDefaultSerializer(FormSerializationWriterFactory::new); + ApiClientBuilder.registerDefaultSerializer(MultipartSerializationWriterFactory::new); + ApiClientBuilder.registerDefaultDeserializer(JsonParseNodeFactory::new); + ApiClientBuilder.registerDefaultDeserializer(FormParseNodeFactory::new); + ApiClientBuilder.registerDefaultDeserializer(TextParseNodeFactory::new); + } +} diff --git a/components/bundle/src/test/java/com/microsoft/kiota/bundle/BundleTests.java b/components/bundle/src/test/java/com/microsoft/kiota/bundle/BundleTests.java new file mode 100644 index 000000000..529dd0151 --- /dev/null +++ b/components/bundle/src/test/java/com/microsoft/kiota/bundle/BundleTests.java @@ -0,0 +1,64 @@ +package com.microsoft.kiota.bundle; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +import com.microsoft.kiota.authentication.AuthenticationProvider; +import com.microsoft.kiota.serialization.ParseNodeFactoryRegistry; +import com.microsoft.kiota.serialization.SerializationWriterFactoryRegistry; + +import org.junit.jupiter.api.Test; + +class BundleTests { + @Test + void throwsErrorNullAuthenticationProvider() throws Exception { + var exception = + assertThrows(NullPointerException.class, () -> new DefaultRequestAdapter(null)); + assertEquals("parameter authenticationProvider cannot be null", exception.getMessage()); + } + + @Test + void serializersAreRegisteredAsExpected() throws Exception { + final var authenticationProviderMock = mock(AuthenticationProvider.class); + var defaultRequestAdapter = new DefaultRequestAdapter(authenticationProviderMock); + assertEquals("", defaultRequestAdapter.getBaseUrl()); + + // validate + var serializerCount = + SerializationWriterFactoryRegistry.defaultInstance.contentTypeAssociatedFactories + .size(); + var deserializerCount = + ParseNodeFactoryRegistry.defaultInstance.contentTypeAssociatedFactories.size(); + + assertEquals(4, serializerCount); // four serializers present + assertEquals(3, deserializerCount); // three deserializers present + + var serializerKeys = + SerializationWriterFactoryRegistry.defaultInstance.contentTypeAssociatedFactories + .keySet(); + var deserializerKeys = + ParseNodeFactoryRegistry.defaultInstance.contentTypeAssociatedFactories.keySet(); + + assertTrue(serializerKeys.contains("application/json")); + assertTrue( + deserializerKeys.contains( + "application/json")); // Serializer and deserializer present for + // application/json + + assertTrue(serializerKeys.contains("text/plain")); + assertTrue( + deserializerKeys.contains( + "text/plain")); // Serializer and deserializer present for text/plain + + assertTrue(serializerKeys.contains("application/x-www-form-urlencoded")); + assertTrue( + deserializerKeys.contains( + "application/x-www-form-urlencoded")); // Serializer and deserializer + // present for + // application/x-www-form-urlencoded + + assertTrue( + serializerKeys.contains( + "multipart/form-data")); // Serializer present for multipart/form-data + } +} diff --git a/gradle.properties b/gradle.properties index 0ea37183a..b09f5e443 100644 --- a/gradle.properties +++ b/gradle.properties @@ -25,8 +25,8 @@ org.gradle.caching=true mavenGroupId = com.microsoft.kiota mavenMajorVersion = 1 -mavenMinorVersion = 1 -mavenPatchVersion = 14 +mavenMinorVersion = 2 +mavenPatchVersion = 0 mavenArtifactSuffix = #These values are used to run functional tests diff --git a/settings.gradle b/settings.gradle index 64c232f1d..5d8ce78ad 100644 --- a/settings.gradle +++ b/settings.gradle @@ -18,3 +18,5 @@ include ':components:serialization:text' + suffix include ':components:serialization:multipart' + suffix include ':components:authentication:azure' + suffix include ':components:http:okHttp' + suffix +include ':components:bundle' + suffix +