Spring HATEOAS custom controller - enable converting URI to entity - spring

This is an example of what I would like to achieve:
#RepositoryRestController
#RequestMapping("/users")
public class UserController {
#Autowired
private UserRepository userRepository;
#Autowired
private TaskRepository taskRepository;
#PostMapping("/addCompletedTask")
public void addCompletedTask(User user, Task task) {
user.getCompletedTasks().add(task);
task.incrementCompletedBy();
userRepository.save(user);
taskRepository.save(task);
}
}
Then I would make a request like this:
POST http://localhost:8080/api/users/addCompletedTask
{
"user": "http://localhost:8080/api/db/users/59fa19bfd58dcf25e82082b2",
"task": "http://localhost:8080/api/db/tasks/59fa19bfd58dcf22d2322312"
}
I tried to wrap arguments to the method with Resource<User>, add #RequestBody, nothing works, everything is null.
I don't know if it's possible, but I've seen examples of people writing code like this, so maybe I'm missing something (here for example). Or maybe is there some way to do that through the repository in one call? I can add a task in one call, and increment counter in another, but that requires two calls from the client. Also I'm pretty sure I will encounter another situation similiar to this, so if there is a solution that would be great.

Related

How can I verify repository invocation?

Say I have a repository interface looks like this,
#Repository
interface MyRepository {
Optional<My> findByOtherId(long otherId);
default Optional<My> findByOther(Other other) {
return findByOtherId(other.getId());
}
}
I'm trying to invoke findByOther and verifies that the call invokes findByOtherId method.
#DataJpaTest
class MyRepositoryTest {
#Test
void test() {
Other other = new Other(0L);
repository.findByOther(other);
verify(other, times(1)).getId(); // verified
verify(repository, times(1)).findByOtherId(other.getId()); // called, yet not verified!
}
#SpyBean
private MyRepository repository;
}
When I debug, the findByOtherId method is called. But mockito complains it doesn't.
How can I do this?
As far as I understand you are trying to mock a methodCall that is done from inside the SpyBean. You are essentially trying to verify a private method call (even though findByOtherId can have the public modifier). That is why Mockito complains. As far as I know, mockito creates proxys around its spies. Similar to how spring proxies its beans. I donĀ“t think what you are trying to achieve is solved that way.
For a solution, I would suggest looking into PowerMock. There might be a solution in there. https://www.baeldung.com/powermock-private-method
verifyPrivate(mock).invoke("saveIntoDatabase", ArgumentMatchers.anyString());

Spring Boot MVC for the same endpoint sending two different responses (with a little difference)

I have a not-a-common requirement where I am two different type of consumers for my microservices. One type of consumer is okay with the type-of-response that I am sending them, whereas the other consumer has a requirement where we have to follow their structure (its pretty strict on this).
Lets say I have a StudentController
#RestController
#RequestMapping("/student")
public class StudentController {
#GetMapping("/{name}")
public Student getStudent(#PathVariable String name) {
return Student.builder()
.name(name)
.subjects(List.of("Maths", "English"))
.dateJoined(LocalDate.now().toString())
.build();
}
}
This is alright as one my consumer is accepting my response, where my response looks like this:
{"name":"smit","subjects":["Maths","English"],"dateJoined":"2020-04-26"}
However, the another consumer says that you should sending me the SAME object in a another from something like below:
#RestController
#RequestMapping("/wrapper")
public class WrapperController {
#Autowired
private StudentController studentController;
#GetMapping("/{name}")
public WrapperResponse getStudent(#PathVariable String name){
return WrapperResponse.builder()
.responseTimeStamp(LocalDateTime.now().toString())
.data(studentController.getStudent(name))
.build();
}
}
The below is the output of the above controller.
{"data":{"name":"smit","subjects":["Maths","English"],"dateJoined":"2020-04-26"},"responseTimeStamp":"2020-04-26T01:11:32.986"}
Summary: WrapperController is internally calling StudentController and then wrapping the response in the custom "WrapperResponse" class and then sending that as a response.
Problem: As of now it does solve the problem but I have many such controllers and in my different microservices. So I do not want to rewrite the "WrapperController" for each controller and each microservice.
This is very much opinion-based, but here's how I would refactor this:
Create a Student service (StudentService) that creates the Student,
rather than doing that in the Controller.
Push the code for building a WrapperResponse into the WrapperResponse class itself - either as a static 'factory' method, or as constructor. Have the method take a Student as a parameter.
For each different kind of 'Wrapper' object that you want to supply, have your controller method call the StudentService to get the base Student class, and then construct the Wrapper object by passing the Student to the factory method or constructor of the Wrapper object.
So, your WrapperController might end up looking like this:
#RestController
#RequestMapping("/wrapper")
public class WrapperController {
#Autowired
private StudentService studentService;
#GetMapping("/{name}")
public WrapperResponse getStudent(#PathVariable String name){
return new WrapperResponse(studentService.getStudent(name));
}
}

Return custom-typed object from JpaRepository

I have the following repository:
public interface UserRepository extends BaseDAO<User> {
Collection<User> findByEmail(#Param("email") String email);
#Query("select new com.data.CustomUser(upper(substring(u.lastName, 1, 1)) as initial, count(*)) from User u join u.chats c where c.business=:business group by upper(substring(u.lastName, 1, 1)) order by initial")
List<CustomUser> getContactsIndex(#Param("email") String email);
}
which is exposed with Spring Data REST. The User object is a managed entity, while CustomUser not and as you can see, it's build on-fly by using custom query.
Once I want to call that function, it fails with Persistent entity must not be a null! exception. Is there any way to implement this behavior?
P.S. Expose CustomUser with separate repository is impossible because it is not a managed entity.
One challenge with using Spring Data Rest is when you hit an edge case and you don't know whether you've hit a bug or whether you're just outside the scope of what the library is intended for. In this case I think you are at the edge of what SDR will easily do for you, and it's time to implement your own controller.
Spring Data Rest is looking for an Entity - in your case a User - as the return type for ALL methods in the repository to expose under /entities/search, and breaks when it doesn't find that entity type. The User it wants to serialize isn't there, hence the "Persistent entity must not be null".
The way around this is to write a simple #Controller that has a #RequestMapping for the exact same url exposed by the repository method. This will override the SDR generated implementation for that url, and from that you can return whatever you want.
Your implementation might look something like this:
#Controller
public class CustomUserController {
private final UserRepository repository;
#Inject
public CustomUserController(UserRepository repo) {
repository = repo;
}
#RequestMapping(value = "/users/search/getContactsIndex", method = GET, produces = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody List<CustomUser> getContactsIndex(#RequestParam String email) {
return repository.getContactsIndex(email);
}
}
Be aware that there is a "recommended" way to override functionality this way. There is an open issue to document the best way to do this.

Why doesn't Mockito's when() get triggered?

I need to test a service class, but when I try to mock the dao class, it doesn't get triggered, thus not able to use ThenReturn().
I think that the problem is because I use an interface for my Dao and #Autowired in the service class (Spring MVC 3.1):
The interface:
public interface TestDao {
int createObject(Test test) throws NamingException;
}
The implementation:
#Repository
public class TestDaoImpl implements TestDao {
#Override
public int createObject(Test test) {
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(new InsertNewTest(test), keyHolder);
return ((java.math.BigDecimal)keyHolder.getKey()).intValue();
}
}
The service:
public class RegTest {
#Autowired
TestDao testDao;
public int regTest(int .....) {
.
.
int cabotageId = testDao.createObject(test);
}
}
In the test I have:
#RunWith(MockitoJUnitRunner.class)
public class TestRegService {
#InjectMocks
private RegTest regTest = new RegTest();
#Mock
TestDao testDao;
#Test()
public void test() {
.
when(testDao.createObject(null)).thenReturn(100);
.
}
testDao.createObject(null) returns 0 (due to being mock'ed) and not 100 as I is trying to achieve.
Can anybody help, please?
Problem solved!
It was the passing test-object to createObject() that did not match. Using
testDao.createObject(any(Test.class))
did the trick!
If your test is actually passing a value to createObject, then when(testDao.createObject(null)... never gets matched. Rather than matching on null, you could match any instance of Test with testDao.createObject(any(Test.class))...
Also when you tried later to supply new Test() as the argument to match, it will literally try to match on that exact instance of Test, but presumably your real code is new-ing up a different one. So the use of Matchers.any(Test.class) as the parameter to match is the way to go.
Mockito injection mechanism don't know about Spring #Autowired or CDI #Inject annotations. It just tries to find the best candidate given the type and the name of the mock, and it can lookup private fields too. See the javadoc of #InjectMocks : http://docs.mockito.googlecode.com/hg/1.9.0/org/mockito/InjectMocks.html
The semantic you are using is correct, though if you are experiencing issues, I would rather look for incorrect interactions or incorrect arguments.
Are you sure the test variable in regTest.regTest(int...) is really null when passed to testDao.createObject(test) ?
I don't know if this is a typo in the example, but you have RegTest.regTest() calling createTest() rather than createObject(). Otherwise, I don't think #Autowired has anything to do with it, since your test itself is not running in a container with Spring management. If it is not a typo, and createTest is in fact a real and different method from createObject, then the default behaviour of a mocked object in Mockito is to return the appropriately-typed zero for numeric return types.
I think that you're right about the autowire not getting called. You could inject the dao yourself using the setTestDao() call instead. Mockito also supports spy which allows you to trace the objects code and just replace functions instead.

Law of Demeter and DAO pattern

Here's a method in my Spring/Hibernate website's code that exemplifies my codebase:
public class UserVoteServiceImpl implements UserVoteService {
#Autowired UserRepository userRepository;
public static int getUserScore(long userId) {
return userRepository.findUserById(userId).getScore();
}
}
I believe that this method violates the Law of Demeter, since it is making calls on the object returned by findUserById(). How can I change this code to obey the Principle of Least Knowledge?
I don't think it's a violation of the Law of Demeter. It would be a violation if you were passing in some object, getting userId off of it, and using only the userid.
Here's an example that would be a violation:
public class UserVoteServiceImpl implements UserVoteService {
#Autowired UserRepository userRepository;
public static int getUserScore(SomeWrapper someWrapper) {
return userRepository.findUserById(someWrapper.getUserId()).getScore();
}
}
But there's nothing wrong with delegating work within the implementation of your method, and there's nothing wrong with making a call on the object returned from the repository.
(Personally I'm not crazy about using services to wrap single dao calls, but that's a different problem.)
Currently I'm working on a codebase perpetrated by people who apparently never heard of LoD, full of stuff like
public Thing getThing(Integer id) {
return new Beta().getGamma().getDelta().getEpsilon().getOmega().getThing(id);
}
and initially I thought your example didn't rise to the same level of pathology as that. But after reading this blog post, which is where I got the above example, of course,
I think I'd recommend you changing your method to
public class UserVoteServiceImpl implements UserVoteService {
#Autowired UserRepository userRepository;
public User getUser(Long userId) {
return userRepository.findUserById(userId);
}
}
and letting the caller pull the score off the User. This change also has the benefit of having the application's service interface deal in domain objects, not in primitives.

Resources