Return IDs in JSON response from Spring Data REST - spring-boot

I've got an entity
#Entity
#Table(name = "books")
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#Column(name = "id", unique = true, nullable = false)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Column(name = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
I initialize it like this
#PostConstruct
public void init() {
List<String> newFiles = this.listFiles();
newFiles.forEach(filename -> {
Book book = new Book();
book.setName(filename);
dbRepository.save(book);
});
}
If I set the result of save to an instance of Book, I can get the id and it is not null—so id is created fine.
I defined a repository
#RepositoryRestResource
public interface IBooksRepository extends CrudRepository<Book, Long> {
}
which I'd like to use to get and set data into the books table in the database.
When I try to access my repository rest using curl localhost:8080/books, I get this response
{
"_embedded":{
"books":[
{
"name":"simple-file.txt",
"_links":{
"self":{
"href":"http://localhost:8080/books/1"
},
"book":{
"href":"http://localhost:8080/books/1"
}
}
}
]
},
"_links":{
"self":{
"href":"http://localhost:8080/books"
},
"profile":{
"href":"http://localhost:8080/profile/books"
}
}
}
The books element returns name only. How can I make it return id too, on the same level as name?

Spring Data Rest hides the ID by default, in order to have it in the JSON you have to manually configure that for your entity. Depending on your spring version you can either provide your own configuration (old):
#Configuration
public class ExposeEntityIdRestConfiguration extends RepositoryRestMvcConfiguration {
#Override
protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(Book.class);
}
}
...or register a RepositoryRestConfigurer (current):
#Component
public class ExposeEntityIdRestMvcConfiguration extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(Book.class);
}
}
See the Spring Data Rest documentation for more details.

The accepted answer overrides a deprecated method. Here's the updated version:
#Component
public class RestConfig implements RepositoryRestConfigurer {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
config.exposeIdsFor(Book.class);
}
}
An alternative approach is to implement RepositoryRestConfigurer in your #SpringBootApplication annotated class:
#SpringBootApplication
public class MyApplication implements RepositoryRestConfigurer {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
config.exposeIdsFor(Book.class);
}
}

There is now a static method RepositoryRestConfigurer.withConfig that does the same thing as above. See javadoc:
Convenience method to easily create simple {#link RepositoryRestConfigurer} instances that solely want to tweak the {#link RepositoryRestConfiguration}.
I found the usage in one of their integration tests
So the following approach would be more up to date as of now:
#Bean
public RepositoryRestConfigurer repositoryRestConfigurer()
{
return RepositoryRestConfigurer.withConfig(config -> {
config.exposeIdsFor(Book.class);
});
}

#Component
public class RestConfig implements RepositoryRestConfigurer {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(Book.class);
//config.exposeIdsFor(Library.class);
}
}

This is a solution which works for all entities
#Autowired
private EntityManager entityManager;
#Bean
public RepositoryRestConfigurer repositoryRestConfigurer() {
return RepositoryRestConfigurer.withConfig(config -> config.exposeIdsFor(entityManager.getMetamodel().getEntities().stream().map(Type::getJavaType).toArray(Class[]::new)));
}

This is a good way to go.
#Projection(name = "customBook", types = { Book.class })
public interface CustomBook {
#Value("#{target.id}")
long getId();
}
credit: https://www.baeldung.com/spring-data-rest-projections-excerpts

Related

Wrong profile is active

#SpringBootApplication
public class SfgDiApplication {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(SfgDiApplication.class, args);
PetController petController = ctx.getBean("petController", PetController.class);
System.out.println("--- The Best Pet is ---");
System.out.println(petController.whichPetIsTheBest());
}
#Controller
#ResponseBody
public class PetController {
public PetController(PetService petService) {
this.petService = petService;
}
private final PetService petService;
#GetMapping("pet-type")
public String whichPetIsTheBest(){
return petService.getPetType();
}
}
public interface PetService {
String getPetType();
}
#Service("cat")
public class CatPetService implements PetService {
#Override
public String getPetType() {
return "Cats Are the Best!";
}
}
#Profile({"dog", "default"})
public class DogPetService implements PetService {
public String getPetType(){
return "Dogs are the best!";
}
}
application.properties
spring.profiles.active=dog
Result
--- The Best Pet is ---
Cats Are the Best!
I don't understand why cats are here. I can even comment the properties out, but cats are still here. I would like to see dogs.
What can I try next?
It looks like the DogService is not a bean. So in the end you only have a single bean (CatService) and this one will be picked all the time.
You should define a bean of DogPetService with #service.
You should add the cat profile to CatPetService.
Like this :
#Service("cat")
#Profile({"cat"})
public class CatPetService implements PetService {
#Override
public String getPetType() {
return "Cats Are the Best!";
}
}
#Profile({"dog", "default"})
#Service("dog")
public class DogPetService implements PetService {
public String getPetType(){
return "Dogs are the best!";
}
}
I think you can achieve what you want like this:
#Configuration("petConfig")
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class PetConfig {
#Value("${spring.profiles.active}")
private String type;
#Bean(name = "petService")
#Primary
public PetService petService() {
PetService petService = null;
if ("cat".equals(type)) {
petService = new CatPetService();
}
if ("dog".equals(type)) {
petService = new DogPetService();
}
return petService;
}
}
Annotate both CatPetService and DogPetService with #Service. This way you can easily adapt the code without hardcodings and duplication.

Why does CachePut not work in this example?

I am playing around with the Spring framework and I would like to get my name returned from the cache. After 5 seconds I will update the cache and I hope to receive a new name.... unfortunately this is not working.... why?
#Component
public class Test {
public String name = "peter";
#Cacheable(value = "numCache")
public String getName() {
return name;
}
#Scheduled(fixedRate = 5000)
#CachePut(value = "numCache")
public String setName() {
this.name = "piet";
return name;
}
}
#Component
public class AppRunner implements CommandLineRunner {
public void run(String... args) throws Exception {
Test test = new Test();
while(true) {
Thread.sleep(1000);
System.out.println(test.getName());
}
}
}
#SpringBootApplication
#EnableCaching
#EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
You are creating an instance of Test yourself with new, you are not autowiring it. I would try like this:
#Component
public class Test {
public String name = "peter";
#Cacheable(value = "numCache")
public String getName() {
return name;
}
#Scheduled(fixedRate = 5000)
#CachePut(value = "numCache")
public String setName() {
this.name = "piet";
return name;
}
}
#Component
public class AppRunner implements CommandLineRunner {
#Autowired private Test test;
public void run(String... args) throws Exception {
while(true) {
Thread.sleep(1000);
System.out.println(test.getName());
}
}
}

JUNIT - Null pointer Exception while calling findAll in spring Data JPA

I am new to Junits and Mockito, I am writing a Unit test class to test my service class CourseService.java which is calling findAll() method of CourseRepository.class which implements CrudRepository<Topics,Long>
Service Class
#Service
public class CourseService {
#Autowired
CourseRepository courseRepository;
public void setCourseRepository(CourseRepository courseRepository) {
this.courseRepository = courseRepository;
}
public Boolean getAllTopics() {
ArrayList<Topics> topicList=(ArrayList<Topics>) courseRepository.findAll();
if(topicList.isEmpty())
{
return false;
}
return true;
}
}
Repository class
public interface CourseRepository extends CrudRepository<Topics,Long>{
}
Domain class
#Entity
#Table(name="Book")
public class Topics {
#Id
#Column(name="Topicid")
private long topicId;
#Column(name="Topictitle",nullable=false)
private String topicTitle;
#Column(name="Topicauthor",nullable=false)
private String topicAuthor;
public long getTopicId() {
return topicId;
}
public void setTopicId(long topicId) {
this.topicId = topicId;
}
public String getTopicTitle() {
return topicTitle;
}
public void setTopicTitle(String topicTitle) {
this.topicTitle = topicTitle;
}
public String getTopicAuthor() {
return topicAuthor;
}
public void setTopicAuthor(String topicAuthor) {
this.topicAuthor = topicAuthor;
}
public Topics(long topicId, String topicTitle, String topicAuthor) {
super();
this.topicId = topicId;
this.topicTitle = topicTitle;
this.topicAuthor = topicAuthor;
}
}
Following is the Junit class I have written but courseRepository is getting initialized to NULL and hence I am getting NullPointerException.
public class CourseServiceTest {
#Mock
private CourseRepository courseRepository;
#InjectMocks
private CourseService courseService;
Topics topics;
#Mock
private Iterable<Topics> topicsList;
#Before
public void setUp() {
MockitoAnnotations.initMocks(CourseServiceTest.class);
}
#Test
public void test_Get_Topic_Details() {
List<Topics> topics = new ArrayList<Topics>();
Mockito.when(courseRepository.findAll()).thenReturn(topics);
boolean result=courseService.getAllTopics();
assertTrue(result);
}
}
Change the setUp() method to:
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
Probably you are dealing with some problem on the framework to make the mocked class be injected by the framework.
I recommend to use Constructor Injection, so you don't need to rely on the reflection and #Inject/#Mock annotations to make this work:
#Service
public class CourseService {
private final CourseRepository courseRepository;
// #Autowired annotation is optional when using constructor injection
CourseService (CourseRepository courseRepository) {
this.courseRepository = courseRepository;
}
// .... code
}
The test:
#Test
public void test_Get_Topic_Details() {
List<Topics> topics = new ArrayList<Topics>();
Mockito.when(courseRepository.findAll()).thenReturn(topics);
CourseService courseService = new CourseService(courseRepository);
boolean result = courseService.getAllTopics();
assertTrue(result);
}

How to Restrict access to particular page to only 1 logged in user?

I am making a simple Social Media Website using Java Spring Boot. Now I want to add a profile edit page, where a logged in user can edit/update his profile data but other logged in users should not have access to it.
For example, there are two people John and Tom, John should be able to see only his profile edit page and Tom should see only his Profile edit page Only after login.
How to achieve this using Spring Security or by any other way ?
First of all you need to write BeanAccessor like following:
#Component
public class BeanAccessor implements ApplicationContextAware {
private static ApplicationContext context;
public static ObjectMapper getObjectMapper() {
return getBean(ObjectMapper.class);
}
public static <T> T getBean(Class<T> beanClass, Object... args) {
return context.getBean(beanClass, args);
}
private static <T> T getBean(Class<T> beanClass) {
return context.getBean(beanClass);
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
}
then we need to write new class for method security like:
#Component
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
private Object filterObject;
private Object returnObject;
private Object target;
public CustomMethodSecurityExpressionRoot(Authentication authentication) {
super(authentication);
}
CustomMethodSecurityExpressionRoot setTarget(Object target) {
this.target = target;
return this;
}
#Override
public void setFilterObject(Object filterObject) {
this.filterObject = filterObject;
}
#Override
public Object getFilterObject() {
return filterObject;
}
#Override
public void setReturnObject(Object returnObject) {
this.returnObject = returnObject;
}
#Override
public Object getReturnObject() {
return returnObject;
}
#Override
public Object getThis() {
return target;
}
}
finally we need custom method security expressinon handler:
#Component
public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
#Autowired
private CustomPermissionEvaluator customPermissionEvaluator;
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
#Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) {
final CustomMethodSecurityExpressionRoot root = BeanAccessor.getBean(CustomMethodSecurityExpressionRoot.class, authentication);
root.setPermissionEvaluator(customPermissionEvaluator);
root.setTrustResolver(this.trustResolver);
root.setRoleHierarchy(getRoleHierarchy());
root.setTarget(invocation.getThis());
return root;
}
}
now on your controller method yo can define #PreAuthorize("isProfileOwner(#id)") annotations your user profile show page method looks like :
#PreAuthorize("isProfileOwner(#id)")
#GetMapping("{id}")
public String show(#PathVariable("id") Long id, Model model) {
//omitted
}
everything okey but we need to write isProfileOwner() method to our CustomMethodSecurityExpressionRoot class like:
public boolean isProfileOwner(Long id) {
//add logic here and you are ready
}
also you can check this post

Junit test for saving data with JPA

Am trying to make a junit test to save data with JPA. Below is my entity class
#Entity
#Table(name="book")
public class test {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="BOOK_REF_ID",nullable=false)
private int bookRefId;
#Column(name="BOOK_CODE",nullable=false)
private String bookCode;
#Column(name="BOOK_NAME",nullable=false)
private String bookDescription;
public int getBookRefId() {
return bookRefId;
}
public void setBookRefId(int bookRefId) {
this.bookRefId = bookRefId;
}
public String getBookCode() {
return bookCode;
}
public void setBookCode(String bookCode) {
this.bookCode = bookCode;
}
public String getBookDescription() {
return bookDescription;
}
public void setBookDescription(String bookDescription) {
this.bookDescription = bookDescription;
}
}
Service class is
public interface BookService()
{
public Book create(Book book);
}
Repository class is
public interface BookRepository extends
JpaRepository<Book,Integer>
{ }
Service Implementation class is
public BookServiceImpli implements BookService()
{
#Resource
BookRepository repository;
#Override
public Book create(Book book) {
// TODO Auto-generated method stub
return repository.save(book);
}
}
Now my test class is
#RunWith(SpringRunner.class)
#DataJpaTest
#SpringBootTest(classes= {JPAConfig.class})
#AutoConfigureTestDatabase(replace=Replace.NONE)
#TestPropertySource(
locations = "classpath:application.properties")
public class TestBook {
#Autowired
private BookService bookService ;
#Test
public void test() {
Book book = new Book();
book.setBookCode("abc");
book.setBookDescription("safd");
bookService.create(book);
}
Application properties contains password and database details and JPAConfig contain JPA configuration details such as entity scan database details. When am trying to run the test case am getting an error like
A component required a bean of type
'com.repository.sample.BookRepository' that could not be found.
I don't have main method in it.Am new to unit testing please anyone help me to solve the issue.

Resources