Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 213 additions & 0 deletions .palantir/revapi.yml

Large diffs are not rendered by default.

15 changes: 0 additions & 15 deletions conjure-java-jaxrs-client/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@ dependencies {
api project(":client-config")
api project(":conjure-java-legacy-clients")
api "com.google.code.findbugs:jsr305"
// TODO(dsanduleac): Should be implementation, but can't because we expose feign.TextDelegateEncoder
api "com.netflix.feign:feign-core", {
// prefer jakarta.ws.rs:jakarta.ws.rs-api
exclude group: 'javax.ws.rs', module: 'javax.ws.rs-api'
}
api "com.palantir.dialogue:dialogue-target"

implementation project(":conjure-java-annotations")
Expand All @@ -35,11 +30,9 @@ dependencies {
implementation project(":conjure-java-jackson-serialization")
implementation "com.google.guava:guava"
implementation "com.github.ben-manes.caffeine:caffeine"
implementation "com.netflix.feign:feign-jackson"
testImplementation project(":conjure-java-jersey-jakarta-server")
testImplementation project(':keystores')
testImplementation project(':undertow-jakarta-testing')
testImplementation "com.netflix.feign:feign-jackson"
testImplementation "com.squareup.okhttp3:mockwebserver"
testImplementation "org.junit.jupiter:junit-jupiter"
testImplementation 'org.junit.jupiter:junit-jupiter-api'
Expand All @@ -48,11 +41,3 @@ dependencies {
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "com.palantir.safe-logging:preconditions-assertj"
}

// feign.DefaultMethodHandler sets Lookup.IMPL_LOOKUP accessible resulting in
// InaccessibleObjectException: Unable to make field static final java.lang.invoke.MethodHandles$Lookup
// java.lang.invoke.MethodHandles$Lookup.IMPL_LOOKUP accessible: module java.base does not
// "opens java.lang.invoke" to unnamed module
moduleJvmArgs {
opens 'java.base/java.lang.invoke'
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* (c) Copyright 2017 Palantir Technologies Inc. All rights reserved.
* (c) Copyright 2026 Palantir Technologies Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -14,19 +14,16 @@
* limitations under the License.
*/

package com.palantir.conjure.java.client.jaxrs.feignimpl;
package com.palantir.conjure.java.client.jaxrs;

import feign.Contract;
import feign.Feign;
import feign.MethodMetadata;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
* Base class that provides the structure for a delegating {@link Contract}. Delegates the initial
* {@link #parseAndValidatateMetadata(Class)} call to the wrapped Contract and then calls {@link #processMetadata(Class,
* {@link #parseAndValidateMetadata(Class)} call to the wrapped Contract and then calls {@link #processMetadata(Class,
* Method, MethodMetadata)} on all of the methods that have metadata from the initial call.
*/
abstract class AbstractDelegatingContract implements Contract {
Expand All @@ -38,8 +35,8 @@ abstract class AbstractDelegatingContract implements Contract {
}

@Override
public final List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType) {
List<MethodMetadata> mdList = delegate.parseAndValidatateMetadata(targetType);
public final List<MethodMetadata> parseAndValidateMetadata(Class<?> targetType) {
List<MethodMetadata> mdList = delegate.parseAndValidateMetadata(targetType);

Map<String, MethodMetadata> methodMetadataByConfigKey = new LinkedHashMap<String, MethodMetadata>();
for (MethodMetadata md : mdList) {
Expand All @@ -60,5 +57,5 @@ public final List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType
return mdList;
}

protected abstract void processMetadata(Class<?> targetType, Method method, MethodMetadata metadata);
abstract void processMetadata(Class<?> targetType, Method method, MethodMetadata metadata);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,6 @@
import com.palantir.conjure.java.annotations.JaxRsServer;
import com.palantir.conjure.java.api.config.service.UserAgent;
import com.palantir.conjure.java.client.config.ClientConfiguration;
import com.palantir.conjure.java.client.jaxrs.feignimpl.CborDelegateDecoder;
import com.palantir.conjure.java.client.jaxrs.feignimpl.CborDelegateEncoder;
import com.palantir.conjure.java.client.jaxrs.feignimpl.EmptyContainerDecoder;
import com.palantir.conjure.java.client.jaxrs.feignimpl.EndpointNameHeaderEnrichmentContract;
import com.palantir.conjure.java.client.jaxrs.feignimpl.GuavaOptionalAwareContract;
import com.palantir.conjure.java.client.jaxrs.feignimpl.GuavaOptionalAwareDecoder;
import com.palantir.conjure.java.client.jaxrs.feignimpl.InputStreamDelegateDecoder;
import com.palantir.conjure.java.client.jaxrs.feignimpl.InputStreamDelegateEncoder;
import com.palantir.conjure.java.client.jaxrs.feignimpl.Java8OptionalAwareContract;
import com.palantir.conjure.java.client.jaxrs.feignimpl.Java8OptionalAwareDecoder;
import com.palantir.conjure.java.client.jaxrs.feignimpl.MethodHeaderEnrichmentContract;
import com.palantir.conjure.java.client.jaxrs.feignimpl.NeverReturnNullDecoder;
import com.palantir.conjure.java.client.jaxrs.feignimpl.SlashEncodingContract;
import com.palantir.conjure.java.client.jaxrs.feignimpl.TextDelegateDecoder;
import com.palantir.conjure.java.client.jaxrs.feignimpl.TextDelegateEncoder;
import com.palantir.conjure.java.dialogue.serde.DefaultConjureRuntime;
import com.palantir.conjure.java.okhttp.HostEventsSink;
import com.palantir.dialogue.Channel;
Expand All @@ -46,17 +31,6 @@
import com.palantir.logsafe.Safe;
import com.palantir.logsafe.SafeArg;
import com.palantir.logsafe.exceptions.SafeIllegalArgumentException;
import feign.Contract;
import feign.Feign;
import feign.Logger;
import feign.Request;
import feign.RequestTemplate;
import feign.Retryer;
import feign.Target;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.jackson.JacksonDecoder;
import java.util.Objects;

/** Not meant to be implemented outside of this library. */
abstract class AbstractFeignJaxRsClientBuilder {
Expand All @@ -72,9 +46,9 @@ abstract class AbstractFeignJaxRsClientBuilder {
this.config = config;
}

protected abstract ObjectMapper getObjectMapper();
abstract ObjectMapper getObjectMapper();

protected abstract ObjectMapper getCborObjectMapper();
abstract ObjectMapper getCborObjectMapper();

/** Set the host metrics registry to use when constructing the OkHttp client. */
final AbstractFeignJaxRsClientBuilder hostEventsSink(HostEventsSink newHostEventsSink) {
Expand Down Expand Up @@ -116,8 +90,6 @@ static <T> T create(
.decoder(createDecoder(clientNameForLogging, jsonMapper, cborMapper))
.errorDecoder(new DialogueFeignClient.RemoteExceptionDecoder(runtime))
.client(new DialogueFeignClient(serviceClass, channel, runtime, FeignDialogueTarget.BASE_URL))
.logLevel(Logger.Level.NONE) // we use Dialogue for logging. (note that NONE is the default)
.retryer(new Retryer.Default(0, 0, 1)) // use dialogue retry mechanism only
.target(new FeignDialogueTarget<>(serviceClass, channel));
}

Expand All @@ -126,57 +98,25 @@ static <T> T create(
* target. However, there's a great deal of other configuration, and we handle failover/retries in Dialogue
* which makes every client appear to use the same URL.
*/
private static final class FeignDialogueTarget<T> implements Target<T> {
private record FeignDialogueTarget<T>(Class<T> serviceClass, Channel channel) implements Target<T> {
private static final String BASE_URL = "dialogue://feign";

private final Class<T> serviceClass;
private final Target<T> delegate;
// For equality checks
private final Channel channel;

FeignDialogueTarget(Class<T> serviceClass, Channel channel) {
this.serviceClass = serviceClass;
this.channel = channel;
this.delegate = new HardCodedTarget<>(serviceClass, BASE_URL);
}

@Override
public Class<T> type() {
return serviceClass;
}

@Override
public String name() {
return delegate.name();
}

@Override
public String url() {
return delegate.url();
return BASE_URL;
}

@Override
public Request apply(RequestTemplate input) {
return delegate.apply(input);
}

@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
if (input.url().indexOf("http") != 0) {
input.insert(0, url());
}
FeignDialogueTarget<?> that = (FeignDialogueTarget<?>) other;
return serviceClass.equals(that.serviceClass)
&& delegate.equals(that.delegate)
&& channel.equals(that.channel);
}

@Override
public int hashCode() {
return Objects.hash(serviceClass, delegate, channel);
return input.request();
}
}

Expand Down Expand Up @@ -205,7 +145,7 @@ private static Decoder createDecoder(

private static Encoder createEncoder(
@Safe String clientNameForLogging, ObjectMapper jsonMapper, ObjectMapper cborMapper) {
Encoder encoder = new ConjureFeignJacksonEncoder(jsonMapper);
Encoder encoder = new JacksonEncoder(jsonMapper);
encoder = new CborDelegateEncoder(cborMapper, encoder);
encoder = new TextDelegateEncoder(encoder);
encoder = new InputStreamDelegateEncoder(clientNameForLogging, encoder);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
* (c) Copyright 2026 Palantir Technologies Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -14,15 +14,12 @@
* limitations under the License.
*/

package com.palantir.conjure.java.client.jaxrs.feignimpl;
package com.palantir.conjure.java.client.jaxrs;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.net.HttpHeaders;
import feign.FeignException;
import feign.Response;
import feign.codec.Decoder;
import java.io.IOException;
import java.io.PushbackInputStream;
import java.lang.reflect.Type;
Expand All @@ -37,18 +34,18 @@
* <p>Ideally we'll codegen a client which handles the content-type switching where necessary (multiple possible
* response Content-Types from the server) and does not do the checking where this is known at compile time.
*/
public final class CborDelegateDecoder implements Decoder {
final class CborDelegateDecoder implements Decoder {

private final ObjectMapper cborMapper;
private final Decoder delegate;

public CborDelegateDecoder(ObjectMapper cborMapper, Decoder delegate) {
CborDelegateDecoder(ObjectMapper cborMapper, Decoder delegate) {
this.cborMapper = cborMapper;
this.delegate = delegate;
}

@Override
public Object decode(Response response, Type type) throws IOException, FeignException {
public Object decode(Response response, Type type) throws IOException {
Collection<String> contentTypes =
HeaderAccessUtils.caseInsensitiveGet(response.headers(), HttpHeaders.CONTENT_TYPE);
if (contentTypes == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
* (c) Copyright 2026 Palantir Technologies Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -14,19 +14,15 @@
* limitations under the License.
*/

package com.palantir.conjure.java.client.jaxrs.feignimpl;
package com.palantir.conjure.java.client.jaxrs;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.HttpHeaders;
import feign.RequestTemplate;
import feign.codec.EncodeException;
import feign.codec.Encoder;
import java.io.UncheckedIOException;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.Collection;

/**
Expand All @@ -38,20 +34,20 @@
* <p>In the future we will likely codegen the client and thus remove the need for scanning the headers on every
* request.
*/
public final class CborDelegateEncoder implements Encoder {
final class CborDelegateEncoder implements Encoder {

public static final String MIME_TYPE = "application/cbor";

private final ObjectMapper cborMapper;
private final Encoder delegate;

public CborDelegateEncoder(ObjectMapper cborMapper, Encoder delegate) {
CborDelegateEncoder(ObjectMapper cborMapper, Encoder delegate) {
this.cborMapper = cborMapper;
this.delegate = delegate;
}

@Override
public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
public void encode(Object object, Type bodyType, RequestTemplate template) {
Collection<String> contentTypes =
HeaderAccessUtils.caseInsensitiveGet(template.headers(), HttpHeaders.CONTENT_TYPE);
if (contentTypes == null) {
Expand All @@ -65,7 +61,7 @@ public void encode(Object object, Type bodyType, RequestTemplate template) throw

try {
JavaType javaType = cborMapper.getTypeFactory().constructType(bodyType);
template.body(cborMapper.writerFor(javaType).writeValueAsBytes(object), StandardCharsets.UTF_8);
template.body(cborMapper.writerFor(javaType).writeValueAsBytes(object));
} catch (JsonProcessingException e) {
throw new UncheckedIOException(e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* (c) Copyright 2026 Palantir Technologies Inc. All rights reserved.
*
* 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.palantir.conjure.java.client.jaxrs;

import java.io.IOException;

/**
* Submits HTTP {@link Request requests}. Implementations are expected to be thread-safe.
*/
interface Client {

/**
* Executes a request against its {@link Request#url() url} and returns a response.
*
* @param request safe to replay.
* @return connected response, {@link Response.Body} is absent or unread
* @throws IOException on a network error connecting to {@link Request#url()}.
*/
Response execute(Request request) throws IOException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
import com.palantir.logsafe.Preconditions;
import com.palantir.logsafe.SafeArg;
import com.palantir.logsafe.exceptions.SafeIllegalStateException;
import feign.Contract;
import feign.MethodMetadata;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
Expand All @@ -34,10 +32,10 @@
* JAXRSContract.java</a> which is licensed under Apache 2.
* We have modified the implementation to handle both jaxrs and jakarta annotations, easing migrations.
*/
public final class CompatibleJaxRsContract extends Contract.BaseContract {
final class CompatibleJaxRsContract extends Contract.BaseContract {

@Override
protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
Annotation path = Annotations.PATH.getAnnotation(clz);
if (path != null) {
String pathValue = Strings.emptyToNull(getAnnotationValue(path));
Expand All @@ -63,7 +61,7 @@ protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
}

@Override
protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
Class<? extends Annotation> annotationType = methodAnnotation.annotationType();
Annotation http = Annotations.HTTP_METHOD.getAnnotation(annotationType);
if (http != null) {
Expand Down Expand Up @@ -112,7 +110,7 @@ private void handleConsumesAnnotation(MethodMetadata data, Annotation consumes,
}

@Override
protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {
boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {
boolean isHttpParam = false;
for (Annotation parameterAnnotation : annotations) {
Class<? extends Annotation> annotationType =
Expand Down
Loading