Spring boot 1.5.8 with jakson 2.10.0 object mapping issue - spring-boot

i have upgraded jakson api on spring boot 1.5.8 from jakson 2.8.0 to 2.10.0, since then mapping of object is behaving different.
when i am passing request body on springboot controller having propertyname cityCode
sample json
{
cityCode:DEL
}
when adding jsonproperty mapping works fine
#JsonProperty("cityCode")
private String cityCode;
but when i don't add #JsonProperty annotation
it looks for CityCode instead.
since json passed on request is
{
cityCode:DEL
}
it assing
object{cityCode=null}
please let me know if there is any property which i need to add on spring boot
because in most of my scenario i don't want to add #JsonProperty annotation to class fields
EDIT:
I enable log.level to trace i saw some message related to jackson
POJOPropertyBuilder - Unable to instantiate jackson 2.6 object. Using higher version of jackson.
EDIT2:
Adding Sample Model Class
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.util.List;
#JsonIgnoreProperties(ignoreUnknown = true)
#ApiModel(value = "CityRequest")
public class CityRequest implements Serializable {
private static final long serialVersionUID = 1L;
#ApiModelProperty
private String cityCode;
#ApiModelProperty
private String cityName;
#ApiModelProperty
private String area;
#ApiModelProperty
private List<String> areas;
public String getCityCode() {
return cityCode;
}
public void setCityCode(String cityCode) {
this.cityCode = cityCode;
}
public String getCityName() {
return cityName;
}
public void setCityName(String cityName) {
this.cityName = cityName;
}
public String getArea() {
return area;
}
public void setArea(String area) {
this.area = area;
}
public List<String> getAreas() {
return areas;
}
public void setAreas(List<String> areas) {
this.areas = areas;
}
#Override
public String toString() {
return "CityRequest{" +
"cityCode='" + cityCode + '\'' +
", cityName='" + cityName + '\'' +
", area='" + area + '\'' +
", areas=" + areas +
'}';
}
}
on the controller method is being passed as
#ApiParam(value = "This field specifies the list of requests", required = true)
#Valid #RequestBody(required = true) CityRequest cityRequest

I haven't tried but maybe you can try changing the naming strategy to lowerCamelCase:
#JsonNaming(PropertyNamingStrategy.lowerCamelCase.class)
public class City {
private String cityCode;
}

You might want to give this a try as an app property to configure Jackson globally.
spring.jackson.property-naming-strategy=LOWER_CAMEL_CASE

I have used one java api on spring-boot application where object mapper naming strategy is being set to upper_camel_case.
since that class imported on App.java(#SpringBootApplication) upper_camel_case strategy being applied globally.
to fix this globally imposed upper_camel_case strategy i have override the object mapper configuration on App.java and removed
objectMapper.setPropertyNamingStrategy(new UpperCamelCaseStrategy());
now i am able to use default naming strategy on application.

Related

Is Spring #Component annotation used correctly?

The purpose of this question is to find out if the codes are written with the right approach. Let's do CRUD operations on categories and posts in the blog website project. To keep the question short, I shared just create and update side.
(Technologies used in the project: spring-boot, mongodb)
Let's start to model Category:
#Document("category")
public class Category{
#Id
private String id;
#Indexed(unique = true, background = true)
private String name;
#Indexed(unique = true, background = true)
private String slug;
// getter and setter
Abstract BaseController class and IController Interface is created for fundamental level save, delete and update operations. I shared below controller side:
public interface IController<T>{
#PostMapping("/save")
ResponseEntity<BlogResponse> save(T object);
#GetMapping(value = "/find-all")
ResponseEntity<BlogResponse> findAll();
#GetMapping(value = "/delete-all")
ResponseEntity<BlogResponse> deleteAll();
}
public abstract class BaseController<T extends MongoRepository<S,String>, S> implements IController<S> {
#Autowired
private T repository;
#Autowired
private BlogResponse blogResponse;
#PostMapping(value = "/save", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public #ResponseBody ResponseEntity<BlogResponse> save(S object) {
try {
S model = (S) repository.save(object);
String modelName = object.getClass().getSimpleName().toLowerCase();
blogResponse.setMessage(modelName + " is saved successfully").putData(modelName, object);
} catch (DuplicateKeyException dke) {
return new ResponseEntity<BlogResponse>(blogResponse.setMessage("This data is already existing!!!"), HttpStatus.BAD_REQUEST);
} catch (Exception e) {
return new ResponseEntity<BlogResponse>(blogResponse.setMessage(e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity<BlogResponse>(blogResponse, HttpStatus.OK);
}
// delete, findAll and other controllers
#RestController
#RequestMapping(value = "category")
#RequestScope
public class CategoryController extends BaseController<ICategoryRepository, Category>{
// More specific opretions like findSlug() can be write here.
}
And finally BlogResponce component is shared below;
#Component
#Scope("prototype")
public class BlogResponse{
private String message;
private Map<String, Object> data;
public String getMessage() {
return message;
}
public BlogResponse setMessage(String message) {
this.message = message;
return this;
}
public BlogResponse putData(String key, Object object){
if(data == null)
data = new HashMap<String,Object>();
data.put(key,object);
return this;
}
public Map<String,Object> getData(){
return data;
}
#Override
public String toString() {
return "BlogResponse{" +
"message='" + message + '\'' +
", data=" + data +
'}';
}
}
Question: I am new spring boot and I want to move forward by doing it right. BlogResponse is set bean by using #Component annotation. This doc said that other annotations like #Controller, #Service are specializations of #Component for more specific use cases. So I think, I cant use them. BlogResponse is set prototype scope for create new object at each injection. Also it's life end after response because of #RequestScope. Are this annotations using correcty? Maybe there is more effective way or approach. You can remark about other roughness if it existing.

Spring Boot 2.3.0 - MongoDB Library does not create indexes automatically

I've provided a sample project to elucidate this problem: https://github.com/nmarquesantos/spring-mongodb-reactive-indexes
According to the spring mongo db documentation (https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mapping-usage):
the #Indexed annotation tells the mapping framework to call createIndex(…) on that property of your document, making searches faster. Automatic index creation is only done for types annotated with #Document.
In my Player class, we can observe the both the #Document and #Indexed annotation:
#Document
public class Player {
#Id
private String id;
private String playerName;
#Indexed(name = "player_nickname_index", unique = true)
private String nickname;
public Player(String playerName, String nickname) {
this.id = UUID.randomUUID().toString();
this.playerName = playerName;
this.nickname = nickname;
}
public String getPlayerName() {
return playerName;
}
public void setPlayerName(String playerName) {
this.playerName = playerName;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
}`
And in my application class, i'm inserting oneelement to check the database is populated successfully:
#PostConstruct
public void seedData() {
var player = new Player("Cristiano Ronaldo", "CR7");
playerRepository.save(player).subscribe();
}
If I check MongoDb after running my application, I can see the collection and the element created successfully.
The unique index for nickname is not created. I can only see an index created for the #Id attribute. Am I missing anything? Did I mis-interpret the documentation?
The Spring Data MongoDB version come with Spring Boot 2.3.0.RELEASE is 3.0.0.RELEASE. Since Spring Data MongoDB 3.0, the auto-index creation is disabled by default.
To enable auto-index creation, set spring.data.mongodb.auto-index-creation = true or if you have custom Mongo configuration, override the method autoIndexCreation
#Configuration
public class CustomMongoConfig extends AbstractMongoClientConfiguration {
#Override
public boolean autoIndexCreation() {
return true;
}
// your other configuration
}
I've faced this problem when upgrading the spring boot version to 2.3.x and overriding this method on the config class solved it (what #yejianfengblue said above)
#Override
public boolean autoIndexCreation() {
return true;
}

How to use Java 8 Optional with Moxy and Jersey

Is it possible to use Jersey with Moxy to/from Json and Java 8 Optionals?
How to configure it?
You can declare following class:
public class OptionalAdapter<T> extends XmlAdapter<T, Optional<T>> {
#Override
public Optional<T> unmarshal(T value) throws Exception {
return Optional.ofNullable(value);
}
#Override
public T marshal(Optional<T> value) throws Exception {
return value.orElse(null);
}
}
And use like this:
#XmlRootElement
public class SampleRequest {
#XmlElement(type = Integer.class)
#XmlJavaTypeAdapter(value = OptionalAdapter.class)
private Optional<Integer> id;
#XmlElement(type = String.class)
#XmlJavaTypeAdapter(value = OptionalAdapter.class)
private Optional<String> text;
/* ... */
}
Or declare in package-info.java and remove #XmlJavaTypeAdapter from POJOs:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlJavaTypeAdapters({
#XmlJavaTypeAdapter(type = Optional.class, value = OptionalAdapter.class)
})
But here are some drawbacks:
Adapter above can only work with simple types like Integer, String, etc. that can be parsed by MOXY by default.
You have to specify #XmlElement(type = Integer.class) explicitly to tell the parser type are working with, otherwise null values would be passed to adapter's unmarshal method.
You miss the opportunity of using adapters for custom types, e.g. custom adapter for java.util.Date class based on some date format string. To overcome this you'll need to create adapter something like class OptionalDateAdapter<String> extends XmlAdapter<String, Optional<Date>>.
Also using Optional on field is not recommended, see this discussion for details.
Taking into account all the above, I would suggest just using Optional as return type for your POJOs:
#XmlRootElement
public class SampleRequest {
#XmlElement
private Integer id;
public Optional<Integer> getId() {
return Optional.ofNullable(id);
}
public void setId(Integer id) {
this.id = id;
}
}

Spring Data Rest Repository with abstract class / inheritance

I can't get Spring Data Rest with class inheritance working.
I'd like to have a single JSON Endpoint which handles all my concrete classes.
Repo:
public interface AbstractFooRepo extends KeyValueRepository<AbstractFoo, String> {}
Abstract class:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = MyFoo.class, name = "MY_FOO")
})
public abstract class AbstractFoo {
#Id public String id;
public String type;
}
Concrete class:
public class MyFoo extends AbstractFoo { }
Now when calling POST /abstractFoos with {"type":"MY_FOO"}, it tells me: java.lang.IllegalArgumentException: PersistentEntity must not be null!.
This seems to happen, because Spring doesn't know about MyFoo.
Is there some way to tell Spring Data REST about MyFoo without creating a Repository and a REST Endpoint for it?
(I'm using Spring Boot 1.5.1 and Spring Data REST 2.6.0)
EDIT:
Application.java:
#SpringBootApplication
#EnableMapRepositories
public class Application {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
I'm using Spring Boot 1.5.1 and Spring Data Release Ingalls.
KeyValueRepository doesn't work with inheritance. It uses the class name of every saved object to find the corresponding key-value-store. E.g. save(new Foo()) will place the saved object within the Foo collection. And abstractFoosRepo.findAll() will look within the AbstractFoo collection and won't find any Foo object.
Here's the working code using MongoRepository:
Application.java
Default Spring Boot Application Starter.
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
AbstractFoo.java
I've tested include = JsonTypeInfo.As.EXISTING_PROPERTY and include = JsonTypeInfo.As.PROPERTY. Both seem to work fine!
It's even possible to register the Jackson SubTypes with a custom JacksonModule.
IMPORTANT: #RestResource(path="abstractFoos") is highly recommended. Else the _links.self links will point to /foos and /bars instead of /abstractFoos.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = Foo.class, name = "MY_FOO"),
#JsonSubTypes.Type(value = Bar.class, name = "MY_Bar")
})
#Document(collection="foo_collection")
#RestResource(path="abstractFoos")
public abstract class AbstractFoo {
#Id public String id;
public abstract String getType();
}
AbstractFooRepo.java
Nothing special here
public interface AbstractFooRepo extends MongoRepository<AbstractFoo, String> { }
Foo.java & Bar.java
#Persistent
public class Foo extends AbstractFoo {
#Override
public String getType() {
return "MY_FOO";
}
}
#Persistent
public class Bar extends AbstractFoo {
#Override
public String getType() {
return "MY_BAR";
}
}
FooRelProvider.java
Without this part, the output of the objects would be separated in two arrays under _embedded.foos and _embedded.bars.
The supports method ensures that for all classes which extend AbstractFoo, the objects will be placed within _embedded.abstractFoos.
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class FooRelProvider extends EvoInflectorRelProvider {
#Override
public String getCollectionResourceRelFor(final Class<?> type) {
return super.getCollectionResourceRelFor(AbstractFoo.class);
}
#Override
public String getItemResourceRelFor(final Class<?> type) {
return super.getItemResourceRelFor(AbstractFoo.class);
}
#Override
public boolean supports(final Class<?> delimiter) {
return AbstractFoo.class.isAssignableFrom(delimiter);
}
}
EDIT
Added #Persistent to Foo.java and Bar.java. (Adding it to AbstractFoo.java doesn't work). Without this annotation I got NullPointerExceptions when trying to use JSR 303 Validation Annotations within inherited classes.
Example code to reproduce the error:
public class A {
#Id public String id;
#Valid public B b;
// #JsonTypeInfo + #JsonSubTypes
public static abstract class B {
#NotNull public String s;
}
// #Persistent <- Needed!
public static class B1 extends B { }
}
Please see the discussion in this resolved jira task for details of what is currently supported in spring-data-rest regarding JsonTypeInfo. And this jira task on what is still missing.
To summarize - only #JsonTypeInfo with include=JsonTypeInfo.As.EXISTING_PROPERTY is working for serialization and deserialization currently.
Also, you need spring-data-rest 2.5.3 (Hopper SR3) or later to get this limited support.
Please see my sample application - https://github.com/mduesterhoeft/spring-data-rest-entity-inheritance/tree/fixed-hopper-sr3-snapshot
With include=JsonTypeInfo.As.EXISTING_PROPERTY the type information is extracted from a regular property. An example helps getting the point of this way of adding type information:
The abstract class:
#Entity #Inheritance(strategy= SINGLE_TABLE)
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME,
include=JsonTypeInfo.As.EXISTING_PROPERTY,
property="type")
#JsonSubTypes({
#Type(name="DECIMAL", value=DecimalValue.class),
#Type(name="STRING", value=StringValue.class)})
public abstract class Value {
#Id #GeneratedValue(strategy = IDENTITY)
#Getter
private Long id;
public abstract String getType();
}
And the subclass:
#Entity #DiscriminatorValue("D")
#Getter #Setter
public class DecimalValue extends Value {
#Column(name = "DECIMAL_VALUE")
private BigDecimal value;
public String getType() {
return "DECIMAL";
}
}

#ConfigurationProperties referencing properties that themselves reference other properties

project.name=my-project
base.url=http://localhost:8080
cas.url=http://my-server:8010/cas
cas.callback.url=${base.url}/${project.name}
Basically I want to use the above in a spring-boot ConfigurationProperties but the casCallbackUrl is always null.
#Component
#ConfigurationProperties(prefix = "cas")
#Getter
#Setter
public class CasSettings {
#NotBlank
private String url; //this is resolved correctly
#NotBlank
private String callbackUrl; //callbackUrl is null
}
update
Well I got it working by camelCasing the property names, but according to the documentation you should be able to use dot notation for property names.
from:
cas.callback.url=${base.url}/${project.name}
to:
cas.callbackUrl=${base.url}/${project.name}
Why is spring-boot not picking up the dot notation?
The dot represents a separate object within the configuration properties object. cas.callback-url would work.
Spring relaxed property is not relaxed enugh to to transform dot notated properties to camel case fields. But you can implement it yourself easily:
#Service
#PropertySource("classpath:git.properties")
public class MngmntService implements EnvironmentAware {
private BuildStatus buildStatus;
private static final Logger LOG = LoggerFactory.getLogger(MngmntService.class);
#Override
public void setEnvironment(Environment env) {
RelaxedPropertyResolver pr = new RelaxedPropertyResolver(env, "git.");
buildStatus = new BuildStatus();
for (Field field : BuildStatus.class.getDeclaredFields()) {
String dotNotation = StringUtils.join(
StringUtils.splitByCharacterTypeCamelCase(field.getName()),
'.'
);
field.setAccessible(true);
try {
field.set(buildStatus, pr.getProperty(dotNotation, field.getType()));
} catch (IllegalArgumentException | IllegalAccessException ex) {
LOG.error("Error setting build property.", ex);
}
}
}
public BuildStatus getBuildStatus() {
return buildStatus;
}
Property object:
public class BuildStatus implements Serializable {
private String tags;
private String branch;
private String dirty;
private String commitId;
private String commitIdAbbrev;
private String commitTime;
private String closestTagName;
private String buildTime;
private String buildHost;
private String buildVersion;
...
}

Resources