Browse Source
Dagger 1.x and 2.x are incompatible. Rather than choose one over the other, this change removes Dagger completely. Users can now choose any injector, constructing Feign via its Builder. This change also drops support for javax.inject.Named, which has been replaced by feign.Param. see #120pull/141/head
Adrian Cole
10 years ago
48 changed files with 530 additions and 1542 deletions
@ -1,178 +0,0 @@ |
|||||||
// Manages classpath and IDE annotation processing config for dagger. |
|
||||||
// |
|
||||||
// setup: |
|
||||||
// Add the following to your root build.gradle |
|
||||||
// |
|
||||||
// apply plugin: 'idea' |
|
||||||
// subprojects { |
|
||||||
// apply from: rootProject.file('dagger.gradle') |
|
||||||
// } |
|
||||||
// |
|
||||||
// do not use gradle integration of the ide. instead generate and import like so: |
|
||||||
// |
|
||||||
// ./gradlew clean cleanEclipse cleanIdea eclipse idea |
|
||||||
// |
|
||||||
// known limitations: |
|
||||||
// as output folders include generated classes, you may need to run clean a few times. |
|
||||||
// incompatible with android plugin as it applies the java plugin |
|
||||||
// unnecessarily applies both eclipse and idea plugins even if you don't use them |
|
||||||
// suffers from the normal non-IDE eclipse integration where nested projects don't import properly. |
|
||||||
// change your structure to flattened to avoid this. |
|
||||||
// |
|
||||||
// deprecated by: https://github.com/Netflix/gradle-template/issues/8 |
|
||||||
// |
|
||||||
// original design: cfieber |
|
||||||
apply plugin: 'java' |
|
||||||
apply plugin: 'eclipse' |
|
||||||
apply plugin: 'idea' |
|
||||||
|
|
||||||
if (!project.hasProperty('daggerVersion')) { |
|
||||||
ext { |
|
||||||
daggerVersion = "1.2.2" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
configurations { |
|
||||||
daggerCompiler { |
|
||||||
visible false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
configurations.all { |
|
||||||
resolutionStrategy { |
|
||||||
eachDependency { DependencyResolveDetails details -> |
|
||||||
if (details.requested.group == 'com.squareup.dagger') { |
|
||||||
details.useVersion daggerVersion |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
def annotationGeneratedSources = file('.generated/src') |
|
||||||
def annotationGeneratedTestSources = file('.generated/test') |
|
||||||
|
|
||||||
task prepareAnnotationGeneratedSourceDirs(overwrite: true) << { |
|
||||||
annotationGeneratedSources.mkdirs() |
|
||||||
annotationGeneratedTestSources.mkdirs() |
|
||||||
sourceSets*.java.srcDirs*.each { it.mkdirs() } |
|
||||||
sourceSets*.resources.srcDirs*.each { it.mkdirs() } |
|
||||||
} |
|
||||||
|
|
||||||
sourceSets { |
|
||||||
main { |
|
||||||
java { |
|
||||||
compileClasspath += configurations.daggerCompiler |
|
||||||
} |
|
||||||
} |
|
||||||
test { |
|
||||||
java { |
|
||||||
compileClasspath += configurations.daggerCompiler |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
dependencies { |
|
||||||
compile "com.squareup.dagger:dagger:${project.daggerVersion}" |
|
||||||
daggerCompiler "com.squareup.dagger:dagger-compiler:${project.daggerVersion}" |
|
||||||
} |
|
||||||
|
|
||||||
rootProject.idea.project.ipr.withXml { projectXml -> |
|
||||||
projectXml.asNode().component.find { it.@name == 'CompilerConfiguration' }.annotationProcessing[0].replaceNode { |
|
||||||
annotationProcessing { |
|
||||||
profile(default: true, name: 'Default', enabled: true) { |
|
||||||
sourceOutputDir name: relativePath(annotationGeneratedSources) |
|
||||||
sourceTestOutputDir name: relativePath(annotationGeneratedTestSources) |
|
||||||
outputRelativeToContentRoot value: true |
|
||||||
processorPath useClasspath: true |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
tasks.ideaModule.dependsOn(prepareAnnotationGeneratedSourceDirs) |
|
||||||
|
|
||||||
idea.module { |
|
||||||
scopes.PROVIDED.plus += [project.configurations.daggerCompiler] |
|
||||||
iml.withXml { xml-> |
|
||||||
def moduleSource = xml.asNode().component.find { it.@name = 'NewModuleRootManager' }.content[0] |
|
||||||
moduleSource.appendNode('sourceFolder', [url: "file://\$MODULE_DIR\$/${relativePath(annotationGeneratedSources)}", isTestSource: false]) |
|
||||||
moduleSource.appendNode('sourceFolder', [url: "file://\$MODULE_DIR\$/${relativePath(annotationGeneratedTestSources)}", isTestSource: true]) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
tasks.eclipseClasspath.dependsOn(prepareAnnotationGeneratedSourceDirs) |
|
||||||
|
|
||||||
eclipse.classpath { |
|
||||||
plusConfigurations += [project.configurations.daggerCompiler] |
|
||||||
} |
|
||||||
|
|
||||||
tasks.eclipseClasspath { |
|
||||||
doLast { |
|
||||||
eclipse.classpath.file.withXml { |
|
||||||
it.asNode().children()[0] + { |
|
||||||
classpathentry(kind: 'src', path: relativePath(annotationGeneratedSources)) { |
|
||||||
attributes { |
|
||||||
attribute name: 'optional', value: true |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// http://forums.gradle.org/gradle/topics/eclipse_generated_files_should_be_put_in_the_same_place_as_the_gradle_generated_files |
|
||||||
Map pathMappings = [:]; |
|
||||||
SourceSetContainer sourceSets = project.sourceSets; |
|
||||||
sourceSets.each { SourceSet sourceSet -> |
|
||||||
String relativeJavaOutputDirectory = project.relativePath(sourceSet.output.classesDir); |
|
||||||
String relativeResourceOutputDirectory = project.relativePath(sourceSet.output.resourcesDir); |
|
||||||
sourceSet.java.getSrcDirTrees().each { DirectoryTree sourceDirectory -> |
|
||||||
String relativeSrcPath = project.relativePath(sourceDirectory.dir.absolutePath); |
|
||||||
|
|
||||||
pathMappings[relativeSrcPath] = relativeJavaOutputDirectory; |
|
||||||
} |
|
||||||
sourceSet.resources.getSrcDirTrees().each { DirectoryTree resourceDirectory -> |
|
||||||
String relativeResourcePath = project.relativePath(resourceDirectory.dir.absolutePath); |
|
||||||
|
|
||||||
pathMappings[relativeResourcePath] = relativeResourceOutputDirectory; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
project.eclipse.classpath.file { |
|
||||||
whenMerged { classpath -> |
|
||||||
classpath.entries.findAll { entry -> |
|
||||||
return entry.kind == 'src'; |
|
||||||
}.each { entry -> |
|
||||||
if(pathMappings.containsKey(entry.path)) { |
|
||||||
entry.output = pathMappings[entry.path]; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
eclipse.jdt.file.withProperties { props -> |
|
||||||
props.setProperty('org.eclipse.jdt.core.compiler.processAnnotations', 'enabled') |
|
||||||
} |
|
||||||
|
|
||||||
tasks.eclipseJdt { |
|
||||||
doFirst { |
|
||||||
def aptPrefs = file('.settings/org.eclipse.jdt.apt.core.prefs') |
|
||||||
aptPrefs.parentFile.mkdirs() |
|
||||||
|
|
||||||
aptPrefs.text = """\ |
|
||||||
eclipse.preferences.version=1 |
|
||||||
org.eclipse.jdt.apt.aptEnabled=true |
|
||||||
org.eclipse.jdt.apt.genSrcDir=${relativePath(annotationGeneratedSources)} |
|
||||||
org.eclipse.jdt.apt.reconcileEnabled=true |
|
||||||
""".stripIndent() |
|
||||||
|
|
||||||
file('.factorypath').withWriter { |
|
||||||
new groovy.xml.MarkupBuilder(it).'factorypath' { |
|
||||||
project.configurations.daggerCompiler.files.each { dep -> |
|
||||||
'factorypathentry' kind: 'EXTJAR', id: dep.absolutePath, enabled: true, runInBatchMode: false |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
@ -1,37 +0,0 @@ |
|||||||
package feign.gson; |
|
||||||
|
|
||||||
import com.google.gson.Gson; |
|
||||||
import feign.RequestTemplate; |
|
||||||
import feign.Response; |
|
||||||
import feign.codec.Decoder; |
|
||||||
import feign.codec.Encoder; |
|
||||||
|
|
||||||
import javax.inject.Inject; |
|
||||||
import java.io.IOException; |
|
||||||
import java.lang.reflect.Type; |
|
||||||
|
|
||||||
/** |
|
||||||
* @deprecated use {@link GsonEncoder} and {@link GsonDecoder} instead |
|
||||||
*/ |
|
||||||
@Deprecated |
|
||||||
public class GsonCodec implements Encoder, Decoder { |
|
||||||
private final GsonEncoder encoder; |
|
||||||
private final GsonDecoder decoder; |
|
||||||
|
|
||||||
public GsonCodec() { |
|
||||||
this(new Gson()); |
|
||||||
} |
|
||||||
|
|
||||||
@Inject public GsonCodec(Gson gson) { |
|
||||||
this.encoder = new GsonEncoder(gson); |
|
||||||
this.decoder = new GsonDecoder(gson); |
|
||||||
} |
|
||||||
|
|
||||||
@Override public void encode(Object object, RequestTemplate template) { |
|
||||||
encoder.encode(object, template); |
|
||||||
} |
|
||||||
|
|
||||||
@Override public Object decode(Response response, Type type) throws IOException { |
|
||||||
return decoder.decode(response, type); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,46 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2013 Netflix, Inc. |
||||||
|
* |
||||||
|
* 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 feign.gson; |
||||||
|
|
||||||
|
import com.google.gson.Gson; |
||||||
|
import com.google.gson.GsonBuilder; |
||||||
|
import com.google.gson.TypeAdapter; |
||||||
|
import com.google.gson.reflect.TypeToken; |
||||||
|
import java.lang.reflect.Type; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import static feign.Util.resolveLastTypeParameter; |
||||||
|
|
||||||
|
final class GsonFactory { |
||||||
|
|
||||||
|
/** |
||||||
|
* Registers type adapters by implicit type. Adds one to read numbers in a |
||||||
|
* {@code Map<String, Object>} as Integers. |
||||||
|
*/ |
||||||
|
static Gson create(Iterable<TypeAdapter<?>> adapters) { |
||||||
|
GsonBuilder builder = new GsonBuilder().setPrettyPrinting(); |
||||||
|
builder.registerTypeAdapter(new TypeToken<Map<String, Object>>() { |
||||||
|
}.getType(), new DoubleToIntMapTypeAdapter()); |
||||||
|
for (TypeAdapter<?> adapter : adapters) { |
||||||
|
Type type = resolveLastTypeParameter(adapter.getClass(), TypeAdapter.class); |
||||||
|
builder.registerTypeAdapter(type, adapter); |
||||||
|
} |
||||||
|
return builder.create(); |
||||||
|
} |
||||||
|
|
||||||
|
private GsonFactory() { |
||||||
|
} |
||||||
|
} |
@ -1,92 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2013 Netflix, Inc. |
|
||||||
* |
|
||||||
* 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 feign.gson; |
|
||||||
|
|
||||||
import com.google.gson.Gson; |
|
||||||
import com.google.gson.GsonBuilder; |
|
||||||
import com.google.gson.TypeAdapter; |
|
||||||
import dagger.Provides; |
|
||||||
import feign.Feign; |
|
||||||
import feign.codec.Decoder; |
|
||||||
import feign.codec.Encoder; |
|
||||||
|
|
||||||
import javax.inject.Singleton; |
|
||||||
import java.lang.reflect.Type; |
|
||||||
import java.util.Collections; |
|
||||||
import java.util.Set; |
|
||||||
|
|
||||||
import static feign.Util.resolveLastTypeParameter; |
|
||||||
|
|
||||||
/** |
|
||||||
* <h3>Custom type adapters</h3> |
|
||||||
* <br> |
|
||||||
* In order to specify custom json parsing, |
|
||||||
* {@code Gson} supports {@link TypeAdapter type adapters}. This module adds one |
|
||||||
* to read numbers in a {@code Map<String, Object>} as Integers. You can |
|
||||||
* customize further by adding additional set bindings to the raw type |
|
||||||
* {@code TypeAdapter}. |
|
||||||
* <p/> |
|
||||||
* <br> |
|
||||||
* Here's an example of adding a custom json type adapter. |
|
||||||
* <p/> |
|
||||||
* <pre> |
|
||||||
* @Provides(type = Provides.Type.SET) |
|
||||||
* TypeAdapter upperZone() { |
|
||||||
* return new TypeAdapter<Zone>() { |
|
||||||
* |
|
||||||
* @Override |
|
||||||
* public void write(JsonWriter out, Zone value) throws IOException { |
|
||||||
* throw new IllegalArgumentException(); |
|
||||||
* } |
|
||||||
* |
|
||||||
* @Override |
|
||||||
* public Zone read(JsonReader in) throws IOException { |
|
||||||
* in.beginObject(); |
|
||||||
* Zone zone = new Zone(); |
|
||||||
* while (in.hasNext()) { |
|
||||||
* zone.put(in.nextName(), in.nextString().toUpperCase()); |
|
||||||
* } |
|
||||||
* in.endObject(); |
|
||||||
* return zone; |
|
||||||
* } |
|
||||||
* }; |
|
||||||
* } |
|
||||||
* </pre> |
|
||||||
*/ |
|
||||||
@dagger.Module(injects = Feign.class, addsTo = Feign.Defaults.class) |
|
||||||
public final class GsonModule { |
|
||||||
|
|
||||||
@Provides Encoder encoder(Gson gson) { |
|
||||||
return new GsonEncoder(gson); |
|
||||||
} |
|
||||||
|
|
||||||
@Provides Decoder decoder(Gson gson) { |
|
||||||
return new GsonDecoder(gson); |
|
||||||
} |
|
||||||
|
|
||||||
@Provides @Singleton Gson gson(Set<TypeAdapter> adapters) { |
|
||||||
GsonBuilder builder = new GsonBuilder().setPrettyPrinting(); |
|
||||||
for (TypeAdapter<?> adapter : adapters) { |
|
||||||
Type type = resolveLastTypeParameter(adapter.getClass(), TypeAdapter.class); |
|
||||||
builder.registerTypeAdapter(type, adapter); |
|
||||||
} |
|
||||||
return builder.create(); |
|
||||||
} |
|
||||||
|
|
||||||
@Provides(type = Provides.Type.SET_VALUES) Set<TypeAdapter> noDefaultTypeAdapters() { |
|
||||||
return Collections.emptySet(); |
|
||||||
} |
|
||||||
} |
|
@ -1,103 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2013 Netflix, Inc. |
|
||||||
* |
|
||||||
* 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 feign.jackson; |
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude; |
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature; |
|
||||||
import com.fasterxml.jackson.databind.JsonDeserializer; |
|
||||||
import com.fasterxml.jackson.databind.JsonSerializer; |
|
||||||
import com.fasterxml.jackson.databind.Module; |
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper; |
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature; |
|
||||||
import dagger.Provides; |
|
||||||
import feign.Feign; |
|
||||||
import feign.codec.Decoder; |
|
||||||
import feign.codec.Encoder; |
|
||||||
|
|
||||||
import javax.inject.Singleton; |
|
||||||
import java.util.Collections; |
|
||||||
import java.util.Set; |
|
||||||
|
|
||||||
/** |
|
||||||
* <h3>Custom serializers/deserializers</h3> |
|
||||||
* <br> |
|
||||||
* In order to specify custom json parsing, Jackson's {@code ObjectMapper} supports {@link JsonSerializer serializers} |
|
||||||
* and {@link JsonDeserializer deserializers}, which can be bundled together in {@link Module modules}. |
|
||||||
* <p/> |
|
||||||
* <br> |
|
||||||
* Here's an example of adding a custom module. |
|
||||||
* <p/> |
|
||||||
* <pre> |
|
||||||
* public class ObjectIdSerializer extends StdSerializer<ObjectId> { |
|
||||||
* public ObjectIdSerializer() { |
|
||||||
* super(ObjectId.class); |
|
||||||
* } |
|
||||||
* |
|
||||||
* @Override |
|
||||||
* public void serialize(ObjectId value, JsonGenerator jsonGenerator, SerializerProvider provider) throws IOException { |
|
||||||
* jsonGenerator.writeString(value.toString()); |
|
||||||
* } |
|
||||||
* } |
|
||||||
* |
|
||||||
* public class ObjectIdDeserializer extends StdDeserializer<ObjectId> { |
|
||||||
* public ObjectIdDeserializer() { |
|
||||||
* super(ObjectId.class); |
|
||||||
* } |
|
||||||
* |
|
||||||
* @Override |
|
||||||
* public ObjectId deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException { |
|
||||||
* return ObjectId.massageToObjectId(jsonParser.getValueAsString()); |
|
||||||
* } |
|
||||||
* } |
|
||||||
* |
|
||||||
* public class ObjectIdModule extends SimpleModule { |
|
||||||
* public ObjectIdModule() { |
|
||||||
* // first deserializers
|
|
||||||
* addDeserializer(ObjectId.class, new ObjectIdDeserializer()); |
|
||||||
* |
|
||||||
* // then serializers:
|
|
||||||
* addSerializer(ObjectId.class, new ObjectIdSerializer()); |
|
||||||
* } |
|
||||||
* } |
|
||||||
* |
|
||||||
* @Provides(type = Provides.Type.SET) |
|
||||||
* Module objectIdModule() { |
|
||||||
* return new ObjectIdModule(); |
|
||||||
* } |
|
||||||
* </pre> |
|
||||||
*/ |
|
||||||
@dagger.Module(injects = Feign.class, addsTo = Feign.Defaults.class) |
|
||||||
public final class JacksonModule { |
|
||||||
@Provides Encoder encoder(ObjectMapper mapper) { |
|
||||||
return new JacksonEncoder(mapper); |
|
||||||
} |
|
||||||
|
|
||||||
@Provides Decoder decoder(ObjectMapper mapper) { |
|
||||||
return new JacksonDecoder(mapper); |
|
||||||
} |
|
||||||
|
|
||||||
@Provides @Singleton ObjectMapper mapper(Set<Module> modules) { |
|
||||||
return new ObjectMapper() |
|
||||||
.setSerializationInclusion(JsonInclude.Include.NON_NULL) |
|
||||||
.configure(SerializationFeature.INDENT_OUTPUT, true) |
|
||||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) |
|
||||||
.registerModules(modules); |
|
||||||
} |
|
||||||
|
|
||||||
@Provides(type = Provides.Type.SET_VALUES) Set<Module> noDefaultModules() { |
|
||||||
return Collections.emptySet(); |
|
||||||
} |
|
||||||
} |
|
@ -1,66 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2014 Netflix, Inc. |
|
||||||
* |
|
||||||
* 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 feign.jaxb; |
|
||||||
|
|
||||||
import dagger.Provides; |
|
||||||
import feign.Feign; |
|
||||||
import feign.codec.Decoder; |
|
||||||
import feign.codec.Encoder; |
|
||||||
|
|
||||||
import javax.inject.Singleton; |
|
||||||
|
|
||||||
/** |
|
||||||
* Provides an Encoder and Decoder for handling XML responses with JAXB annotated classes. |
|
||||||
* <p> |
|
||||||
* <br> |
|
||||||
* Here is an example of configuring a custom JAXBContextFactory: |
|
||||||
* </p> |
|
||||||
* <pre> |
|
||||||
* JAXBContextFactory jaxbFactory = new JAXBContextFactory.Builder() |
|
||||||
* .withMarshallerJAXBEncoding("UTF-8") |
|
||||||
* .withMarshallerSchemaLocation("http://apihost http://apihost/schema.xsd") |
|
||||||
* .build(); |
|
||||||
* |
|
||||||
* Response response = Feign.create(Response.class, "http://apihost", new JAXBModule(jaxbFactory)); |
|
||||||
* </pre> |
|
||||||
* <p> |
|
||||||
* The JAXBContextFactory should be reused across requests as it caches the created JAXB contexts. |
|
||||||
* </p> |
|
||||||
*/ |
|
||||||
@dagger.Module(injects = Feign.class, addsTo = Feign.Defaults.class) |
|
||||||
public final class JAXBModule { |
|
||||||
private final JAXBContextFactory jaxbContextFactory; |
|
||||||
|
|
||||||
public JAXBModule() { |
|
||||||
this.jaxbContextFactory = new JAXBContextFactory.Builder().build(); |
|
||||||
} |
|
||||||
|
|
||||||
public JAXBModule(JAXBContextFactory jaxbContextFactory) { |
|
||||||
this.jaxbContextFactory = jaxbContextFactory; |
|
||||||
} |
|
||||||
|
|
||||||
@Provides Encoder encoder(JAXBEncoder jaxbEncoder) { |
|
||||||
return jaxbEncoder; |
|
||||||
} |
|
||||||
|
|
||||||
@Provides Decoder decoder(JAXBDecoder jaxbDecoder) { |
|
||||||
return jaxbDecoder; |
|
||||||
} |
|
||||||
|
|
||||||
@Provides @Singleton JAXBContextFactory jaxbContextFactory() { |
|
||||||
return this.jaxbContextFactory; |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,123 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2013 Netflix, Inc. |
||||||
|
* |
||||||
|
* 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 feign.jaxrs; |
||||||
|
|
||||||
|
import feign.Contract; |
||||||
|
import feign.MethodMetadata; |
||||||
|
|
||||||
|
import javax.ws.rs.Consumes; |
||||||
|
import javax.ws.rs.FormParam; |
||||||
|
import javax.ws.rs.HeaderParam; |
||||||
|
import javax.ws.rs.HttpMethod; |
||||||
|
import javax.ws.rs.Path; |
||||||
|
import javax.ws.rs.PathParam; |
||||||
|
import javax.ws.rs.Produces; |
||||||
|
import javax.ws.rs.QueryParam; |
||||||
|
import java.lang.annotation.Annotation; |
||||||
|
import java.lang.reflect.Method; |
||||||
|
import java.util.Collection; |
||||||
|
|
||||||
|
import static feign.Util.checkState; |
||||||
|
import static feign.Util.emptyToNull; |
||||||
|
|
||||||
|
/** |
||||||
|
* Please refer to the |
||||||
|
* <a href="https://github.com/Netflix/feign/tree/master/feign-jaxrs">Feign JAX-RS README</a>. |
||||||
|
*/ |
||||||
|
public final class JAXRSContract extends Contract.BaseContract { |
||||||
|
static final String ACCEPT = "Accept"; |
||||||
|
static final String CONTENT_TYPE = "Content-Type"; |
||||||
|
|
||||||
|
@Override |
||||||
|
public MethodMetadata parseAndValidatateMetadata(Method method) { |
||||||
|
MethodMetadata md = super.parseAndValidatateMetadata(method); |
||||||
|
Path path = method.getDeclaringClass().getAnnotation(Path.class); |
||||||
|
if (path != null) { |
||||||
|
String pathValue = emptyToNull(path.value()); |
||||||
|
checkState(pathValue != null, "Path.value() was empty on type %s", method.getDeclaringClass().getName()); |
||||||
|
if (!pathValue.startsWith("/")) { |
||||||
|
pathValue = "/" + pathValue; |
||||||
|
} |
||||||
|
md.template().insert(0, pathValue); |
||||||
|
} |
||||||
|
return md; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) { |
||||||
|
Class<? extends Annotation> annotationType = methodAnnotation.annotationType(); |
||||||
|
HttpMethod http = annotationType.getAnnotation(HttpMethod.class); |
||||||
|
if (http != null) { |
||||||
|
checkState(data.template().method() == null, |
||||||
|
"Method %s contains multiple HTTP methods. Found: %s and %s", method.getName(), data.template() |
||||||
|
.method(), http.value()); |
||||||
|
data.template().method(http.value()); |
||||||
|
} else if (annotationType == Path.class) { |
||||||
|
String pathValue = emptyToNull(Path.class.cast(methodAnnotation).value()); |
||||||
|
checkState(pathValue != null, "Path.value() was empty on method %s", method.getName()); |
||||||
|
String methodAnnotationValue = Path.class.cast(methodAnnotation).value(); |
||||||
|
if (!methodAnnotationValue.startsWith("/") && !data.template().toString().endsWith("/")) { |
||||||
|
methodAnnotationValue = "/" + methodAnnotationValue; |
||||||
|
} |
||||||
|
data.template().append(methodAnnotationValue); |
||||||
|
} else if (annotationType == Produces.class) { |
||||||
|
String[] serverProduces = ((Produces) methodAnnotation).value(); |
||||||
|
String clientAccepts = serverProduces.length == 0 ? null: emptyToNull(serverProduces[0]); |
||||||
|
checkState(clientAccepts != null, "Produces.value() was empty on method %s", method.getName()); |
||||||
|
data.template().header(ACCEPT, clientAccepts); |
||||||
|
} else if (annotationType == Consumes.class) { |
||||||
|
String[] serverConsumes = ((Consumes) methodAnnotation).value(); |
||||||
|
String clientProduces = serverConsumes.length == 0 ? null: emptyToNull(serverConsumes[0]); |
||||||
|
checkState(clientProduces != null, "Consumes.value() was empty on method %s", method.getName()); |
||||||
|
data.template().header(CONTENT_TYPE, clientProduces); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) { |
||||||
|
boolean isHttpParam = false; |
||||||
|
for (Annotation parameterAnnotation : annotations) { |
||||||
|
Class<? extends Annotation> annotationType = parameterAnnotation.annotationType(); |
||||||
|
if (annotationType == PathParam.class) { |
||||||
|
String name = PathParam.class.cast(parameterAnnotation).value(); |
||||||
|
checkState(emptyToNull(name) != null, "PathParam.value() was empty on parameter %s", paramIndex); |
||||||
|
nameParam(data, name, paramIndex); |
||||||
|
isHttpParam = true; |
||||||
|
} else if (annotationType == QueryParam.class) { |
||||||
|
String name = QueryParam.class.cast(parameterAnnotation).value(); |
||||||
|
checkState(emptyToNull(name) != null, "QueryParam.value() was empty on parameter %s", paramIndex); |
||||||
|
Collection<String> query = addTemplatedParam(data.template().queries().get(name), name); |
||||||
|
data.template().query(name, query); |
||||||
|
nameParam(data, name, paramIndex); |
||||||
|
isHttpParam = true; |
||||||
|
} else if (annotationType == HeaderParam.class) { |
||||||
|
String name = HeaderParam.class.cast(parameterAnnotation).value(); |
||||||
|
checkState(emptyToNull(name) != null, "HeaderParam.value() was empty on parameter %s", paramIndex); |
||||||
|
Collection<String> header = addTemplatedParam(data.template().headers().get(name), name); |
||||||
|
data.template().header(name, header); |
||||||
|
nameParam(data, name, paramIndex); |
||||||
|
isHttpParam = true; |
||||||
|
} else if (annotationType == FormParam.class) { |
||||||
|
String name = FormParam.class.cast(parameterAnnotation).value(); |
||||||
|
checkState(emptyToNull(name) != null, "FormParam.value() was empty on parameter %s", paramIndex); |
||||||
|
data.formParams().add(name); |
||||||
|
nameParam(data, name, paramIndex); |
||||||
|
isHttpParam = true; |
||||||
|
} |
||||||
|
} |
||||||
|
return isHttpParam; |
||||||
|
} |
||||||
|
} |
@ -1,133 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2013 Netflix, Inc. |
|
||||||
* |
|
||||||
* 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 feign.jaxrs; |
|
||||||
|
|
||||||
import dagger.Provides; |
|
||||||
import feign.Body; |
|
||||||
import feign.Contract; |
|
||||||
import feign.MethodMetadata; |
|
||||||
|
|
||||||
import javax.ws.rs.Consumes; |
|
||||||
import javax.ws.rs.FormParam; |
|
||||||
import javax.ws.rs.HeaderParam; |
|
||||||
import javax.ws.rs.HttpMethod; |
|
||||||
import javax.ws.rs.Path; |
|
||||||
import javax.ws.rs.PathParam; |
|
||||||
import javax.ws.rs.Produces; |
|
||||||
import javax.ws.rs.QueryParam; |
|
||||||
import java.lang.annotation.Annotation; |
|
||||||
import java.lang.reflect.Method; |
|
||||||
import java.util.Collection; |
|
||||||
|
|
||||||
import static feign.Util.checkState; |
|
||||||
import static feign.Util.emptyToNull; |
|
||||||
|
|
||||||
/** |
|
||||||
* Please refer to the |
|
||||||
* <a href="https://github.com/Netflix/feign/tree/master/feign-jaxrs">Feign JAX-RS README</a>. |
|
||||||
*/ |
|
||||||
@dagger.Module(library = true, overrides = true) |
|
||||||
public final class JAXRSModule { |
|
||||||
static final String ACCEPT = "Accept"; |
|
||||||
static final String CONTENT_TYPE = "Content-Type"; |
|
||||||
|
|
||||||
@Provides Contract provideContract() { |
|
||||||
return new JAXRSContract(); |
|
||||||
} |
|
||||||
|
|
||||||
public static final class JAXRSContract extends Contract.BaseContract { |
|
||||||
|
|
||||||
@Override |
|
||||||
public MethodMetadata parseAndValidatateMetadata(Method method) { |
|
||||||
MethodMetadata md = super.parseAndValidatateMetadata(method); |
|
||||||
Path path = method.getDeclaringClass().getAnnotation(Path.class); |
|
||||||
if (path != null) { |
|
||||||
String pathValue = emptyToNull(path.value()); |
|
||||||
checkState(pathValue != null, "Path.value() was empty on type %s", method.getDeclaringClass().getName()); |
|
||||||
if (!pathValue.startsWith("/")) { |
|
||||||
pathValue = "/" + pathValue; |
|
||||||
} |
|
||||||
md.template().insert(0, pathValue); |
|
||||||
} |
|
||||||
return md; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) { |
|
||||||
Class<? extends Annotation> annotationType = methodAnnotation.annotationType(); |
|
||||||
HttpMethod http = annotationType.getAnnotation(HttpMethod.class); |
|
||||||
if (http != null) { |
|
||||||
checkState(data.template().method() == null, |
|
||||||
"Method %s contains multiple HTTP methods. Found: %s and %s", method.getName(), data.template() |
|
||||||
.method(), http.value()); |
|
||||||
data.template().method(http.value()); |
|
||||||
} else if (annotationType == Path.class) { |
|
||||||
String pathValue = emptyToNull(Path.class.cast(methodAnnotation).value()); |
|
||||||
checkState(pathValue != null, "Path.value() was empty on method %s", method.getName()); |
|
||||||
String methodAnnotationValue = Path.class.cast(methodAnnotation).value(); |
|
||||||
if (!methodAnnotationValue.startsWith("/") && !data.template().toString().endsWith("/")) { |
|
||||||
methodAnnotationValue = "/" + methodAnnotationValue; |
|
||||||
} |
|
||||||
data.template().append(methodAnnotationValue); |
|
||||||
} else if (annotationType == Produces.class) { |
|
||||||
String[] serverProduces = ((Produces) methodAnnotation).value(); |
|
||||||
String clientAccepts = serverProduces.length == 0 ? null: emptyToNull(serverProduces[0]); |
|
||||||
checkState(clientAccepts != null, "Produces.value() was empty on method %s", method.getName()); |
|
||||||
data.template().header(ACCEPT, clientAccepts); |
|
||||||
} else if (annotationType == Consumes.class) { |
|
||||||
String[] serverConsumes = ((Consumes) methodAnnotation).value(); |
|
||||||
String clientProduces = serverConsumes.length == 0 ? null: emptyToNull(serverConsumes[0]); |
|
||||||
checkState(clientProduces != null, "Consumes.value() was empty on method %s", method.getName()); |
|
||||||
data.template().header(CONTENT_TYPE, clientProduces); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) { |
|
||||||
boolean isHttpParam = false; |
|
||||||
for (Annotation parameterAnnotation : annotations) { |
|
||||||
Class<? extends Annotation> annotationType = parameterAnnotation.annotationType(); |
|
||||||
if (annotationType == PathParam.class) { |
|
||||||
String name = PathParam.class.cast(parameterAnnotation).value(); |
|
||||||
checkState(emptyToNull(name) != null, "PathParam.value() was empty on parameter %s", paramIndex); |
|
||||||
nameParam(data, name, paramIndex); |
|
||||||
isHttpParam = true; |
|
||||||
} else if (annotationType == QueryParam.class) { |
|
||||||
String name = QueryParam.class.cast(parameterAnnotation).value(); |
|
||||||
checkState(emptyToNull(name) != null, "QueryParam.value() was empty on parameter %s", paramIndex); |
|
||||||
Collection<String> query = addTemplatedParam(data.template().queries().get(name), name); |
|
||||||
data.template().query(name, query); |
|
||||||
nameParam(data, name, paramIndex); |
|
||||||
isHttpParam = true; |
|
||||||
} else if (annotationType == HeaderParam.class) { |
|
||||||
String name = HeaderParam.class.cast(parameterAnnotation).value(); |
|
||||||
checkState(emptyToNull(name) != null, "HeaderParam.value() was empty on parameter %s", paramIndex); |
|
||||||
Collection<String> header = addTemplatedParam(data.template().headers().get(name), name); |
|
||||||
data.template().header(name, header); |
|
||||||
nameParam(data, name, paramIndex); |
|
||||||
isHttpParam = true; |
|
||||||
} else if (annotationType == FormParam.class) { |
|
||||||
String name = FormParam.class.cast(parameterAnnotation).value(); |
|
||||||
checkState(emptyToNull(name) != null, "FormParam.value() was empty on parameter %s", paramIndex); |
|
||||||
data.formParams().add(name); |
|
||||||
nameParam(data, name, paramIndex); |
|
||||||
isHttpParam = true; |
|
||||||
} |
|
||||||
} |
|
||||||
return isHttpParam; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,48 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2013 Netflix, Inc. |
|
||||||
* |
|
||||||
* 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 feign.ribbon; |
|
||||||
|
|
||||||
import dagger.Provides; |
|
||||||
import feign.Client; |
|
||||||
import javax.inject.Named; |
|
||||||
import javax.inject.Singleton; |
|
||||||
|
|
||||||
/** |
|
||||||
* Adding this module will override URL resolution of {@link feign.Client Feign's client}, |
|
||||||
* adding smart routing and resiliency capabilities provided by Ribbon. |
|
||||||
* <br> |
|
||||||
* When using this, ensure the {@link feign.Target#url()} is set to as {@code http://clientName}
|
|
||||||
* or {@code https://clientName}. {@link com.netflix.client.config.IClientConfig#getClientName() clientName}
|
|
||||||
* will lookup the real url and port of your service dynamically. |
|
||||||
* <br> |
|
||||||
* Ex. |
|
||||||
* <pre> |
|
||||||
* MyService api = Feign.create(MyService.class, "http://myAppProd", new RibbonModule()); |
|
||||||
* </pre> |
|
||||||
* Where {@code myAppProd} is the ribbon client name and {@code myAppProd.ribbon.listOfServers} configuration |
|
||||||
* is set. |
|
||||||
*/ |
|
||||||
@dagger.Module(overrides = true, library = true, complete = false) |
|
||||||
public class RibbonModule { |
|
||||||
|
|
||||||
@Provides @Named("delegate") Client delegate(Client.Default delegate) { |
|
||||||
return delegate; |
|
||||||
} |
|
||||||
|
|
||||||
@Provides @Singleton Client httpClient(@Named("delegate") Client client) { |
|
||||||
return new RibbonClient(client); |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue