I have found a lot about this, but no one having this same issue, the only that i can think is the last answer in this question where the mutability of the object makes the annotation work different.
So, I have an object like this
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonInclude(value = Include.NON_EMPTY)
public class Invoice implements Serializable {
#JsonInclude(value = Include.NON_EMPTY)
private String originCode;
#JsonInclude(value = Include.NON_EMPTY)
public String getOriginCode() {
return originCode;
}
#JsonInclude(value = Include.NON_EMPTY)
public void setOriginCode(String originCode) {
this.originCode = originCode;
}
}
When deserializing this object from a JSON, using spring framework the value of originCode keeps coming empty, if i use
{
"originCode" : ""
}
In the other way around if I use this object where originCode is already empty and I serialize it, the originCode is ignores in the serialized json.
Why this is working just when serializing?, how the fact that this object is mutable can affect in the use of this annotation when deserializing?
---EDIT---
The solution proposed here below did not fix the problem, I thought the problem was actually in spring. So I tried like this
#RequestMapping(method = RequestMethod.POST, value = "/test")
#ResponseBody
public ResponseEntity<InvMessage> testInvoice(
#PathVariable(value = "someId") #Example({ "1233232-7" }) InvoicerId invoicerId,
#RequestBody Invoice2 invoiceRequest,
InvMessage messageContainer) {
ObjectMapper mapper = new ObjectMapper();
try {
String jsonString1 = mapper.writeValueAsString(invoiceRequest);
Invoice2 invoiceTest1 = mapper.readValue(jsonString1, Invoice2.class);
String jsonInString2 = "{\"originCode\" : \"\",\"originText\" : \"Original\"}";
Invoice2 testInvoice = mapper.readValue(jsonInString2, Invoice2.class);
} catch (JsonProcessingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return ok(messageContainer);
}
So, when sending a request with body
{
"originCode" : "",
"originText" : "Original"
}
The results are
jsonString1: "{"originText" : "Original"}"
invoiceTest1 (originCode null)
invoiceTest2 (originCode: "")
Checking those results, i can see that always when deserializing that empty string I'm getting also an empty string inside the object, even I have defined the class like.
#JsonIgnoreProperties(ignoreUnknown = true)
public class Invoice2 implements Serializable {
private static final long serialVersionUID = 1L;
#JsonInclude(value = Include.NON_EMPTY)
private String originCode;
private String originText;
public String getOriginCode() {
return originCode;
}
public void setOriginCode(String originCode) {
this.originCode = originCode;
}
public String getOriginText() {
return originText;
}
public void setOriginText(String originText) {
this.originText = originText;
}
}
Jackson-databind version 2.5
Since you may be using Class level annotation and property level annotation, latter overriding the former explained here
Try,
#JsonIgnoreProperties(ignoreUnknown = true)
public class Invoice implements Serializable {
#JsonInclude(value = Include.NON_EMPTY)
private String originCode;
public String getOriginCode() {
return originCode;
}
public void setOriginCode(String originCode) {
this.originCode = originCode;
}
}
Related
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.
I am trying to unit test a put request which takes a file and some json data as request body. following is the method i am trying to test:
#RequestMapping(
value = "/{id}",
method = RequestMethod.PUT,
produces = { "application/json" }
)
public ResponseEntity<UpdateT1Output> update(#PathVariable String id, #ModelAttribute #Valid UpdateT1Input t1) {
// implementation here
}
UpdateT1Input.java
public class UpdateT1Input {
private char[] ca;
private byte[] file;
public void setFile(MultipartFile mpfile) {
try {
file = mpfile.getBytes();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private List<Double> flpa;
private List<Double> fpa;
#NotNull(message = "id Should not be null")
private Long id;
private String str;
private Long versiono;
}
test setup
#Test
public void UpdateT1_T1Exists_ReturnStatusOk() throws Exception {
// create entity obj with default values
T1Entity entity = createUpdateEntity();
entity.setVersiono(0L);
UpdateT1Input t1Input = new UpdateT1Input();
t1Input.setId(entity.getId());
t1Input.setFlpa(entity.getFlpa());
t1Input.setStr(entity.getStr());
ObjectWriter ow = new ObjectMapper()
.registerModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.writer()
.withDefaultPrettyPrinter();
String json = ow.writeValueAsString(t1Input);
MockMultipartHttpServletRequestBuilder builder =
MockMvcRequestBuilders.multipart("/t1/" + entity.getId());
builder.with(request -> {
request.setMethod("PUT");
return request;
});
mvc.perform(builder
.file("file", "ABC".getBytes("UTF-8"))
.content(json)
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(status().isOk());
}
but in controller only id and file fields are set in input dto all other fields are null. i am using #ModelAttribute to avoid dividing request into file and data parts. so is there a way that to get all the fields in single object?
we need to make a spring boot project that works with spring ldap.
every things is good.But when we remove a member from a group,the member deleted form group (i see it in debug mode in a Setmembers) but, in ldap(Oracle Internet Directory) that member exists!
Please help me!
//Group Entry
#Entry(objectClasses = {"top", "groupOfUniqueNames", "orclGroup"}, base = "cn=Groups")
public final class Group {
#Id
private Name dn;
#Attribute(name = "cn")
private String name;
private String description;
private String displayName;
#Attribute(name = "ou")
private String ou;
#Attribute(name = "uniqueMember")
private Set<Name> members;
public void addMember(Name newMember) {
members.add(newMember);
}
public void removeMember(Name member) {
members.remove(member);
}
//Custom LdapUtils
public class CustomLdapUtils {
private static final String GROUP_BASE_DN = "cn=Groups";
private static final String USER_BASE_DN = "cn=Users";
public Name buildGroupDn(String name) {
return LdapNameBuilder.newInstance(GROUP_BASE_DN)
.add("cn","Charts")
.add("cn",name)
.build();
}
private static final CsutomLdapUtils LDAP_UTILS = new CsutomLdapUtils ();
private CsutomLdapUtils () {
}
public Name buildPersonDn(String name) {
return LdapNameBuilder.newInstance(USER_BASE_DN)
.add("cn", name)
.build();
}
}
//Controller
#DeleteMapping(value = "/memberOfGroup", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> removeMemberFromGroup(#RequestBody Map<String,String> map) throws NamingException {
List<Group> groupToFind = ldapSearchGroupsService.getGroupByCn(map.get("groupName"));
List<User> userToFind = ldapSearchUserService.getAllUserByUserName(map.get("userName"));
if (groupToFind.isEmpty()) {
//TODO : Group no found!
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} else {
for (Group group1 : groupToFind) {
group1.removeMember(userToFind.stream().findAny().get().getDn());
//ldapBindGroupService.deleteMemberFromGroup(group1);
DirContextOperations ctx = ldapTemplate.lookupContext(CustomLdapUtils.getInstance().buildGroupDn(map.get("groupName")));
ctx.removeAttributeValue("uniqueMember",map.get("userName"));
ctx.rebind(CustomLdapUtils.getInstance().buildGroupDn(map.get("groupName")),map.get("groupName"));
ldapTemplate.modifyAttributes(ctx);
}
return new ResponseEntity<>(HttpStatus.OK);
}
}
Is some problem in code? or need some methods?
Finally after several search and debug,i found the problem!
In each ldap env,after every changes,the directory must be commit and apply.
In above code,i implemented that,but not in true way!
Best way is here:
#DeleteMapping(value = "/membersOfGroup", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> removeMemberFromGroup(#RequestBody Map<String,String> map) {
List<Group> groupToFind = ldapSearchGroupsService.getGroupByCn(map.get("groupName"));
List<User> userToFind = ldapSearchUserService.getAllUserByUserName(map.get("userName"));
if (groupToFind.isEmpty()) {
//TODO : Group no found!
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} else {
for (Group group1 : groupToFind) {
group1.removeMember(userToFind.stream().findAny().get().getDn());
DirContextOperations ctx = ldapTemplate.lookupContext(CustomLdapUtils.getInstance().buildGroupDn(map.get("groupName")));
ctx.removeAttributeValue("member",CustomLdapUtils.getInstance().buildPersonDn(map.get("userName")));
//True way
ldapTemplate.update(group1);
}
return new ResponseEntity<>(HttpStatus.OK);
}
}
I am trying out to write data to my local Elasticsearch Docker Container (7.4.2), for simplicity I used the AbstractReactiveElasticsearchConfiguration given from Spring also Overriding the entityMapper function. The I constructed my repository extending the ReactiveElasticsearchRepository
Then in the end I used my autowired repository to saveAll() my collection of elements containing the data. However Elasticsearch doesn't write any data. Also i have a REST controller which is starting my whole process returning nothing basicly, DeferredResult>
The REST method coming from my ApiDelegateImpl
#Override
public DeferredResult<ResponseEntity<Void>> openUsageExporterStartPost() {
final DeferredResult<ResponseEntity<Void>> deferredResult = new DeferredResult<>();
ForkJoinPool.commonPool().execute(() -> {
try {
openUsageExporterAdapter.startExport();
deferredResult.setResult(ResponseEntity.accepted().build());
} catch (Exception e) {
deferredResult.setErrorResult(e);
}
}
);
return deferredResult;
}
My Elasticsearch Configuration
#Configuration
public class ElasticSearchConfig extends AbstractReactiveElasticsearchConfiguration {
#Value("${spring.data.elasticsearch.client.reactive.endpoints}")
private String elasticSearchEndpoint;
#Bean
#Override
public EntityMapper entityMapper() {
final ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(), new DefaultConversionService());
entityMapper.setConversions(elasticsearchCustomConversions());
return entityMapper;
}
#Override
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo(elasticSearchEndpoint)
.build();
return ReactiveRestClients.create(clientConfiguration);
}
}
My Repository
public interface OpenUsageRepository extends ReactiveElasticsearchRepository<OpenUsage, Long> {
}
My DTO
#Data
#Document(indexName = "open_usages", type = "open_usages")
#TypeAlias("OpenUsage")
public class OpenUsage {
#Field(name = "id")
#Id
private Long id;
......
}
My Adapter Implementation
#Autowired
private final OpenUsageRepository openUsageRepository;
...transform entity into OpenUsage...
public void doSomething(final List<OpenUsage> openUsages){
openUsageRepository.saveAll(openUsages)
}
And finally my IT test
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
#Testcontainers
#TestPropertySource(locations = {"classpath:application-it.properties"})
#ContextConfiguration(initializers = OpenUsageExporterApplicationIT.Initializer.class)
class OpenUsageExporterApplicationIT {
#LocalServerPort
private int port;
private final static String STARTCALL = "http://localhost:%s/open-usage-exporter/start/";
#Container
private static ElasticsearchContainer container = new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:6.8.4").withExposedPorts(9200);
static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(final ConfigurableApplicationContext configurableApplicationContext) {
final List<String> pairs = new ArrayList<>();
pairs.add("spring.data.elasticsearch.client.reactive.endpoints=" + container.getContainerIpAddress() + ":" + container.getFirstMappedPort());
pairs.add("spring.elasticsearch.rest.uris=http://" + container.getContainerIpAddress() + ":" + container.getFirstMappedPort());
TestPropertyValues.of(pairs).applyTo(configurableApplicationContext);
}
}
#Test
void testExportToES() throws IOException, InterruptedException {
final List<OpenUsageEntity> openUsageEntities = dbPreparator.insertTestData();
assertTrue(openUsageEntities.size() > 0);
final String result = executeRestCall(STARTCALL);
// Awaitility here tells me nothing is in ElasticSearch :(
}
private String executeRestCall(final String urlTemplate) throws IOException {
final String url = String.format(urlTemplate, port);
final HttpUriRequest request = new HttpPost(url);
final HttpResponse response = HttpClientBuilder.create().build().execute(request);
// Get the result.
return EntityUtils.toString(response.getEntity());
}
}
public void doSomething(final List<OpenUsage> openUsages){
openUsageRepository.saveAll(openUsages)
}
This lacks a semicolon at the end, so it should not compile.
But I assume this is just a typo, and there is a semicolon in reality.
Anyway, saveAll() returns a Flux. This Flux is just a recipe for saving your data, and it is not 'executed' until subscribe() is called by someone (or something like blockLast()). You just throw that Flux away, so the saving never gets executed.
How to fix this? One option is to add .blockLast() call:
openUsageRepository.saveAll(openUsages).blockLast();
But this will save the data in a blocking way effectively defeating the reactivity.
Another option is, if the code you are calling saveAll() from supports reactivity is just to return the Flux returned by saveAll(), but, as your doSomething() has void return type, this is doubtful.
It is not seen how your startExport() connects to doSomething() anyway. But it looks like your 'calling code' does not use any notion of reactivity, so a real solution would be to either rewrite the calling code to use reactivity (obtain a Publisher and subscribe() on it, then wait till the data arrives), or revert to using blocking API (ElasticsearchRepository instead of ReactiveElasticsearchRepository).
I'm trying to test some services with Mockito but I have problems when the main class that I test and where I inject Mocks calls to super.
I run the project with spring and these are the steps I follow to get the error.
Here is where I create the test
public class UrlShortenerTests {
private MockMvc mockMvc;
#Mock
private ShortURLRepository shortURLRepository;
#Mock
private ClickRepository clickRespository;
#InjectMocks
private UrlShortenerControllerWithLogs urlShortenerWL;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(urlShortenerWL).build();
}
#Test
public void thatShortenerCreatesARedirectIfTheURLisOK() throws Exception {
mockMvc.perform(post("/link")
.param("url", "http://www.google.com"))
.andDo(print())
.andExpect(status().isCreated())
.andExpect(jsonPath("$.target", is("http://example.com/")));
}
}
Here is the class UrlShortenerControllerWithLogs with the method shortener, which is the one I want to test with the previous POST call
#RestController
public class UrlShortenerControllerWithLogs extends UrlShortenerController {
#Autowired
private ClickRepository clickRepository;
#Autowired
private ShortURLRepository SURLR;
public ResponseEntity<ShortURL> shortener(#RequestParam("url") String url,
#RequestParam(value = "sponsor", required = false) String sponsor,
#RequestParam(value = "brand", required = false) String brand,
HttpServletRequest request) {
ResponseEntity<ShortURL> su = super.shortener(url, sponsor, brand,
request);
return su;
}
And this is the super class
#RestController
public class UrlShortenerController {
#Autowired
protected ShortURLRepository shortURLRepository;
#Autowired
protected ClickRepository clickRepository;
#RequestMapping(value = "/link", method = RequestMethod.POST)
public ResponseEntity<ShortURL> shortener(#RequestParam("url") String url,
#RequestParam(value = "sponsor", required = false) String sponsor,
#RequestParam(value = "brand", required = false) String brand,
HttpServletRequest request) {
ShortURL su = createAndSaveIfValid(url, sponsor, brand, UUID
.randomUUID().toString(), extractIP(request));
if (su != null) {
HttpHeaders h = new HttpHeaders();
h.setLocation(su.getUri());
return new ResponseEntity<>(su, h, HttpStatus.CREATED);
} else {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
protected ShortURL createAndSaveIfValid(String url, String sponsor,
String brand, String owner, String ip) {
UrlValidator urlValidator = new UrlValidator(new String[] { "http",
"https" });
if (urlValidator.isValid(url)) {
String id = Hashing.murmur3_32()
.hashString(url, StandardCharsets.UTF_8).toString();
ShortURL su = new ShortURL(id, url,
linkTo(
methodOn(UrlShortenerController.class).redirectTo(
id, null)).toUri(), sponsor, new Date(
System.currentTimeMillis()), owner,
HttpStatus.TEMPORARY_REDIRECT.value(), true, ip, null);
return shortURLRepository.save(su);
} else {
return null;
}
}
So, when I call to shortURLRepository.save(su) in the second method (createAndSaveIfValid), it never enters in the method save, so it returns me null instead of the object I want.
The code of the implementation of ShortURLRepository and the method save is:
#Repository
public class ShortURLRepositoryImpl implements ShortURLRepository {
private static final Logger log = LoggerFactory
.getLogger(ShortURLRepositoryImpl.class);
#Override
public ShortURL save(ShortURL su) {
try {
jdbc.update("INSERT INTO shorturl VALUES (?,?,?,?,?,?,?,?,?)",
su.getHash(), su.getTarget(), su.getSponsor(),
su.getCreated(), su.getOwner(), su.getMode(), su.getSafe(),
su.getIP(), su.getCountry());
} catch (DuplicateKeyException e) {
log.debug("When insert for key " + su.getHash(), e);
return su;
} catch (Exception e) {
log.debug("When insert", e);
return null;
}
return su;
}
I think that the problem is that the object ShortURLRepository created in the test class is not initialized on the super class (UrlShortenerController) or something similar.
Is it possible?
Can anybody help me?
The full code is in: https://github.com/alberto-648702/UrlShortener2014
The class UrlShortenerTests is in:
bangladeshGreen/src/test/java/urlshortener2014/bangladeshgreen
The class UrlShortenerControllerWithLogs is in:
bangladeshGreen/src/main/java/urlshortener2014/bangladeshgreen/web
The class UrlShortenerController is in:
common/src/main/java/urlshortener2014/common/web
The class ShortURLRepositoryImpl is in:
common/src/main/java/urlshortener2014/common/repository
This is not an error. This is the expected behaviour. #Mock creates a mock. #InjectMocks creates an instance of the class and injects the mocks that are created with the #Mock. A mock is not a real object with known values and methods. It is an object that has the same interface as the declared type but you control its behaviour. By default the mocked object methods do nothing (e.g. return null). Therefore if ShortURLRepository is mocked and injected in UrlShortenerControllerWithLogs calling save in the injected ShortURLRepository does not call the real code as you expected, it does nothing. If you want to mock the behaviour of save, add the following code in your setup:
when(shortURLRepository.save(org.mockito.Matchers.any(ShortURL.class))).
then(new Answer<ShortURL>() {
#Override
public ShortURL answer(InvocationOnMock invocation) throws Throwable {
ShortURL su = (ShortURL) invocation.getArguments()[0];
// Do something with su if needed
return su;
}
});