I am using Java StreamSupport to stream on Iterable return from repository method inside of #Transactional method in service layer.
Svc:
#Transactional(readOnly = true)
public Map<String, Map<String, Object>> getAllProjects() {
return StreamSupport.stream(projectRepo.findAllByOrderByNameAsc().spliterator(), true)
.collect(
LinkedHashMap::new,
(map, projectItem) -> map.put(projectItem.getName(), populateProjectItem(projectItem)),
(map, projectMap) -> map.putAll(projectMap)
);
}
private Map<String, Object> populateProjectItem(final Project project) {
return ImmutableMap.of(
"id", project.getId(),
"name", project.getName(),
"taskList", project.getTaskList().stream()
.map(taskItem -> ImmutableMap.of(
"id", taskItem.getId()
)
.collect(Collectors.toList())
);
}
I am not sure what I am doing wrong but it gives me nullpointer on Task Id in test case. project.getTaskList() is lazy fetch loaded.
Am i missing something ?
Related
I try to create a simple controller with spring hateoas.
The controller is the following :
#RestController
public class SearchController {
private final List<Plugin> plugins;
#Autowired
public SearchController(List<Plugin> plugins) {
this.plugins = plugins;
}
#GetMapping("/search")
public CollectionModel<PluginDTO> search(
#RequestParam(value = "name", defaultValue = "") String name) {
List<PluginDTO> pluginsDTO = this.plugins.stream()
.filter(plugin -> {
if(name.isBlank()) { // No filter case.
return true;
}
return plugin.getName().toLowerCase(Locale.ROOT).contains(name.toLowerCase(Locale.ROOT));
})
.map(plugin -> new PluginDTO(plugin.getName(), plugin.getDescription(),
plugin.getCapacities().stream().map(c -> new CapacityDTO(c.getName(), c.getDescription())).toList())
.add(
linkTo(methodOn(SearchController.class).one(plugin.getName())).withSelfRel(),
linkTo(methodOn(SearchController.class).search(name)).withRel("search"))
)
.toList();
Link link = linkTo(methodOn(SearchController.class).search(name)).withSelfRel();
return CollectionModel.of(pluginsDTO, link);
}
#GetMapping("/plugin")
private PluginDTO one(#RequestParam(value = "name") String name) {
return this.plugins.stream().filter(plugin -> plugin.getName().equals(name)).findFirst()
.map(plugin -> new PluginDTO(plugin.getName(), plugin.getDescription(),
plugin.getCapacities().stream().map(c -> new CapacityDTO(c.getName(), c.getDescription())).toList())
.add(
linkTo(methodOn(SearchController.class).one("")).withSelfRel(),
linkTo(methodOn(SearchController.class).search("")).withRel("search"))
)
.orElseThrow(() -> new PluginNotFoundException(name));
}
}
With this code linkTo(methodOn(SearchController.class).get(plugin.getName())).withSelfRel() Spring call the method on() and throw a NPE on this.plugin. It seems that #Autowire is not resolve in this case.
In the official doc : https://spring.io/guides/tutorials/rest/ Injection seems to work
Any idea why this happen ?
I have a project (https://github.com/serjteplov/demo-kafka-mock.git) where there are couple of dummy functions to read messages from kafka. In one of these functions crudRepository is used to perform some operations against DB:
#EnableAutoConfiguration
public class SampleFunctionConfiguration {
#Bean
public Function<String, String> uppercase(UserRepository userRepository) {
String id = userRepository.findById("abc").map(User::getId).orElse(null);
return value -> value.toUpperCase() + " id=" + id;
}
#Bean
public Function<String, String> reverse() {
return value -> new StringBuilder(value).reverse().toString();
}
}
So the problem is to write test on uppercase binder function. To make this test works correctly I have to mock such call
userRepository.findById("abc")
then I've created mock and added it to the context:
#Primary
#Bean("test")
UserRepository userRepository() {
return new DetachedMockFactory().Mock(UserRepository)
}
and mock call in the test:
userRepository.findById("abc") >> Optional.of(new User("abc", "Bob"))
After test execution mock is successfully created, but userRepository.findById("abc") still returns null.
Can anyone tell how to fix this problem? Or any alternative implementations and workarounds would be nice
You are invoking the stubbed method in the bean definition instead of in the function.
#Bean
public Function<String, String> uppercase(UserRepository userRepository) {
String id = userRepository.findById("abc").map(User::getId).orElse(null);
return value -> value.toUpperCase() + " id=" + id;
}
Vs.
#Bean
public Function<String, String> uppercase(UserRepository userRepository) {
return value -> {
String id = userRepository.findById("abc").map(User::getId).orElse(null);
return value.toUpperCase() + " id=" + id;
};
}
#Mapper(componentModel = "spring")
public interface MapperThings {
#MapMapping(keyTargetType = Object.class, valueTargetType = Object.class)
Map<String, String> toDto(Map<Object, Object> mapEntity);
List<Map <String, String>> toListDto(Collection<Map<Object, Object>> listEntity);
#MapMapping(keyTargetType = Object.class, valueTargetType = Object.class)
Map<Object, Object> toEntity(Map<String, String> mapDto);
List<Map<Object, Object> > toListEntity(Collection<Map<String, String>> listDto);
}
There is to generate without mistakes only :
#MapMapping(keyTargetType = Object.class, valueTargetType = Object.class)
Map<Object, Object> toEntity(Map<String, String> mapDto);
List<Map<Object, Object> > toListEntity(Collection<Map<String, String>> listDto);
I found temporary decision. But I would want to use the annotation #MapMapping.
#Mapper(componentModel = "spring")
public abstract class MapperMoviesAbstract {
public Map<String, String> toDto(Map<Object, Object> mapEntity) {
if(mapEntity == null) return null;
Map<String, String> stringMap = new HashMap<>();
for(Map.Entry<Object, Object> entry : mapEntity.entrySet()){
String key = (String) entry.getKey();
stringMap.put(key, mapEntity.get(key).toString());
}
return stringMap;
}
public abstract List< Map<String, String>> toListDto(Collection<Map<Object, Object>> listEntity);
}
According to the MapStruct documentation, using the #MapMapping annotation should generate a class that will perform the conversion.
But I get an error:
Can't map map key "java.lang.Object" to "java.lang.String ". Consider
to dec lare/implement a mapping method: "java.lang.String
map(java.lang.Object value)".
Do anyone have any ideas to do what?
The error message is telling you what to do. You need to provide a way to map Object into a String.
So you'll need a custom method like:
public String objectToString(Object object) {
// Your custom implementation
}
I'm trying to find a more or less elegant way to handle PATCH http operations in Spring MVC.
Basically, I'd like to perform a "dual" Jackson deserialization of a JSON document from a Request Body: one to a Map, and the other to the target POJO. Ideally, I would like to perform this in a single PartialDto<T> instance, where T is my target DTO type.
Better giving an example. Let's say I currently have this PUT mapping in a REST Controller:
#PutMapping("/resource")
public MyDto updateWhole(#RequestBody MyDto dto) {
System.out.println("PUT: updating the whole object to " + dto);
return dto;
}
My idea is to build a PartialDto type that would provide both POJO representation of the request body, as well as the Map representation, like this:
#PatchMapping("/resource")
public MyDto updatePartial(#RequestBody PartialDto<MyDto> partial) {
System.out.println("PATCH: partial update of the object to " + partial);
final MyDto dto = partial.asDto();
// Do stuff using the deserialized POJO
final Map<String, Object> map = partial.asMap();
// Do stuff as a deserialized map...
return dto;
}
I hope this will allow me to further expand the PartialDto implementation so I can perform things like this:
if (partial.hasAttribute("myAttribute")) {
final String myAttribute = dto.getMyAttribute();
// ...
}
Or even using a metamodel generator:
if (partial.hasAttribute(MyDto_.myAttribute)) {
final String myAttribute = dto.getMyAttribute();
// ...
}
So the question is simple: Jackson can easily map a JSON document to a POJO. It can also easily map a JSON document to a java Map. How can I do both at the same time in a Wrapper object such as my PartialDto?
public class PartialDto<T> {
private final Map<String, Object> map;
private final T dto;
PartialDto(Map<String, Object> map, T dto) {
this.map = map;
this.dto = dto;
}
public T asDto() {
return this.dto;
}
public Map<String, Object> asMap() {
return Collections.unmodifiableMap(this.map);
}
}
I tried to use a GenericConverter like this (that, of course, I registered in Spring MVC's FormatterRegistry):
public class PartialDtoConverter implements GenericConverter {
private final ObjectMapper objectMapper;
public PartialDtoConverter(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
#Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(String.class, PartialDto.class));
}
#Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
final Class<?> targetClazz = targetType.getResolvableType().getGeneric(0).getRawClass();
final Map<String, Object> map;
try {
map = objectMapper.readValue((String) source, Map.class);
} catch (JsonProcessingException e) {
throw new RuntimeException(e); // FIXME
}
final Object dto = objectMapper.convertValue(map, targetClazz);
return new PartialDto(map, dto) ;
}
}
And this converter works well when tested directly using Spring's ConversionService:
#SpringBootTest
class ConverterTest {
#Autowired
private ConversionService conversionService;
#Test
public void testPartialUpdate() throws Exception {
final MyDto dto = new MyDto()
.setIt("It");
final PartialDto<MyDto> partialDto = (PartialDto<MyDto>) conversionService.convert(
"{ \"it\": \"Plop\" }",
new TypeDescriptor(ResolvableType.forClass(String.class), null, null),
new TypeDescriptor(ResolvableType.forClassWithGenerics(PartialDto.class, MyDto.class), null, null)
);
Assertions.assertEquals("Plop", partialDto.asDto().getIt());
Assertions.assertEquals("Plop", partialDto.asMap().get("it"));
}
}
However, it doesn't seem to work in a #RequestBody such as shown above. Reminder:
#PatchMapping("/resource")
public MyDto updatePartial(#RequestBody PartialDto<MyDto> partial) {
// ...
}
Any idea is welcome.
Spring cloud openFeign can't create dynamic query parameters. It throws below exception because SpringMvcContract tries to find the RequestParam value attribute which doesn't exist.
java.lang.IllegalStateException: RequestParam.value() was empty on parameter 0
#RequestMapping(method = RequestMethod.GET, value = "/orders")
Pageable<Order> searchOrder2(#RequestParam CustomObject customObject);
I tried using #QueryMap instead of #RequestParam but #QueryMap does not generate query parameters.
Btw #RequestParam Map<String, Object> params method parameter works fine to generate a dynamic query parameter.
But I want to use a custom object in which the feign client can generate dynamic query parameters from the object's attributes.
From Spring Cloud OpenFeign Docs:
Spring Cloud OpenFeign provides an equivalent #SpringQueryMap annotation, which is used to annotate a POJO or Map parameter as a query parameter map
So your code should be:
#RequestMapping(method = RequestMethod.GET, value = "/orders")
Pageable<Order> searchOrder2(#SpringQueryMap #ModelAttribute CustomObject customObject);
spring-cloud-starter-feign has a open issue for supporting pojo object as request parameter. Therefore I used a request interceptor that take object from feign method and create query part of url from its fields. Thanks to #charlesvhe
public class DynamicQueryRequestInterceptor implements RequestInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(DynamicQueryRequestInterceptor.class);
private static final String EMPTY = "";
#Autowired
private ObjectMapper objectMapper;
#Override
public void apply(RequestTemplate template) {
if ("GET".equals(template.method()) && Objects.nonNull(template.body())) {
try {
JsonNode jsonNode = objectMapper.readTree(template.body());
template.body(null);
Map<String, Collection<String>> queries = new HashMap<>();
buildQuery(jsonNode, EMPTY, queries);
template.queries(queries);
} catch (IOException e) {
LOGGER.error("IOException occurred while try to create http query");
}
}
}
private void buildQuery(JsonNode jsonNode, String path, Map<String, Collection<String>> queries) {
if (!jsonNode.isContainerNode()) {
if (jsonNode.isNull()) {
return;
}
Collection<String> values = queries.computeIfAbsent(path, k -> new ArrayList<>());
values.add(jsonNode.asText());
return;
}
if (jsonNode.isArray()) {
Iterator<JsonNode> it = jsonNode.elements();
while (it.hasNext()) {
buildQuery(it.next(), path, queries);
}
} else {
Iterator<Map.Entry<String, JsonNode>> it = jsonNode.fields();
while (it.hasNext()) {
Map.Entry<String, JsonNode> entry = it.next();
if (StringUtils.hasText(path)) {
buildQuery(entry.getValue(), path + "." + entry.getKey(), queries);
} else {
buildQuery(entry.getValue(), entry.getKey(), queries);
}
}
}
}
}