diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f1d88685..be1672993 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +## [0.4.7] - 2023-07-21 + +### Added + +- Adds the `UrlReplaceHandler` middleware to the Okhttp component to allow for customizing the URL before sending the request. + ## [0.4.6] - 2023-07-20 ### Added diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/UrlReplaceHandler.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/UrlReplaceHandler.java new file mode 100644 index 000000000..5136ccb9f --- /dev/null +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/UrlReplaceHandler.java @@ -0,0 +1,105 @@ +package com.microsoft.kiota.http.middleware; + +import com.microsoft.kiota.http.middleware.options.UrlReplaceHandlerOption; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Scope; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.Map; +import java.util.Objects; + +/** + * A middleware to replace the url with the specified replacement pairs. + */ +public class UrlReplaceHandler implements Interceptor { + + private UrlReplaceHandlerOption mUrlReplaceHandlerOption; + + /** + * Instantiate a UrlReplaceHandler with default UrlReplaceHandlerOption. + */ + public UrlReplaceHandler(){ + this(new UrlReplaceHandlerOption()); + } + /** + * Instantiate a UrlReplaceHandler with specified UrlReplaceHandlerOption + * @param urlReplaceHandlerOption the specified UrlReplaceHandlerOption for the UrlReplaceHandler. + */ + public UrlReplaceHandler(@Nonnull UrlReplaceHandlerOption urlReplaceHandlerOption){ + Objects.requireNonNull(urlReplaceHandlerOption); + this.mUrlReplaceHandlerOption = new UrlReplaceHandlerOption(urlReplaceHandlerOption.getReplacementPairs(), urlReplaceHandlerOption.isEnabled()); + } + /** {@inheritDoc} */ + @Nonnull + @Override + public Response intercept(@Nonnull Chain chain) throws IOException { + Objects.requireNonNull(chain, "parameter chain cannot be null"); + Request request = Objects.requireNonNull(chain.request(), "request cannot be null"); + UrlReplaceHandlerOption replaceOption = request.tag(UrlReplaceHandlerOption.class); + replaceOption = replaceOption == null ? mUrlReplaceHandlerOption : replaceOption; + if(!replaceOption.isEnabled() || replaceOption.getReplacementPairs().isEmpty()) { + return chain.proceed(request); + } + + final Span span = ObservabilityHelper.getSpanForRequest(request, "UrlReplaceHandler_Intercept"); + Scope scope = null; + if (span != null) { + scope = span.makeCurrent(); + span.setAttribute("com.microsoft.kiota.handler.urlreplace.enable", true); + } + try{ + request = replaceRequestUrl(request, replaceOption.getReplacementPairs()); + } finally { + if (scope != null) { + scope.close(); + } + if (span != null) { + span.end(); + } + } + return chain.proceed(request); + } + /** + * Gets the UrlReplaceHandlerOption for the UrlReplaceHandler. + * @return the UrlReplaceHandlerOption for the UrlReplaceHandler. + */ + @Nonnull + public UrlReplaceHandlerOption getUrlReplaceHandlerOption() { + return new UrlReplaceHandlerOption(mUrlReplaceHandlerOption.getReplacementPairs(), mUrlReplaceHandlerOption.isEnabled()); + } + /** + * Sets the UrlReplaceHandlerOption for the UrlReplaceHandler. + * @param urlReplaceHandlerOption the UrlReplaceHandlerOption to set. + */ + public void setUrlReplaceHandlerOption(@Nonnull UrlReplaceHandlerOption urlReplaceHandlerOption) { + this.mUrlReplaceHandlerOption = new UrlReplaceHandlerOption(urlReplaceHandlerOption.getReplacementPairs(), urlReplaceHandlerOption.isEnabled()); + } + /** + * Replaces the url of the request using the replacement pairs provided. + * @param request the request to replace the url of. + * @param replacementPairs the replacement pairs to use. + * @return the request with the updated url. + */ + @Nonnull + public static Request replaceRequestUrl(@Nonnull Request request, @Nonnull Map replacementPairs) { + Request.Builder builder = request.newBuilder(); + try { + //Decoding the url since Request.url is encoded by default. + String replacedUrl = URLDecoder.decode(request.url().toString(), "UTF-8");//Using decode(String,String) method to maintain source compatibility with Java 8 + for (Map.Entry entry : replacementPairs.entrySet()) { + replacedUrl = replacedUrl.replace(entry.getKey(), entry.getValue()); + } + builder.url(replacedUrl); + return builder.build(); + + } catch (UnsupportedEncodingException e) { + return request; //This should never happen since "UTF-8" is a supported encoding. + } + } +} diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/options/UrlReplaceHandlerOption.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/options/UrlReplaceHandlerOption.java new file mode 100644 index 000000000..0dc53b85c --- /dev/null +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/options/UrlReplaceHandlerOption.java @@ -0,0 +1,82 @@ +package com.microsoft.kiota.http.middleware.options; + +import com.microsoft.kiota.RequestOption; + +import javax.annotation.Nonnull; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * The options to be passed to the UrlReplaceHandler. + * Defines the replacement pairs and whether the handler is enabled or not. + */ +public class UrlReplaceHandlerOption implements RequestOption { + + private Map replacementPairs; + private boolean enabled; + + /** + * Instantiates a new UrlReplaceOption with an empty replacementPairs map and enabled set to true. + */ + public UrlReplaceHandlerOption() { + this(new HashMap<>()); + } + /** + * Instantiates a new UrlReplaceOption with the specified replacementPairs map and enabled set to true. + * @param replacementPairs the replacement pairs map. + */ + public UrlReplaceHandlerOption(@Nonnull Map replacementPairs) { + this(replacementPairs, true); + } + /** + * Instantiates a new UrlReplaceOption with the specified replacementPairs map and enabled set to the specified value. + * @param enabled whether the handler is enabled or not. + * @param replacementPairs the replacement pairs map. + */ + public UrlReplaceHandlerOption(@Nonnull Map replacementPairs, boolean enabled) { + Objects.requireNonNull(replacementPairs); + this.replacementPairs = new HashMap<>(replacementPairs); + this.enabled = enabled; + } + /** + * Gets the replacement pairs map. + * @return the replacement pairs map. + */ + @Nonnull + public Map getReplacementPairs() { + return new HashMap<>(replacementPairs); + } + /** + * Sets the replacement pairs map. + * @param replacementPairs the replacement pairs map. + */ + public void setReplacementPairs(@Nonnull final Map replacementPairs) { + this.replacementPairs = new HashMap<>(replacementPairs); + } + /** + * Enables the handler. + */ + public void enable() { + this.enabled = true; + } + /** + * Disables the handler. + */ + public void disable() { + this.enabled = false; + } + /** + * Gets whether the handler is enabled or not. + * @return whether the handler is enabled or not. + */ + public boolean isEnabled() { + return enabled; + } + @Nonnull + @SuppressWarnings("unchecked") + @Override + public Class getType() { + return (Class) UrlReplaceHandlerOption.class; + } +} diff --git a/components/http/okHttp/src/test/java/com/microsoft/kiota/http/UrlReplaceHandlerTest.java b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/UrlReplaceHandlerTest.java new file mode 100644 index 000000000..80cae9c4b --- /dev/null +++ b/components/http/okHttp/src/test/java/com/microsoft/kiota/http/UrlReplaceHandlerTest.java @@ -0,0 +1,59 @@ +package com.microsoft.kiota.http; + +import com.microsoft.kiota.http.middleware.UrlReplaceHandler; +import com.microsoft.kiota.http.middleware.options.UrlReplaceHandlerOption; +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class UrlReplaceHandlerTest { + + private final static String defaultUsersWithTokenUrl = "https://graph.microsoft.com/v1.0/users/TokenToReplace"; + private final static HashMap defaultReplacementPairs = new HashMap<>(); + + @Test + void testUrlReplaceHandler_no_replacementPairs() throws IOException { + Interceptor[] interceptors = new Interceptor[]{new UrlReplaceHandler(new UrlReplaceHandlerOption())}; + final OkHttpClient client = KiotaClientFactory.create(interceptors).build(); + final Request request = new Request.Builder().url(defaultUsersWithTokenUrl).build(); + final Response response = client.newCall(request).execute(); + + assertNotNull(response); + assertEquals(defaultUsersWithTokenUrl, response.request().url().toString()); //url should remain the same without replacement pairs + + } + @Test + void testUrlReplaceHandler_default_url() throws IOException { + defaultReplacementPairs.put("/users/TokenToReplace", "/me"); + Interceptor[] interceptors = new Interceptor[]{new UrlReplaceHandler(new UrlReplaceHandlerOption(defaultReplacementPairs))}; + final OkHttpClient client = KiotaClientFactory.create(interceptors).build(); + final Request request = new Request.Builder().url(defaultUsersWithTokenUrl).build(); + final Response response = client.newCall(request).execute(); + final String expectedNewUrl = "https://graph.microsoft.com/v1.0/me"; + + assertNotNull(response); + assertEquals(expectedNewUrl, response.request().url().toString()); + } + @Test + void testUrlReplaceHandler_multiple_pairs() throws IOException { + defaultReplacementPairs.put("/users/TokenToReplace", "/me"); + defaultReplacementPairs.put("{secondToken}", "expectedValue"); + String customUrl = "https://graph.microsoft.com/beta/users/TokenToReplace/{secondToken}"; //using special characters to test decoding. + Interceptor[] interceptors = new Interceptor[]{new UrlReplaceHandler(new UrlReplaceHandlerOption(defaultReplacementPairs))}; + final OkHttpClient client = KiotaClientFactory.create(interceptors).build(); + final Request request = new Request.Builder().url(customUrl).build(); + final Response response = client.newCall(request).execute(); + final String expectedNewUrl = "https://graph.microsoft.com/beta/me/expectedValue"; + + assertNotNull(response); + assertEquals(expectedNewUrl, response.request().url().toString()); + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 66cc48b5f..6a1346055 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,7 +26,7 @@ org.gradle.caching=true mavenGroupId = com.microsoft.kiota mavenMajorVersion = 0 mavenMinorVersion = 4 -mavenPatchVersion = 6 +mavenPatchVersion = 7 mavenArtifactSuffix = #These values are used to run functional tests