Skip to content

Commit

Permalink
add BeanQueryMapEncoder (#802)
Browse files Browse the repository at this point in the history
* changed default query encoder result from POJO field to getter property

* changed default query encoder result from POJO field to getter property

* reset mistakenly deleted file

* Create PropertyQueryMapEncoder and extract QueryMapEncoder.Default to FieldQueryMapEncoder

* rename PropertyQueryMapEncoder to BeanQueryMapEncoder and add README

* fix README

* add comments to QueryMapEncoder and remove deprecation on Default

* rename test name

* rename package name queryMap to querymap

* format code
  • Loading branch information
王灿 authored and velo committed Oct 10, 2018
1 parent 6ad5584 commit 99bbcba
Show file tree
Hide file tree
Showing 8 changed files with 429 additions and 78 deletions.
12 changes: 12 additions & 0 deletions README.md
Expand Up @@ -690,6 +690,18 @@ public class Example {
}
```

When annotating objects with @QueryMap, the default encoder uses reflection to inspect provided objects Fields to expand the objects values into a query string. If you prefer that the query string be built using getter and setter methods, as defined in the Java Beans API, please use the BeanQueryMapEncoder

```java
public class Example {
public static void main(String[] args) {
MyApi myApi = Feign.builder()
.queryMapEncoder(new BeanQueryMapEncoder())
.target(MyApi.class, "https://api.hostname.com");
}
}
```

### Error Handling
If you need more control over handling unexpected responses, Feign instances can
register a custom `ErrorDecoder` via the builder.
Expand Down
69 changes: 13 additions & 56 deletions core/src/main/java/feign/QueryMapEncoder.java
Expand Up @@ -13,16 +13,16 @@
*/
package feign;

import feign.codec.EncodeException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import feign.querymap.FieldQueryMapEncoder;
import feign.querymap.BeanQueryMapEncoder;
import java.util.Map;

/**
* A QueryMapEncoder encodes Objects into maps of query parameter names to values.
*
* @see FieldQueryMapEncoder
* @see BeanQueryMapEncoder
*
*/
public interface QueryMapEncoder {

Expand All @@ -34,55 +34,12 @@ public interface QueryMapEncoder {
*/
Map<String, Object> encode(Object object);

class Default implements QueryMapEncoder {

private final Map<Class<?>, ObjectParamMetadata> classToMetadata =
new HashMap<Class<?>, ObjectParamMetadata>();

@Override
public Map<String, Object> encode(Object object) throws EncodeException {
try {
ObjectParamMetadata metadata = getMetadata(object.getClass());
Map<String, Object> fieldNameToValue = new HashMap<String, Object>();
for (Field field : metadata.objectFields) {
Object value = field.get(object);
if (value != null && value != object) {
fieldNameToValue.put(field.getName(), value);
}
}
return fieldNameToValue;
} catch (IllegalAccessException e) {
throw new EncodeException("Failure encoding object into query map", e);
}
}

private ObjectParamMetadata getMetadata(Class<?> objectType) {
ObjectParamMetadata metadata = classToMetadata.get(objectType);
if (metadata == null) {
metadata = ObjectParamMetadata.parseObjectType(objectType);
classToMetadata.put(objectType, metadata);
}
return metadata;
}

private static class ObjectParamMetadata {

private final List<Field> objectFields;

private ObjectParamMetadata(List<Field> objectFields) {
this.objectFields = Collections.unmodifiableList(objectFields);
}

private static ObjectParamMetadata parseObjectType(Class<?> type) {
List<Field> fields = new ArrayList<Field>();
for (Field field : type.getDeclaredFields()) {
if (!field.isAccessible()) {
field.setAccessible(true);
}
fields.add(field);
}
return new ObjectParamMetadata(fields);
}
}
/**
* @deprecated use {@link BeanQueryMapEncoder} instead. default encoder uses reflection to inspect
* provided objects Fields to expand the objects values into a query string. If you
* prefer that the query string be built using getter and setter methods, as defined
* in the Java Beans API, please use the {@link BeanQueryMapEncoder}
*/
class Default extends FieldQueryMapEncoder {
}
}
85 changes: 85 additions & 0 deletions core/src/main/java/feign/querymap/BeanQueryMapEncoder.java
@@ -0,0 +1,85 @@
/**
* Copyright 2012-2018 The Feign Authors
*
* 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.querymap;

import feign.QueryMapEncoder;
import feign.codec.EncodeException;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

/**
* the query map will be generated using java beans accessible getter property as query parameter
* names.
*
* eg: "/uri?name={name}&number={number}"
*
* order of included query parameters not guaranteed, and as usual, if any value is null, it will be
* left out
*/
public class BeanQueryMapEncoder implements QueryMapEncoder {
private final Map<Class<?>, ObjectParamMetadata> classToMetadata =
new HashMap<Class<?>, ObjectParamMetadata>();

@Override
public Map<String, Object> encode(Object object) throws EncodeException {
try {
ObjectParamMetadata metadata = getMetadata(object.getClass());
Map<String, Object> propertyNameToValue = new HashMap<String, Object>();
for (PropertyDescriptor pd : metadata.objectProperties) {
Object value = pd.getReadMethod().invoke(object);
if (value != null && value != object) {
propertyNameToValue.put(pd.getName(), value);
}
}
return propertyNameToValue;
} catch (IllegalAccessException | IntrospectionException | InvocationTargetException e) {
throw new EncodeException("Failure encoding object into query map", e);
}
}

private ObjectParamMetadata getMetadata(Class<?> objectType) throws IntrospectionException {
ObjectParamMetadata metadata = classToMetadata.get(objectType);
if (metadata == null) {
metadata = ObjectParamMetadata.parseObjectType(objectType);
classToMetadata.put(objectType, metadata);
}
return metadata;
}

private static class ObjectParamMetadata {

private final List<PropertyDescriptor> objectProperties;

private ObjectParamMetadata(List<PropertyDescriptor> objectProperties) {
this.objectProperties = Collections.unmodifiableList(objectProperties);
}

private static ObjectParamMetadata parseObjectType(Class<?> type)
throws IntrospectionException {
List<PropertyDescriptor> properties = new ArrayList<PropertyDescriptor>();

for (PropertyDescriptor pd : Introspector.getBeanInfo(type).getPropertyDescriptors()) {
boolean isGetterMethod = pd.getReadMethod() != null && !"class".equals(pd.getName());
if (isGetterMethod) {
properties.add(pd);
}
}

return new ObjectParamMetadata(properties);
}
}
}
79 changes: 79 additions & 0 deletions core/src/main/java/feign/querymap/FieldQueryMapEncoder.java
@@ -0,0 +1,79 @@
/**
* Copyright 2012-2018 The Feign Authors
*
* 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.querymap;

import feign.QueryMapEncoder;
import feign.codec.EncodeException;
import java.lang.reflect.Field;
import java.util.*;

/**
* the query map will be generated using member variable names as query parameter names.
*
* eg: "/uri?name={name}&number={number}"
*
* order of included query parameters not guaranteed, and as usual, if any value is null, it will be
* left out
*/
public class FieldQueryMapEncoder implements QueryMapEncoder {

private final Map<Class<?>, ObjectParamMetadata> classToMetadata =
new HashMap<Class<?>, ObjectParamMetadata>();

@Override
public Map<String, Object> encode(Object object) throws EncodeException {
try {
ObjectParamMetadata metadata = getMetadata(object.getClass());
Map<String, Object> fieldNameToValue = new HashMap<String, Object>();
for (Field field : metadata.objectFields) {
Object value = field.get(object);
if (value != null && value != object) {
fieldNameToValue.put(field.getName(), value);
}
}
return fieldNameToValue;
} catch (IllegalAccessException e) {
throw new EncodeException("Failure encoding object into query map", e);
}
}

private ObjectParamMetadata getMetadata(Class<?> objectType) {
ObjectParamMetadata metadata = classToMetadata.get(objectType);
if (metadata == null) {
metadata = ObjectParamMetadata.parseObjectType(objectType);
classToMetadata.put(objectType, metadata);
}
return metadata;
}

private static class ObjectParamMetadata {

private final List<Field> objectFields;

private ObjectParamMetadata(List<Field> objectFields) {
this.objectFields = Collections.unmodifiableList(objectFields);
}

private static ObjectParamMetadata parseObjectType(Class<?> type) {
List<Field> fields = new ArrayList<Field>();
for (Field field : type.getDeclaredFields()) {
if (!field.isAccessible()) {
field.setAccessible(true);
}
fields.add(field);
}
return new ObjectParamMetadata(fields);
}
}
}
4 changes: 2 additions & 2 deletions core/src/test/java/feign/DefaultQueryMapEncoderTest.java
Expand Up @@ -13,11 +13,11 @@
*/
package feign;

import java.util.HashMap;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

Expand Down

0 comments on commit 99bbcba

Please sign in to comment.