Java Custom annotation with array element - java

I have a custom annotation as follows.
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE, ElementType.METHOD })
#Documented
#Conditional(OnApiVersionConditional.class)
public #interface ConditionalOnApiVersion {
int[] value() default 5;
String property();
}
OnApiVersionConditional is,
public class OnApiVersionConditional implements Condition {
#Override
public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
final MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(ConditionalOnApiVersion.class.getName());
attributes.get("value");
final String inputVersion = context.getEnvironment().getProperty("userInputVersion");
}
In my Bean annotation,
#Bean
#ConditionalOnApiVersion(value = {6, 7}, property = "userInputVersion")
There are beans with single version matching also, like
#Bean
#ConditionalOnApiVersion(value = 8, property = "userInputVersion")
I would like to validate the userInput version from the property file to the available Beans supported versions. Not sure, how can i get the value, iterate and compare with userInoutVersion. The value could be 8 or {6,7} as an int array. Not sure, how can i iterate the value to check if any of the value is matching with the input version.
final List apiVersions = attributes.get("value").stream().collect(Collectors.toList());
Question:
How to iterate attributes.get("value") and compare with userInputVersion?
attributes.get("value") returns a List of Object.
I tried the below code,
final List<Object> apiVersions = attributes.get("value").stream().collect(Collectors.toList());
boolean result = apiVersions.stream().anyMatch(version -> (int)version == Integer.parseInt(userInputVersion));
But getting the below error int eh 2nd line anyMatch,
java.lang.ClassCastException: [I cannot be cast to java.lang.Integer
Thanks

You have defined a good custom annotation which uses Spring's #Conditional and this process will only create beans if the property named 'property' is present in the array value.
The annotation defines these two variables as follows:
value: int[] (One or more supported versions)
property: String (The name of the property to compare to)
When you use the metadata to retrieve these values, Spring returns them as Objects. Casting these objects into the types you defined them as is a key step. There is no need for streams as iterating an int[] array is trivial.
I tested various forms of your annotation (value = 5, value = {6,7}, etc) and found the following code to work well (#Conditional beans were only created if the version was correct).
public class OnApiVersionConditional implements Condition {
#Override
public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
final MultiValueMap<String, Object> attributes = metadata
.getAllAnnotationAttributes(ConditionalOnApiVersion.class.getName());
// 1. Retrieve the property name from the annotation (i.e. userInputVersion)
List<Object> propertyObject = attributes.get("property");
// 2. Cast it to a String
String propertyName = (String)propertyObject.get(0);
// 3. Retrieve the value
List<Object> list = attributes.get("value");
// 4. Cast it to int[]
int[] values = (int[])list.get(0);
// 5. Look up the actual version (based on the property name in the annotation)
final String inputVersion = context.getEnvironment().getProperty(propertyName);
// Assume it is an integer?
int version = Integer.valueOf(inputVersion).intValue();
// 6. Scan the supported version; look to match against actual version
for (int i : values)
if (i == version)
return true;
// The version is not supported
return false;
}
}
This method could be improved by validating both property and value; returning false if either of these contains bad/empty values, etc.

Related

MapStruct map fields to target only when target's fields are null

I am trying to map this object
public class Source {
private String value1;
private String value2;
private String value3;
}
Into this object
public class Target {
private String targetValue1;
private String targetValue2;
private String targetValue3;
}
This is the Mapper definition.
#Mapper
public interface SourceMapper {
void toTarget(Source source, #MappingTarget Target target);
}
What I am trying to achieve is only map fields in source into target only when the fields in target are null. For example, source.value1 only maps to target.targetValue1 when target.targetValue1 is null. If it is not null, the mapping for that field is ignored.
Is it possible with MapStruct without having to write custom code?
Edit
I changed the field names of Target to make it clear that the names of the Target may/may not match the names of the fields in Source.
I don't think that can be done with mapstruct. If you still want to use mapstruct, you can ignore the target variables that could be null with #Mapping (target =" propName ", ignore = true) and decide yourself with a #AfterMapping method when you set your target variables.
You can achieve that by doing the following trick:
first, you need to know that you can control the mapping behavior on 'source' nulls fields
using Mapping.nullValuePropertyMappingStrategy()
So the following should do the work:
Target target = new Target(); // the actual target, first grade values
Source source = new Source(); // the actual source, secund grade values
Target result = new Target(); // the result
SourceMapper.toTarget(source, result); // now you have secund grade value
SourceMapper.toTarget(target, result); /* now you overide the secund grade value only
if the first grade isn't null */
#Mapper
public interface SourceMapper {
#Mapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
void toTarget(Target source, #MappingTarget Target target);
void toTarget(Source source, #MappingTarget Target target);
}

How to inject a map of key-value pairs into an Object with just Core Java?

How can I inject a map into an object using only Core Java?
I have a map with 4 key-value(String, Object) pairs and a class with 3 fields, I want to invoke the setter method based on the key name and set them.
{
"variableA": "A",
"variableB": true,
"variableC": 1,
"variableD": "DONT USE"
}
public Class Example {
public void setVaraibleA(String variableA);
public void setVaraibleB(Boolean variableB);
public void setVaraibleC(Integer variableC);
}
Example example = new Example();
// Do something to map it
assert(example.getVariableA.equals("A"));
assert(example.getVariableB.equals(true));
assert(example.getVariableC.equals(1));
you can use Java Reflection to get a method (given its name) and invoke it with a given parameter.
Example example = new Example();
Method method = Example.class.getMethod("setVariableA", String.class);
method.invoke(example, "parameter-value1");
Alternatively to #BeppeC's answer, if you can't easily determine the type of the object that you're injecting at runtime, and assuming that you don't have duplicate property names, I would use Class's getMethods() method and Method's getName() method.
Basically, I would write some code like the following:
Method[] exampleMethods = Example.class.getMethods();
Map<String, Method> setterMethodsByPropertyName = new HashMap<>(exampleMethods.length);
for (Method exampleMethod : exampleMethods) {
String methodName = exampleMethod.getName();
if (!methodName.startsWith("set")) {
continue;
}
// substring starting right after "set"
String variableName = methodName.substring(3);
// use lowercase here because:
// 1. JSON property starts with lower case but setter name after "set" starts with upper case
// 2. property names should all be different so no name conflict (assumption)
String lcVariableNmae = variableName.toLowerCase();
setterMethodsByPropertyName.put(lcVariableName, exampleMethod);
}
// later in the code, and assuming that your JSON map is accessible via a Java Map
for (Map.Entry<String, ?> entry : jsonMap.entrySet()) {
String propertyName = entry.getKey();
String lcPropertyName = propertyName.toLowerCase();
if(!setterMethodsByPropertyName.containsKey(lcPropertyName)) {
// do something for this error condition where the property setter can't be found
}
Object propertyValue = entry.getValue();
Method setter = setterMethodsByPropertyName.get(lcPropertyName);
setter.invoke(myExampleInstance, propertyValue);
}

Descending sorting LDAP with Spring Boot

Is it possible to sort records from LDAP directory with SortControlDirContextProcessor descending, not ascending?
I based on this:
SpringLdap - LdapTemplateSortedSearchITest
public void testSearch_SortControl_ConvenienceMethod() {
SortControlDirContextProcessor requestControl;
// Prepare for first search
requestControl = new SortControlDirContextProcessor("cn");
tested.search(BASE, FILTER_STRING, searchControls, callbackHandler,
requestControl);
int resultCode = requestControl.getResultCode();
boolean sorted = requestControl.isSorted();
assertThat("Search result should have been sorted: " + resultCode, sorted).isTrue();
List list = callbackHandler.getList();
assertSortedList(list);
}
It works but ascending. How to set descending sort?
I believe it could be helpful:
public SortKey(String attrID,
boolean ascendingOrder,
String matchingRuleID)
Creates a sort key for an attribute. Entries will be sorted according to the specified attribute in the specified sort order and using the specified matching rule, if supplied.
Parameters:
attrID - The non-null ID of the attribute to be used as a sort key.
ascendingOrder - If true then entries are arranged in ascending order. Otherwise there are arranged in descending order.
matchingRuleID - The possibly null ID of the matching rule to use to order the attribute values. If not specified then the ordering matching rule defined for the sort key attribute is used.
It is from docs: Java doc
Regarding your question: Example code from java doc
And I found one more alternative called unboundid ldap sdk
link
The solution for me was to implement a custom DirContextProcessor that allowed me to sort on multiple attributes with the desired direction (ascending/descending) by making use of the overload method of the SortControl class that takes an array of SortKeys objects as a parameter.
The implementation must extend the AbstractFallbackRequestAndResponseControlDirContextProcessor and override the createRequestControl method.
The superclass AbstractFallbackRequestAndResponseControlDirContextProcessor will take care of the actual creation of the control. It only expects 2 pieces of information from the subclass.
The fully qualified class name of the control to instantiate
The types and values of the constructor parameters
The fully qualified class name is provided in the subclass property defaultRequestControl, and the types and values of the constructor parameters are provided in the subclass method createRequestControl.
The information of the sorting direction for any particular attribute is provided in the ascendingOrder property of the SortKey object.
public class SortMultipleControlDirContextProcessor extends AbstractFallbackRequestAndResponseControlDirContextProcessor{
private SortKey[] sortKeys;
private boolean sorted;
private int resultCode;
public SortMultipleControlDirContextProcessor(SortKey ... sortKeys){
if(ArrayUtils.isEmpty(sortKeys)){
throw new IllegalArgumentException("At least one key to sort on must be provided.");
}
this.sortKeys = sortKeys;
this.sorted = false;
this.resultCode = -1;
this.defaultRequestControl = "javax.naming.ldap.SortControl";
this.defaultResponseControl = "javax.naming.ldap.SortResponseControl";
this.fallbackRequestControl = "com.sun.jndi.ldap.ctl.SortControl";
this.fallbackResponseControl = "com.sun.jndi.ldap.ctl.SortResponseControl";
loadControlClasses();
}
#Override
public Control createRequestControl(){
return super.createRequestControl(new Class[]{SortKey[].class, boolean.class}, new Object[]{sortKeys, critical});
}
#Override
protected void handleResponse(Object control) {
Boolean result = (Boolean) invokeMethod("isSorted", responseControlClass, control);
this.sorted = result;
Integer code = (Integer) invokeMethod("getResultCode", responseControlClass, control);
this.resultCode = code;
}
public SortKey[] getSortKeys(){
return sortKeys;
}
public boolean isSorted(){
return sorted;
}
public int getResultCode(){
return resultCode;
}
}
After the implementation, you can use the class to sort the results on multiple attributes in any desired direction:
// SortKey for sorting results on the cn attribute in descending order
SortKey cnSortKey = new SortKey("cn", false, null);
// Instantiate the control
SortMultipleControlDirContextProcessor myCustomControl = new SortMultipleControlDirContextProcessor(cnSortKey);
// Perform the search with the control
List<User> users = ldapTemplate.search("", orFilter.encode(), searchControls, new UserAttributesMapper(), myCustomControl);

Hibernate Search 5.5.3 - Sort by collection size no longer working

I've been trying to migrate from Hibernate Search 5.5.2 to 5.5.3, and I've run into an issue with one of my sort fields. This is the code that was working with 5.5.2 (or maybe it wasn't working, and just wasn't throwing errors?)
public class CollectionCountBridge implements MetadataProvidingFieldBridge {
#Override
public void configureFieldMetadata(String name, FieldMetadataBuilder builder) {
builder.field(name, FieldType.INTEGER).sortable(true);
}
#Override
public void set(String name, Object object, Document document, LuceneOptions luceneOptions) {
if (object == null || (!(object instanceof Collection))) {
return;
}
Collection<?> coll = (Collection<?>) object;
int size = coll.size();
IntField field = new IntField(name, size, (luceneOptions.getStore() != Store.NO) ? Field.Store.YES : Field.Store.NO);
document.add(field);
}
}
...
#Field(analyze = Analyze.NO, norms = Norms.YES, index = Index.YES)
#FieldBridge(impl = CollectionCountBridge.class)
#IndexedEmbedded
#OneToMany
public Set<MyCollection> getMyCollection() {
return myCollection;
}
The code essentially stores the size of the collection as a sortable field. This was based on the documentation which suggested if I need to define a sortable field via a bridge, then I have to implement MetadataProvidingFieldBridge to mark it sortable. The documentation however shows only an example for a string field, whereas I need to use a numeric field. http://docs.jboss.org/hibernate/search/5.5/reference/en-US/html_single/#sortablefield-annotation
So following the upgrade to 5.5.3 I started getting errors like:
org.hibernate.search.exception.SearchException: HSEARCH000307: Sort type INT is not compatible with string type of field 'myCollection'
I've tried adding the field to the document in a variety of ways, and nothing seems to work. Some things I've tried:
luceneOptions.addNumericFieldToDocument(name, size, document);
document.add(new SortedNumericDocValuesField(name, size));
//this throws an error on index
java.lang.IllegalArgumentException: cannot change DocValues type from SORTED_NUMERIC to NUMERIC for field "myCollection"
public class CollectionCountBridge extends NumberBridge
So, my question is, what is the correct way to add a sortable numeric field to the index, via a bridge, as of 5.5.3?
In fact, it's not a bug.
You have to add a corresponding NumericDocValuesField to your document to enable sorting. We will improve that in the future but for now, that's what you have to do.
Additionally, I wouldn't recommend you to add the field with the same name as the default one, you'd better index the collection size with another field name.
Your FieldBridge should look like this:
public class CollectionCountBridge implements MetadataProvidingFieldBridge {
private static final String COUNT_SUFFIX = "_count";
#Override
public void configureFieldMetadata(String name, FieldMetadataBuilder builder) {
builder.field(name + COUNT_SUFFIX, FieldType.INTEGER).sortable(true);
}
#Override
public void set(String name, Object object, Document document, LuceneOptions luceneOptions) {
if (object == null || (!(object instanceof Collection))) {
return;
}
Collection<?> coll = (Collection<?>) object;
int size = coll.size();
luceneOptions.addNumericFieldToDocument(name + COUNT_SUFFIX, size, document);
document.add(new NumericDocValuesField(name + COUNT_SUFFIX, size.longValue()));
}
}
And when sorting, use a new SortField( "myCollection_count", SortField.Type.LONG ) ).
It seems you've hit a bug here. For a custom bridge as yours we are failing to properly detect the numeric encoding type. I've filed HSEARCH-2292 for this.
As a work-around, you may create a transient property in your entity which exposes the collection size. To this property, you add #Field and #SortableField which should add the required fields to the index, using the correct types.

Java Jackson receive key with null value [duplicate]

What happens if I annotate a constructor parameter using #JsonProperty but the Json doesn't specify that property. What value does the constructor get?
How do I differentiate between a property having a null value versus a property that is not present in the JSON?
Summarizing excellent answers by Programmer Bruce and StaxMan:
Missing properties referenced by the constructor are assigned a default value as defined by Java.
You can use setter methods to differentiate between properties that are implicitly or explicitly set. Setter methods are only invoked for properties with explicit values. Setter methods can keep track of whether a property was explicitly set using a boolean flag (e.g. isValueSet).
What happens if I annotate a constructor parameter using #JsonProperty but the Json doesn't specify that property. What value does the constructor get?
For questions such as this, I like to just write a sample program and see what happens.
Following is such a sample program.
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.ObjectMapper;
public class JacksonFoo
{
public static void main(String[] args) throws Exception
{
ObjectMapper mapper = new ObjectMapper();
// {"name":"Fred","id":42}
String jsonInput1 = "{\"name\":\"Fred\",\"id\":42}";
Bar bar1 = mapper.readValue(jsonInput1, Bar.class);
System.out.println(bar1);
// output:
// Bar: name=Fred, id=42
// {"name":"James"}
String jsonInput2 = "{\"name\":\"James\"}";
Bar bar2 = mapper.readValue(jsonInput2, Bar.class);
System.out.println(bar2);
// output:
// Bar: name=James, id=0
// {"id":7}
String jsonInput3 = "{\"id\":7}";
Bar bar3 = mapper.readValue(jsonInput3, Bar.class);
System.out.println(bar3);
// output:
// Bar: name=null, id=7
}
}
class Bar
{
private String name = "BLANK";
private int id = -1;
Bar(#JsonProperty("name") String n, #JsonProperty("id") int i)
{
name = n;
id = i;
}
#Override
public String toString()
{
return String.format("Bar: name=%s, id=%d", name, id);
}
}
The result is that the constructor is passed the default value for the data type.
How do I differentiate between a property having a null value versus a property that is not present in the JSON?
One simple approach would be to check for a default value post deserialization processing, since if the element were present in the JSON but had a null value, then the null value would be used to replace any default value given the corresponding Java field. For example:
import org.codehaus.jackson.annotate.JsonAutoDetect.Visibility;
import org.codehaus.jackson.annotate.JsonMethod;
import org.codehaus.jackson.map.ObjectMapper;
public class JacksonFooToo
{
public static void main(String[] args) throws Exception
{
ObjectMapper mapper = new ObjectMapper().setVisibility(JsonMethod.FIELD, Visibility.ANY);
// {"name":null,"id":99}
String jsonInput1 = "{\"name\":null,\"id\":99}";
BarToo barToo1 = mapper.readValue(jsonInput1, BarToo.class);
System.out.println(barToo1);
// output:
// BarToo: name=null, id=99
// {"id":99}
String jsonInput2 = "{\"id\":99}";
BarToo barToo2 = mapper.readValue(jsonInput2, BarToo.class);
System.out.println(barToo2);
// output:
// BarToo: name=BLANK, id=99
// Interrogate barToo1 and barToo2 for
// the current value of the name field.
// If it's null, then it was null in the JSON.
// If it's BLANK, then it was missing in the JSON.
}
}
class BarToo
{
String name = "BLANK";
int id = -1;
#Override
public String toString()
{
return String.format("BarToo: name=%s, id=%d", name, id);
}
}
Another approach would be to implement a custom deserializer that checks for the required JSON elements. And yet another approach would be to log an enhancement request with the Jackson project at http://jira.codehaus.org/browse/JACKSON
In addition to constructor behavior explained in #Programmer_Bruce's answer, one way to differentiate between null value and missing value is to define a setter: setter is only called with explicit null value.
Custom setter can then set a private boolean flag ("isValueSet" or whatever) if you want to keep track of values set.
Setters have precedence over fields, in case both field and setter exist, so you can "override" behavior this way as well.
I'm thinking of using something in the style of an Option class, where a Nothing object would tell me if there is such a value or not. Has anyone done something like this with Jackson (in Java, not Scala, et al)?
(My answer might be useful to some people finding this thread via google, even if it doesn't answer OPs question)
If you are dealing with primitive types which are omittable, and you do not want to use a setter like described in the other answers (for example if you want your field to be final), you can use box objects:
public class Foo {
private final int number;
public Foo(#JsonProperty Integer number) {
if (number == null) {
this.number = 42; // some default value
} else {
this.number = number;
}
}
}
this doesn't work if the JSON actually contains null, but it can be sufficient if you know it will only contain primitives or be absent
another option is to validate the object after deserialization either manually or via frameworks such java bean validation or, if you are using spring, the spring validation support.

Categories

Resources