What does CGLib's intercept actually return? - java

I'm trying to create my own lazy load implementation using CGLib, but i've faced some strange behavior that I cannot explain.
Here is what i'm doing.
Proxy instance is being created like follows:
public static <T> T newInstance(Long sourceId, SourceMapper<T> sourceMapper) {
Class<?> proxyTargetType = sourceMapper.getType();
//mapper will use provided sourceId in order to load real object from the DB
Function<Long, T> mapper = sourceMapper.getMapper();
return (T) Enhancer.create(proxyTargetType,
new DynamicProxy<>(sourceId, mapper));
}
Here is the usage of the code above:
Order order = new Order();
try {
//heavy object is being proxied
long customerTariffId = rs.getLong("customer_tariff_id");
order.setCustomerTariff(DynamicProxy
.newInstance(customerTariffId, CUSTOMER_TARIFF_MAPPER));
}
Heavy object should be loaded only if any of its methods gets invoked:
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
T source = this.getSource(); // loads real object using sourceId and mapper
if(source == null) return null;
return method.invoke(source, args);
}
It works perfectly if this.getSource() loads some object.
But here what i'm getting if we assume, that order.getCustomerTariff() should return null (this.getSource() will return null)
LOG.debug("{}", order.getCustomerTariff()); //null (1)
LOG.debug("{}", order.getCustomerTariff() != null); //true (2)
I assume, that for some reason toString() gets invoked at line (2), so i'm getting String null instead of literal null. That's why it is not equal to a literal null in the comparison clause.How do you think, is there any way to return a regular null at line (2) and receive a correct value of false during that check?
EDIT
Class being proxied looks similar to this:
public class CustomerTariff extends DomainEntity {
private Customer customer;
//some other similar fields
private Tariff tariff;
public CustomerTariff() {
}
public CustomerTariff(Customer customer
Tariff tariff) {
this.customer = customer;
this.tariff = tariff;
}
public CustomerTariff(Long id, Customer customer,
Tariff tariff) {
super(id);
this.customer = customer;
this.tariff = tariff;
}
//getters and setters
#Override
public String toString() {
return "CustomerTariff{" +
"customer=" + customer +
", tariff=" + tariff +
"} " + super.toString();
}
}
public abstract class DomainEntity {
private Long id;
public DomainEntity() {}
public DomainEntity(Long id) {
this.id = id;
}
#Override
public String toString() {
return "DomainEntity{" +
"id=" + id +
'}';
}
}

I assume that you are intercepting your toString method from your interceptor and you do not get the interception chain you expect. Specify a method filter that only hits the methods you want to intercept and you should get the expected result.

Related

How to check null for all properties from an object with lambda expression [duplicate]

This would mean that the class was initialized, but the variables were not set.
A sample Class:
public class User {
String id = null;
String name = null;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
The actual class is huge that I prefer not to check if(xyz == null) for each of the variables.
Another non-reflective solution for Java 8, in the line of paxdiabo's answer but without using a series of if's, would be to stream all fields and check for nullness:
return Stream.of(id, name)
.allMatch(Objects::isNull);
This remains quite easy to maintain while avoiding the reflection hammer.
Try something like this:
public boolean checkNull() throws IllegalAccessException {
for (Field f : getClass().getDeclaredFields())
if (f.get(this) != null)
return false;
return true;
}
Although it would probably be better to check each variable if at all feasible.
This can be done fairly easily using a Lombok generated equals and a static EMPTY object:
import lombok.Data;
public class EmptyCheck {
public static void main(String[] args) {
User user1 = new User();
User user2 = new User();
user2.setName("name");
System.out.println(user1.isEmpty()); // prints true
System.out.println(user2.isEmpty()); // prints false
}
#Data
public static class User {
private static final User EMPTY = new User();
private String id;
private String name;
private int age;
public boolean isEmpty() {
return this.equals(EMPTY);
}
}
}
Prerequisites:
Default constructor should not be implemented with custom behavior as that is used to create the EMPTY object
All fields of the class should have an implemented equals (built-in Java types are usually not a problem, in case of custom types you can use Lombok)
Advantages:
No reflection involved
As new fields added to the class, this does not require any maintenance as due to Lombok they will be automatically checked in the equals implementation
Unlike some other answers this works not just for null checks but also for primitive types which have a non-null default value (e.g. if field is int it checks for 0, in case of boolean for false, etc.)
If you want this for unit testing I just use the hasNoNullFieldsOrProperties() method from assertj
assertThat(myObj).hasNoNullFieldsOrProperties();
How about streams?
public boolean checkFieldsIsNull(Object instance, List<String> fieldNames) {
return fieldNames.stream().allMatch(field -> {
try {
return Objects.isNull(instance.getClass().getDeclaredField(field).get(instance));
} catch (IllegalAccessException | NoSuchFieldException e) {
return true;//You can throw RuntimeException if need.
}
});
}
"Best" is such a subjective term :-)
I would just use the method of checking each individual variable. If your class already has a lot of these, the increase in size is not going to be that much if you do something like:
public Boolean anyUnset() {
if ( id == null) return true;
if (name == null) return true;
return false;
}
Provided you keep everything in the same order, code changes (and automated checking with a script if you're paranoid) will be relatively painless.
Alternatively (assuming they're all strings), you could basically put these values into a map of some sort (eg, HashMap) and just keep a list of the key names for that list. That way, you could iterate through the list of keys, checking that the values are set correctly.
I think this is a solution that solves your problem easily: (return true if any of the parameters is not null)
public boolean isUserEmpty(){
boolean isEmpty;
isEmpty = isEmpty = Stream.of(id,
name)
.anyMatch(userParameter -> userParameter != null);
return isEmpty;}
Another solution to the same task is:(you can change it to if(isEmpty==0) checks if all the parameters are null.
public boolean isUserEmpty(){
long isEmpty;
isEmpty = Stream.of(id,
name)
.filter(userParameter -> userParameter != null).count();
return isEmpty > 0
}
The best way in my opinion is Reflection as others have recommended. Here's a sample that evaluates each local field for null. If it finds one that is not null, method will return false.
public class User {
String id = null;
String name = null;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isNull() {
Field fields[] = this.getClass().getDeclaredFields();
for (Field f : fields) {
try {
Object value = f.get(this);
if (value != null) {
return false;
}
}
catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return true;
}
public static void main(String args[]) {
System.out.println(new User().isNull());
}
}
Field[] field = model.getClass().getDeclaredFields();
for(int j=0 ; j<field.length ; j++){
String name = field[j].getName();
name = name.substring(0,1).toUpperCase()+name.substring(1);
String type = field[j].getGenericType().toString();
if(type.equals("class java.lang.String")){
Method m = model.getClass().getMethod("get"+name);
String value = (String) m.invoke(model);
if(value == null){
... something to do...
}
}
Best for me is
Stream.of(getClass().getDeclaredMethods()).allMatch(Objects::isNull);
It can be used in a custom annotation + annotation processor to automagically define a boolean isNull() method on the annotated classes.
Based on Irkwz's answer, but a different approach:
public class SomeClass{
private String field1;
private String field2;
private ComplexField field3;
private String field4;
private Integer field15;
public boolean isNullAllFields() {
return Stream.of(this.getClass().getDeclaredFields()).anyMatch(element -> (element != null));
}
}
And the end of the day u invoke isNullAllFields method to figure out wheter the object fields are empty.
If you want to do the opposite i.e check if some/all members of class are non-non, the check this answer.
In order to make sure that certain members of the class are always non-null, we can use lombok #NonNull annotation on the individual fields of the class.
import lombok.Data;
import lombok.NonNull;
#Data
public class DataClass {
#NonNull
private String data1;
private int data2;
#NonNull
private String data3;
#NonNull
private String data4;
#NonNull
private String data5;
private String data6;
DataClass(String data1,...) {
// constructor
}
}
Easiest way is to convert the class to a map and get its keys and with stream check if any or all key's values are null or not, you can take input from user as well whether they want to check for specific set of keys only!
Below is the code to check whether any of the key's value has null, you can change stream config to all match or any match as per your requirement
Just replace isNullOrEmpty method i have used with proper null or empty check condition for that particular collection
public boolean checkIfAnyFieldIsNull(Object instance, Set<String> fields){
try {
Map<String, Object> instanceMap = new Gson().fromJson(new GsonBuilder().serializeNulls().create().toJson(instance), Map.class);
if(!isNullorEmpty(instanceMap)) {
fields = isNullorEmpty(fields) ? instanceMap.keySet() : fields;
return fields.stream().anyMatch(curField -> isNull(instanceMap.get(curField)));
}else{
return false;
}
}catch (Exception e){
return false;
}
}
}
Try this method once, its works for me!!
private fun checkIfAnyDataIsNull(model: YourModelCass): Boolean {
return Stream.of<Any>(
model.date,
model.merchantName,
model.payment,
).allMatch(Objects::isNull)
}
You can use the simple solution:
if(user.equals(new User()){
//your processing goes here
}

How to copy values of all attributes from one instance of an object to another if they are not null or empty

Given a class Person:
String name;
String surname;
String id;
String address;
I have an object obj1 with the following values:
name="Name"
surname=null
id="ABC123"
address="Here"
Through an api call I get the following json:
{
"name":"John",
"surname":"Doe",
"id":"A1B2C3"
}
which gets mapped into an object obj2like this:
name="John"
surname="Doe"
id="A1B2C3"
address=null
I want to copy all non-null (or empty string) values of obj2 into obj1 so the final result is this:
name="John"
surname="Doe"
id="A1B2C3"
address="Here"
I have two problems.
The first one is that I don't want to have to manually type the get/set call for each attribute of the object.
The second problem, is that I want the method to work for any type of object with no or minimal changes.
At the very least, I need the first problem solved. The second one is optional, but would be great to learn a solution too.
You can use reflection to get all the instance fields and use Field#set to copy over non-null values.
try {
for (Field field : Person.class.getDeclaredFields()) {
if (!Modifier.isStatic(field.getModifiers())) {
field.setAccessible(true);
Object val = field.get(obj2);
if (val != null) {
field.set(obj1, val);
}
}
}
System.out.println(obj1);
} catch (IllegalAccessException e) {
// Handle exception
}
Demo
public static void main(String[] args) {
MyClass obj1 = new MyClass(1,"1");
MyClass obj2 = new MyClass(2,"2");
System.out.println(obj2);
copy(obj1, obj2);
System.out.println(obj2);
}
public static MyClass copy(MyClass obj1, MyClass obj2){
Arrays.stream(obj1.getClass().getDeclaredFields()).forEach(f -> {
try {
f.set(obj2, f.get(obj1));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
});
return obj2;
}
static class MyClass {
int myInt;
String myString;
String emptyString = null;
public MyClass(int myInt, String myString) {
this.myInt = myInt;
this.myString = myString;
}
#Override
public String toString() {
return "MyClass{" +
"myInt=" + myInt +
", myString='" + myString + '\'' +
", emptyString='" + emptyString + '\'' +
'}';
}
}
in case you have private fields you can use
f.setAccessible(true);
but I don't suggest that.
Reading your question, seems like, you have two classes, in two distinct projects, which, exchange information in json.
So, It is easy if you can use Jackson(default in spring)
You could annotate the target class with the annotation of jackson #JsonInclude(JsonInclude.Include.NON_NULL)

GET more than one object from JSON using Mono

Looking at this code:
#Service
public class WebService {
public WebService() {
this.webClient = WebClient.builder()
.baseUrl(URL)
.build();
}
public Mono<Person> searchById(String id) {
return webClient.get().uri("/v3/"+ id).retrieve().bodyToMono(Person.class);
}
}
And this:
WebService ws = new WebService();
Mono<Person> version = ws.searchById("1");
System.out.println(version.toProcessor().block());
This code works well in getting one person object from a JSON, however I wanted a method to return more than one person from a JSON like so:
public Mono<List<Person>> or public Mono<Person[]>
And I can't seem to make it work. I've tried what I found here, but I don't understand what they are doing here:
return Arrays.stream(objects)
.map(object -> mapper.convertValue(object, Reader.class))
.map(Reader::getFavouriteBook)
.collect(Collectors.toList());
And in my case I want to have a list of objects Person on my main function, not just part of the object like in their case, where they use Reader::getFavouriteBook to apparently map a Book.
Converting this to an answer, for better readability.
WebClient can convert the response body into a mono of array. So, instead of performing bodyToMono(Person.class) we can do bodyToMono(Person[].class).
Now the resulting Mono is a Mono<Person[]>. You can then perform map, subscribe or flatMap as you wish (one or many times) to chain actions. Once all the actions are chained, you can then call block to wait till all the actions are executed and get back the result (or an error).
See this documentation to know more about all the supported methods on Mono.
Note - Using block in a reactive application is not recommended.
EDIT
Here is an example on how a Mono<Person> is converted to a Mono<PersonDTO>
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
public class Test {
public static void main(String[] args) {
Person person = new Person();
Mono<Person> personMono = Mono.just(person);
Mono<PersonDTO> personDTOMono = personMono.map(p -> {
PersonDTO dto = new PersonDTO();
dto.setName(p.name);
dto.setVersion(p.version);
return dto;
}).delayElement(Duration.of(1000L, ChronoUnit.MILLIS));
System.out.println("Waiting for 1000 millis");
System.out.println("personDTOMono = " + personDTOMono.block());
}
}
class Person {
int version = 10;
String name = "test";
}
class PersonDTO {
private int version;
private String name;
#Override
public String toString() {
return "PersonDTO{" +
"version=" + version +
", name='" + name + '\'' +
'}';
}
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

How to write generics method in java that accepts any list of two object and returns list?

i have a domain class(DB):
public class PersonDoamin {
private String name;
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
i also have model class:
public class PersonBean extends PersonDoamin {
}
so when i go to DAOImpl class and query for List and transfer this list to List and return to users as i have interface method for List getAllPerson(). so my questions is here when i transfer all data from List. Here i have some utility method that copies from one bean to another like this:
List<PersonDoamin> list = PersonDAO.getAllPersons();
List<PersonBean> pbList = new ArrayList<PersonBean>();
/* this below logic is pretty much in the all DAO impl*/
for(PersonDoamin p : list){
PersonBean pb = new PersonBean();
CopyHelper.copyBean(p, pb);
pbList.add(pb);
}
return pbList;
can we replace the looping and copying and adding to another list and returning part with somekind of generic method which will take any object two list and loop thorugh one and add it to another passed List parameter and return it. something like below which is not perfect right now:
public static <T> List<T> listToArray(List<T> list,List<T> list2) {
for(T element : list){
list2.add(element);
}
return list2;
}
public static void main(String[] args) {
List<PersonDoamin> personList = new ArrayList<PersonDoamin>();
PersonDoamin p = new PersonDoamin();
p.setName("aj");
p.setAge("25");
personList.add(p);
List<PersonBean> personBeansToReturn = new ArrayList<PersonBean>();
Test.listToArray(personList , personBeansToReturn );
}
A bit off topic, your design seems a bit weird that you have "Domain" class and "Bean" class and have "Bean" extends "Domain"...
Anyway, come back to your question, what you are trying to do is:
You have a List<Domain>
You want to transform each Domain in the List into a Bean (by use of some util method)
Put the resulting Beans into a list and return
Let's go through it step by step.
(by the way, the listToArray method you wrote does not align with your original loop as it does not do the transformation (point 2). I guess it is typo?)
(all psuedo code as I don't have environment on hand to make it compile. Concept should be correct I guess)
Step 1: Util method for Person
One biggest problem of your original util method is that, it is illegal to put a Parent object instance to a List of Child (it should be easy to figure why by yourself).
The util method should look like this:
List<PersonBean> toBeans(List<PersonDomain> domains) {
List<PersonBean> beans = new ArrayList<>(domains.size());
for (PersonDomain domain: domains) {
PersonBean bean = new PersonBean();
CopyHelper.copyBean(domain, bean);
beans.add(bean);
}
return beans;
}
Step 2: Make it generic
The problem above is that it only works for Person. If you want to make it generic, you will also need to provide the function to transform Domain to Bean:
(Assume you are using Java8, should be trivial to make your own interface if you are using older version)
<D,B> List<B> toBeans(List<D> domains, Function<B,D> mapper) {
List<PersonBean> beans = new ArrayList<>(domains.size());
for (PersonDomain domain: domains) {
beans.add(mapper.apply(domain));
}
return beans;
}
so that you can use it by:
return toBeans(personDomains, (domain) -> {
PersonBean bean = new PersonBean();
CopyHelper.copyBean(domain, bean);
return bean;
});
(You may consider wrap the function if in most case you are going to use the CopyHelper way)
<D,B> List<B> toBeansByBeanCopy(List<D> domains, Class<B> beanClass) {
return toBeans(domains, (domain)-> {
B bean = beanClass.newInstance();
CopyHelper.copyBean(domain, bean);
return bean;
});
}
so that you can use it as
return toBeansByBeanCopy(personDomains, PersonBean.class);
Step 3: Java has done it for you
Actually what you are trying to do above, it is already provided by Java in Java 8. You can simply do:
return personDomains.stream()
.map(d -> {
PersonBean bean = new PersonBean();
CopyHelper.copyBean(domain, bean);
return bean;
})
.collect(Collectors.toList());
You may write a little method to use in the lambda expression if it is the standard way.
return personDomains.stream()
.map(BeanMapper.mapper(PersonBean.class))
.collect(Collectors.toList());
(Leave the implementation as your exercise)
If you're looking for a way to call new on a generic type, you can, sort of. You have to use reflection and call newInstance on the Class object. I don't know if this is going to be feasible for you.
Also, I don't see anyway of realistically implementing your bean copy method without using some heavy reflection as well. In the example below I faked by just casting to the required classes.
public class GenericCopyTest
{
public static void main( String[] args ) throws Exception
{
List<PersonDoamin> personList = new ArrayList<PersonDoamin>();
PersonDoamin p = new PersonDoamin();
p.setName( "aj" );
p.setAge( "25" );
personList.add( p );
List<PersonBean> personBeansToReturn = new ArrayList<PersonBean>();
copyAndDowncast( personList, personBeansToReturn, PersonBean.class );
System.out.println( personBeansToReturn );
}
public static <T,U extends T> List<U> copyAndDowncast( List<T> from,
List<U> to, Class<U> type )
throws InstantiationException, IllegalAccessException
{
for( T element : from ) {
U nu = type.newInstance();
copyBean( element, nu );
to.add( nu );
}
return to;
}
private static <X,Y extends X> void copyBean( X from, Y nu ) {
((PersonBean)nu).setName( ((PersonDoamin)from).getName() );
((PersonBean)nu).setAge( ((PersonDoamin)from).getAge() );
}
}
class PersonDoamin {
private String name;
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
#Override
public String toString()
{
return "PersonDoamin{" + "name=" + name + ", age=" + age + '}';
}
}
class PersonBean extends PersonDoamin {
#Override
public String toString()
{
return "PersonBean{" + getName() + ',' + getAge()+ '}';
}
}
Output:
run:
[PersonBean{aj,25}]
BUILD SUCCESSFUL (total time: 0 seconds)
Why not just use addAll() for this? It does what you're trying to do, and it's already part of the system library.
Remember you can add a PersonBean to a PersonDomain list, but not the other way around.
public class GenericCopyTest
{
public static void main( String[] args ) {
List<PersonDoamin> personList = new ArrayList<PersonDoamin>();
List<PersonBean> personBeansToReturn = new ArrayList<PersonBean>();
personList.addAll( personBeansToReturn );
personBeansToReturn.addAll( personList ); // <-- FAILS
// No suitable method found
}
}
class PersonDoamin {}
class PersonBean extends PersonDoamin {}
If you want to put more than one bean class in the same list,
how about creating the list with parent class PersonDoamin , and then, you can store both PersonDoamin and PersonBean classes.
public static void main(String[] args) {
List<PersonDoamin> personList = new ArrayList<PersonDoamin>();
PersonDoamin p = new PersonDoamin();
p.setName("aj");
p.setAge("25");
personList.add(p);
// Changed here. PersonBean => PersonDoamin
List<PersonDoamin> personBeansToReturn = new ArrayList<PersonDoamin>();
Test.listToArray(personList, personBeansToReturn);
// also you can insert PersonBean into the list
personBeansToReturn.add(new PersonBean());
}

Multiple #QueryParam keys for a single value in Jersey

Is it possible to allow multiple #QueryParam keys for a single object/variable in Jersey?
Actual:
#POST
public Something getThings(#QueryParam("customer-number") Integer n) {
...
}
so, if I add ?customer-number=3 after the URL it works.
Expected:
I want to get the behavior above if I add any of the following values:
?customer-number=3
?customerNumber=3
?customerNo=3
Obs:
The QueryParam annotation looks like:
...
public #interface QueryParam {
String value();
}
so, it cannot accept multiple String values (like #Produces).
The approach below allows the user to use multiple keys having the same meaning at the same time (and I want to have an "OR" condition between them):
#POST
public Something getThings(#QueryParam("customer-number") Integer n1,
#QueryParam("customerNumber") Integer n2,
#QueryParam("customerNo") Integer n3) {
...
}
Something like this doesn't work:
#POST
public Something getThings(#QueryParam("customer-number|customerNumber|customerNo") Integer n) {
...
}
How can I do this?
Details:
Jersey 2.22.1
Java 8
To be honest: this is not how webservices are supposed to be designed. You lay down a strict contract that both client and server follow; you define one parameter and that's it.
But of course it would be a perfect world where you have the freedom to dictate what is going to happen. So if you must allow three parameters in, then you'll have to make that the contract. This is one way following approach #2 which I have to provide without being able to test it for goofs:
public Something getThings(#QueryParam("customer-number") Integer n1,
#QueryParam("customerNumber") Integer n2,
#QueryParam("customerNo") Integer n3) throws YourFailureException {
Integer customerNumber = getNonNullValue("Customer number", n1, n2, n3);
// things with stuff
}
private static Integer getNonNullValue(String label, Integer... params) throws YourFailureException {
Integer value = null;
for(Integer choice : params){
if(choice != null){
if(value != null){
// this means there are at least two query parameters passed with a value
throw new YourFailureException("Ambiguous " + label + " parameters");
}
value = choice;
}
}
if(value == null){
throw new YourFailureException("Missing " + label + " parameter");
}
return value;
}
So basically reject any call that does not pass specifically one of the parameters, and let an exception mapper translate the exception you throw into a HTTP response code in the 4xx range of course.
(I made the getNonNullValue() method static is it strikes me as a reusable utility function).
Maybe the simplest and easiest way would be to use a custom #BeanParam:
First define the custom bean merging all the query parameters as:
class MergedIntegerValue {
private final Integer value;
public MergedIntegerValue(
#QueryParam("n1") Integer n1,
#QueryParam("n2") Integer n2,
#QueryParam("n3") Integer n3) {
this.value = n1 != null ? n1
: n2 != null ? n2
: n3 != null ? n3
: null;
// Throw an exception if value == null ?
}
public Integer getValue() {
return value;
}
}
and then use it with #BeanParam in your resource method:
public Something getThings(
#BeanParam MergedIntegerValue n) {
// Use n.getValue() ...
}
Reference: https://jersey.java.net/documentation/latest/user-guide.html#d0e2403
You can create a custom annotation. I won't go in too much about how to do it, you can see this post, or this post. Basically it relies on a different infrastructure than the usual dependency injection with Jersey. You can see this package from the Jersey project. This is where all the injection providers live that handle the #XxxParam injections. If you examine the source code, you will see the the implementations are fairly the same. The two links I provided above follow the same pattern, as well as the code below.
What I did was created a custom annotation
#Target({ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public #interface VaryingParam {
String value();
#SuppressWarnings("AnnotationAsSuperInterface")
public static class Factory
extends AnnotationLiteral<VaryingParam> implements VaryingParam {
private final String value;
public static VaryingParam create(final String newValue) {
return new Factory(newValue);
}
public Factory(String newValue) {
this.value = newValue;
}
#Override
public String value() {
return this.value;
}
}
}
It may seem odd that I have a factory to create it, but this was required for the implementation of the below code, where I split the value of the String, and end up creating a new annotation instance for each split value.
Here is the ValueFactoryProvider (which, if you've read either of the above articles, you will see that is required for custom method parameter injection). It a large class, only because I put all the required classes into a single class, following the pattern you see in the Jersey project.
public class VaryingParamValueFactoryProvider extends AbstractValueFactoryProvider {
#Inject
public VaryingParamValueFactoryProvider(
final MultivaluedParameterExtractorProvider mpep,
final ServiceLocator locator) {
super(mpep, locator, Parameter.Source.UNKNOWN);
}
#Override
protected Factory<?> createValueFactory(final Parameter parameter) {
VaryingParam annotation = parameter.getAnnotation(VaryingParam.class);
if (annotation == null) {
return null;
}
String value = annotation.value();
if (value == null || value.length() == 0) {
return null;
}
String[] variations = value.split("\\s*\\|\\s*");
return new VaryingParamFactory(variations, parameter);
}
private static Parameter cloneParameter(final Parameter original, final String value) {
Annotation[] annotations = changeVaryingParam(original.getAnnotations(), value);
Parameter clone = Parameter.create(
original.getRawType(),
original.getRawType(),
true,
original.getRawType(),
original.getRawType(),
annotations);
return clone;
}
private static Annotation[] changeVaryingParam(final Annotation[] annos, final String value) {
for (int i = 0; i < annos.length; i++) {
if (annos[i] instanceof VaryingParam) {
annos[i] = VaryingParam.Factory.create(value);
break;
}
}
return annos;
}
private class VaryingParamFactory extends AbstractContainerRequestValueFactory<Object> {
private final String[] variations;
private final Parameter parameter;
private final boolean decode;
private final Class<?> paramType;
private final boolean isList;
private final boolean isSet;
VaryingParamFactory(final String[] variations, final Parameter parameter) {
this.variations = variations;
this.parameter = parameter;
this.decode = !parameter.isEncoded();
this.paramType = parameter.getRawType();
this.isList = paramType == List.class;
this.isSet = paramType == Set.class;
}
#Override
public Object provide() {
MultivaluedParameterExtractor<?> e = null;
try {
Object value = null;
MultivaluedMap<String, String> params
= getContainerRequest().getUriInfo().getQueryParameters(decode);
for (String variant : variations) {
e = get(cloneParameter(parameter, variant));
if (e == null) {
return null;
}
if (isList) {
List list = (List<?>) e.extract(params);
if (value == null) {
value = new ArrayList();
}
((List<?>) value).addAll(list);
} else if (isSet) {
Set set = (Set<?>) e.extract(params);
if (value == null) {
value = new HashSet();
}
((Set<?>) value).addAll(set);
} else {
value = e.extract(params);
if (value != null) {
return value;
}
}
}
return value;
} catch (ExtractorException ex) {
if (e == null) {
throw new ParamException.QueryParamException(ex.getCause(),
parameter.getSourceName(), parameter.getDefaultValue());
} else {
throw new ParamException.QueryParamException(ex.getCause(),
e.getName(), e.getDefaultValueString());
}
}
}
}
private static class Resolver extends ParamInjectionResolver<VaryingParam> {
public Resolver() {
super(VaryingParamValueFactoryProvider.class);
}
}
public static class Binder extends AbstractBinder {
#Override
protected void configure() {
bind(VaryingParamValueFactoryProvider.class)
.to(ValueFactoryProvider.class)
.in(Singleton.class);
bind(VaryingParamValueFactoryProvider.Resolver.class)
.to(new TypeLiteral<InjectionResolver<VaryingParam>>() {
})
.in(Singleton.class);
}
}
}
You will need to register this class' Binder (bottom of class) with Jersey to use it.
What differentiates this class from Jersey QueryParamValueFactoryProvider is that instead of just processing a single String value of the annotation, it splits the value, and tries to extract the values from the query param map. The first value found will be returned. If the parameter is a List or Set, it just continues to keep looking up all the options, and adding them to the list.
For the most part this keeps all the functionality you would expect from an #XxxParam annotation. The only thing that was difficult to implement (so I left out supporting this use case), is multiple parameters, e.g.
#GET
#Path("multiple")
public String getMultipleVariants(#VaryingParam("param-1|param-2|param-3") String value1,
#VaryingParam("param-1|param-2|param-3") String value2) {
return value1 + ":" + value2;
}
I actually don't think it should be that hard to implement, if you really need it, it's just a matter of creating a new MultivaluedMap, removing a value if it is found. This would be implemented in the provide() method of the VaryingParamFactory above. If you need this use case, you could just use a List or Set instead.
See this GitHub Gist (it's rather long) for a complete test case, using Jersey Test Framework. You can see all the use cases I tested in the QueryTestResource, and where I register the Binder with the ResourceConfig in the test configure() method.

Categories