Autowiring issue with SpringBoot - java

I have a class like below:
package com.company.data.render.model
#RestController
public class ControllerClass {
#Autowired
ApplicationPropertiesServiceImpl services;
#RequestMapping(value = "/node1", method = RequestMethod.GET)
#ResponseBody
public ParentNode getNode1()
{
Child node = new Child();
List<Map<String, Object>> properties properties = services.getData("A",xxx);
node.addtree();
node.setProperties(properties);
return node;
}
} -------------------------------------------------------------------------------
package com.company.data.service;
#Component
public List<Map<String, Object>> getData(String type,String name)
{
if(type.equalsIgnoreCase("A"))
{
String sql = "select * from data.data_properties(?)";
List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql,host);
return rows;
}else if(properties.equalsIgnoreCase("B"))
{
String sql = "select * from data.application_properties(?)";
List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql,host);
return rows;
}
}
-------------------------------------------------------------------------------
package com.company.data.render.model;
#Component
public class Child {
#Autowired
ApplicationPropertiesServiceImpl services;
public void addtree()
{
List<Map<String, Object>> properties=services.getData("B", "xxy");
}
}
How can I access the getdata() function in Child class.I am getting null pointer exception for service object though I have autowired the ApplicationPropertiesServiceImpl

Seems like you are going to have 2 Controllers.
Clearly your controller is doing too much. And it's not a good idea to inject a controller into another controller.
I suggest you to make a Service and a Repository:
The model is receiving to much data from the Controller so I suggest to create a class to make it more clear, because return a Map is too abstract and make the code hard to read.
public class CarProperties {
private Integer id;
private String name;
private Integer age;
private String color;
//setters and gettters
....
}
Service:
public interface CarPropertiesService {
public List<CarProperties> findAll(String type);
}
#Service("CarPropertiesService")
public class CarPropertiesServiceImpl implements CarPropertiesService {
#Autowired
private CarPropertiesDAO carPropertiesDAO;
public List<CarProperties> findAll(String type) {
List<CarProperties> result = new ArrayList<>();
if ("XXX".equalsIgnoreCase(type)) {
List<Map<String, Object>> carPropertiesList = carPropertiesDAO.findAll();
for(Map<String, Object> carProperties : carPropertiesList) {
result.add(getCarPropertiesInstance(carProperties));
}
}
return result;
}
private CarProperties getCarPropertiesInstance(Map<String, Object> properties) {
CarProperties instance = new CarProperties();
instance.setId(properties.get("id"));
instance.setName(properties.get("name"));
...
return instance;
}
}
DAO:
public interface CarPropertiesDAO {
public List<Map<String, Object>> findAll();
}
#Repository("CarPropertiesDAO")
public class CarPropertiesDAOImpl implements CarPropertiesDAO {
...
public List<Map<String, Object>> findAll() {
String sql = "select * from data.car_properties(?)";
return jdbcTemplate.queryForList(sql,host);
}
}
and finally your controllers would make usage of the service:
#RestController
public class ControllerClass {
#Autowired
private CarPropertiesService carPropertiesService;
#RequestMapping(value = "/node1", method = RequestMethod.GET)
#ResponseBody
public ParentNode getNode1()
{
List<CarProperties> properties = carPropertiesService.findAllByType("XXX");
return properties;
}
#RequestMapping(value = "/prop/{type}/{name}", method = RequestMethod.GET)
#ResponseBody
public List<CarProperties> getData(#PathVariable String type,#PathVariable String name)
{
List<CarProperties> rows = carPropertiesService.findAllByType(type);
...
}
}
#Controller
public class Controller2 {
#Autowired
CarPropertiesService carPropertiesService;
public void addtree(){
List<CarProperties> rows = carPropertiesService.findAllByType("XXX");
}
}
Resuming: Controllers should not worry about the business, but only returning the data. Service should be where the business is, like calculations, data exchange, etc... The DAO class you can clearly see it is used to DB actions. Also keep in mind the access to the DAO objects should be in Services/Facedes, using it in the Controller recommended. So using this structure your code would became more reusable and easy to maintain.

Pls try this :-
#Component
#ComponentScan(basePackages="<provide your base pkg where it will scan for the component ControllerClass>")
#ConditionalOnBean(ControllerClass.class)
public class Child {
#Autowired(required=true)
private ControllerClass controllerClass;
public void addtree(){
controllerClass.getData("XXX",xxx)
}
}

Related

SpringBoot selecting the #Repository based on design pattern and configuration

Small question on Spring Boot, and how to use a design pattern combined with Spring #Value configuration in order to select the appropriate #Repository please.
Setup: A springboot project which does nothing but save a pojo. The "difficulty" is the need to choose where to save the pojo, based on some info from inside the payload request.
I started with a first straightforward version, which looks like this:
#RestController
public class ControllerVersionOne {
#Autowired private ElasticRepository elasticRepository;
#Autowired private MongoDbRepository mongoRepository;
#Autowired private RedisRepository redisRepository;
//imagine many more other repositories
//imagine many more other repositories
//imagine many more other repositories
#PostMapping(path = "/save")
public String save(#RequestBody MyRequest myRequest) {
String whereToSave = myRequest.getWhereToSave();
MyPojo myPojo = new MyPojo(UUID.randomUUID().toString(), myRequest.getValue());
if (whereToSave.equals("elastic")) {
return elasticRepository.save(myPojo).toString();
} else if (whereToSave.equals("mongo")) {
return mongoRepository.save(myPojo).toString();
} else if (whereToSave.equals("redis")) {
return redisRepository.save(myPojo).toString();
// imagine many more if
// imagine many more if
// imagine many more if
} else {
return "unknown destination";
}
}
With the appropriate #Configuration and #Repository for each and every databases. I am showing 3 here, but imagine many. The project has a way to inject future #Configuration and #Repository as well (the question is not here actually)
#Configuration
public class ElasticConfiguration extends ElasticsearchConfiguration {
#Repository
public interface ElasticRepository extends CrudRepository<MyPojo, String> {
#Configuration
public class MongoConfiguration extends AbstractMongoClientConfiguration {
#Repository
public interface MongoDbRepository extends MongoRepository<MyPojo, String> {
#Configuration
public class RedisConfiguration {
#Repository
public interface RedisRepository {
Please note, some of the repositories are not children of CrudRepository. There is no direct ___Repository which can cover everything.
And this first version is working fine. Very happy, meaning I am able to save the pojo to where it should be saved, as I am getting the correct repository bean, using this if else structure.
In my opinion, this structure is not very elegant (if it ok if we have different opinion here), especially, not flexible at all (need to hardcode each and every possible repository, again imagine many).
This is why I went to refactor and change to this second version:
#RestController
public class ControllerVersionTwo {
private ElasticRepository elasticRepository;
private MongoDbRepository mongoRepository;
private RedisRepository redisRepository;
private Map<String, Function<MyPojo, MyPojo>> designPattern;
#Autowired
public ControllerVersionTwo(ElasticRepository elasticRepository, MongoDbRepository mongoRepository, RedisRepository redisRepository) {
this.elasticRepository = elasticRepository;
this.mongoRepository = mongoRepository;
this.redisRepository = redisRepository;
// many more repositories
designPattern = new HashMap<>();
designPattern.put("elastic", myPojo -> elasticRepository.save(myPojo));
designPattern.put("mongo", myPojo -> mongoRepository.save(myPojo));
designPattern.put("redis", myPojo -> redisRepository.save(myPojo));
//many more put
}
#PostMapping(path = "/save")
public String save(#RequestBody MyRequest myRequest) {
String whereToSave = myRequest.getWhereToSave();
MyPojo myPojo = new MyPojo(UUID.randomUUID().toString(), myRequest.getValue());
return designPattern.get(whereToSave).apply(myPojo).toString();
}
As you can see, I am leveraging a design pattern refactoring the if-else into a hashmap.
This post is not about if-else vs hashmap by the way.
Working fine, but please note, the map is a Map<String, Function<MyPojo, MyPojo>>, as I cannot construct a map of Map<String, #Repository>.
With this second version, the if-else is being refactored, but again, we need to hardcode the hashmap.
This is why I am having the idea to build a third version, where I can configure the map itself, via a spring boot property #Value for Map:
Here is what I tried:
#RestController
public class ControllerVersionThree {
#Value("#{${configuration.design.pattern.map}}")
Map<String, String> configurationDesignPatternMap;
private Map<String, Function<MyPojo, MyPojo>> designPatternStrategy;
public ControllerVersionThree() {
convertConfigurationDesignPatternMapToDesignPatternStrategy(configurationDesignPatternMap, designPatternStrategy);
}
private void convertConfigurationDesignPatternMapToDesignPatternStrategy(Map<String, String> configurationDesignPatternMap, Map<String, Function<MyPojo, MyPojo>> designPatternStrategy) {
// convert configurationDesignPatternMap
// {elastic:ElasticRepository, mongo:MongoDbRepository , redis:RedisRepository , ...}
// to a map where I can directly get the appropriate repository based on the key
}
#PostMapping(path = "/save")
public String save(#RequestBody MyRequest myRequest) {
String whereToSave = myRequest.getWhereToSave();
MyPojo myPojo = new MyPojo(UUID.randomUUID().toString(), myRequest.getValue());
return designPatternStrategy.get(whereToSave).apply(myPojo).toString();
}
And I would configure in the property file:
configuration.design.pattern.map={elastic:ElasticRepository, mongo:MongoDbRepository , saveToRedis:RedisRepositry, redis:RedisRepository , ...}
And tomorrow, I would be able to configure add or remove the future repository target.
configuration.design.pattern.map={elastic:ElasticRepository, anotherElasticKeyForSameElasticRepository, redis:RedisRepository , postgre:PostGreRepository}
Unfortunately, I am stuck.
What is the correct code in order to leverage a configurable property for mapping a key with it's "which #Repository to use" please?
Thank you for your help.
You can create a base repository to be extended by all your repositories:
public interface BaseRepository {
MyPojo save(MyPojo onboarding);
}
so you will have a bunch of repositories like:
#Repository("repoA")
public interface ARepository extends JpaRepository<MyPojo, String>, BaseRepository {
}
#Repository("repoB")
public interface BRepository extends JpaRepository<MyPojo, String>, BaseRepository {
}
...
Those repositories will be provided by a factory:
public interface BaseRepositoryFactory {
BaseRepository getBaseRepository(String whereToSave);
}
that you must configure in a ServiceLocatorFactoryBean:
#Bean
public ServiceLocatorFactoryBean baseRepositoryBean() {
ServiceLocatorFactoryBean serviceLocatorFactoryBean = new ServiceLocatorFactoryBean();
serviceLocatorFactoryBean.setServiceLocatorInterface(BaseRepositoryFactory.class);
return serviceLocatorFactoryBean;
}
Now you can inject the factory wherever you need and get the repo want:
#Autowired
private BaseRepositoryFactory baseRepositoryFactory;
...
baseRepositoryFactory.getBaseRepository("repoA").save(myPojo);
...
Hope it helps.
Short answer:
create a shared interface
create multiple sub-class of this interface (one per storage) using different spring component names
Use a map to deal with aliases
use Spring context to retrieve the right bean by alias (instead of creating a custom factory)
Now adding a new storage is only adding a new Repository classes with a name
Explanation:
As mentioned in the other answer you first need to define a common interface as you can't use the CrudRepository.save(...).
In my example I reuse the same signature as the save method to avoid re-implementing it in the sub-classes of CrudRepository.
public interface MyInterface<T> {
<S extends T> S save(S entity);
}
Redis Repository:
#Repository("redis") // Here is the name of the redis repo
public class RedisRepository implements MyInterface<MyPojo> {
#Override
public <S extends MyPojo> S save(S entity) {
entity.setValue(entity.getValue() + " saved by redis");
return entity;
}
}
For the other CrudRepository no need to provide an implementation:
#Repository("elastic") // Here is the name of the elastic repo
public interface ElasticRepository extends CrudRepository<MyPojo, String>, MyInterface<MyPojo> {
}
Create a configuration for your aliases in application.yml
configuration:
design:
pattern:
map:
redis: redis
saveToRedisPlease: redis
elastic: elastic
Create a custom properties to retrieve the map:
#Component
#ConfigurationProperties(prefix = "configuration.design.pattern")
public class PatternProperties {
private Map<String, String> map;
public String getRepoName(String alias) {
return map.get(alias);
}
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
}
Now create the version three of your repository with the injection of SpringContext:
#RestController
public class ControllerVersionThree {
private final ApplicationContext context;
private PatternProperties designPatternMap;
public ControllerVersionThree(ApplicationContext context,
PatternProperties designPatternMap) {
this.context = context;
this.designPatternMap = designPatternMap;
}
#PostMapping(path = "/save")
public String save(#RequestBody MyRequest myRequest) {
String whereToSave = myRequest.getWhereToSave();
MyPojo myPojo = new MyPojo(UUID.randomUUID().toString(), myRequest.getValue());
String repoName = designPatternMap.getRepoName(whereToSave);
MyInterface<MyPojo> repo = context.getBean(repoName, MyInterface.class);
return repo.save(myPojo).toString();
}
}
You can check that this is working with a test:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.HttpEntity;
import static org.junit.jupiter.api.Assertions.assertEquals;
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ControllerVersionThreeTest {
#LocalServerPort
private int port;
#Autowired
private TestRestTemplate restTemplate;
#Test
void testSaveByRedis() {
// Given: here 'redis' is the name of the spring beans
HttpEntity<MyRequest> request = new HttpEntity<>(new MyRequest("redis", "aValue"));
// When
String response = restTemplate.postForObject("http://localhost:" + port + "/save", request, String.class);
// Then
assertEquals("MyPojo{value='aValue saved by redis'}", response);
}
#Test
void testSaveByRedisAlias() {
// Given: here 'saveToRedisPlease' is an alias name of the spring beans
HttpEntity<MyRequest> request = new HttpEntity<>(new MyRequest("saveToRedisPlease", "aValue"));
// When
String response = restTemplate.postForObject("http://localhost:" + port + "/save", request, String.class);
// Then
assertEquals("MyPojo{value='aValue saved by redis'}", response);
}
}
Have you tried creating a configuration class to create your repository map
#Configuration
public class MyConfiguration {
#Bean
public Map repositoryMap() {
Map<String, ? extends Repository> repositoryMap = new HashMap<>();
repositoryMap.put('redis', new RedisRepository());
repositoryMap.put('mongo', new MongoRepository());
repositoryMap.put('elastic', new ElasticRepository());
return Collections.unmodifiableMap(repositoryMap);
}
}
Then you could have the following in your rest controller
#RestController
#Configuration
public class ControllerVersionFour {
#Autowired
private Map<String, ? extends Repository> repositoryMap;
#PostMapping(path = "/save/{dbname}")
public String save(#RequestBody MyRequest myRequest, #PathVariable("dbname") String dbname) {
MyPojo myPojo = new MyPojo(UUID.randomUUID().toString(), myRequest.getValue());
return repisitoryMap.get(dbname).save(myPojo);
}
It might be better to have the db as a path/query parameter instead of having it in the request body. That way you may or may not be able to just save the request body depending on your use case instead of creating another pojo.
This post may also be useful for autowiring a map

Factory design patter Spring Boot double bean

#Component
public abstract class CommandBase {
#Autowired
WebServiceProxy nbiService;
#Autowired
OperationCacheRepository cacheRepository;
public CommandBase(
WebServiceProxy nbiService,
OperationCacheRepository cacheRepository) {
this.nbiService = nbiService;
this.cacheRepository = cacheRepository;
}
public abstract void executeSPV(SpeedTestDTO stDTO) throws NBIException;
public abstract long executeGPV(long guid, OperationCache operationCache) throws NBIException;
#Slf4j
public class DownloadDiagnosticsCommand extends CommandBase {
public DownloadDiagnosticsCommand(WebServiceProxy nbiService, OperationCacheRepository cacheRepository) {
super(nbiService, cacheRepository);
}
#Override
public void executeSPV(SpeedTestDTO stDTO) throws NBIException {
// some executable code
}
#Override
public long executeGPV(long guid, OperationCache operationCache) throws NBIException {
// some executable code
}
}
#Slf4j
public class UploadDiagnosticsCommand extends CommandBase {
public UploadDiagnosticsCommand(WebServiceProxy nbiService, OperationCacheRepository cacheRepository) {
super(nbiService, cacheRepository);
}
#Override
public void executeSPV(SpeedTestDTO stDTO) throws NBIException {
// some executable code
}
#Override
public long executeGPV(long guid, OperationCache operationCache) throws NBIException {
//some executable code
}
}
#Component
public class RFACommandFactory {
#Autowired
WebServiceProxy nbiServiceProxy;
#Autowired
OperationCacheRepository cacheRepository;
public final CommandBase createCommand(final String measureType) {
if ("download".equalsIgnoreCase(measureType)) {
return new DownloadDiagnosticsCommand(nbiServiceProxy, cacheRepository);
} else if ("upload".equalsIgnoreCase(measureType)) {
return new UploadDiagnosticsCommand(nbiServiceProxy, cacheRepository);
}
return null;
}
}
Calling method executeSPV from abstract class
#RestController
#RequestMapping("/rfa/speedtest/v1")
#Slf4j
public class Controller {
#Autowired
CommandBase command;
#Autowired
RFACommandFactory rfaCommandFactory;
#PostMapping(value = "{id}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
private ResponseEntity<String> post(
#PathVariable String assetId,
#RequestBody Payload payload) {
log.info("Received new payload:{}", payload);
command = rfaCommandFactory.createCommand(speedTestDTO.getType());
try {
command.executeSPV(speedTestDTO);
} catch (NBIException e) {
log.info("NBIException", e);
return new ResponseEntity(payload, HttpStatus.BAD_REQUEST);
}
return new ResponseEntity(payload, HttpStatus.CREATED);
}
}
If I remove #Componet from Upload and Download classes I receive error I need to add Bean for abstrcat class CommndBase
If I use #Compoment on Upload and Download classes I receive dual Bean is useed...
Field command in .Controller required a single bean, but 2 were found:
You should not use #Component for abstract class, because Spring context will not be able to initialize that bean. You should remove it then.
Another thing is the way you want to implement a factory pattern here - I recommend you the way described here: https://stackoverflow.com/a/39361500/14056755, refactored version https://stackoverflow.com/a/55060326/14056755.

Using JPA with multiple AND operations

I'm working on a Spring app and defining various find methods on a repository:
#Repository
public interface TicketRepository extends JpaRepository<TicketEntity, Long> {
List<TicketEntity> findByTicketId(#Param("ticketId") Long ticketId);
List<TicketEntity> findByTicketIdAndState(#Param("ticketId") Long ticketId, #Param("state") String state);
List<TicketEntity> findByTicketIdAndStateAndFlagged(#Param("ticketId") Long ticketId, #Param("state") String state, #Param("flagged") String Flagged);
}
The problem is that I have 30 columns which can be optionally filtered on. This is will result in the repository methods becoming unwieldy:
List<TicketEntity> findByTicketIdAndStateAndFlaggedAndCol4AndCol5AndCol6AndCol7AndCol8AndCol9AndCol10AndCol11AndCol12AndCol13AndCol14AndCol15AndCol16AndCol17AndCol18AndCol19AndCol120....);
How should the JPA layer be designed to cater for this scenario ?
If I create an object with attributes:
public class SearchObject {
private String attribute1;
//Getter and Setters
.
.
.
.
}
Can I pass SearchObject into a a find method and Spring JPA will determine which attributes to insert AND statements for depending on which attributes are Null - if the attribute is not null a corresponding AND is generated for that attribute.
Create filter object that will contain all optional columns e.g.:
#AllArgsConstructor
public class TicketFilter {
private final String col1;
private final Integer col2;
public Optional<String> getCol1() {
return Optional.ofNullable(col1);
}
public Optional<Integer> getCol2() {
return Optional.ofNullable(col2);
}
}
Extend your Respoitory with JpaSpecificationExecutor
Create specification class:
public class TicketSpecification implements Specification {
private final TicketFilter ticketFilter;
public TicketSpecification(TicketFilter ticketFilter) {
this.ticketFilter = ticketFilter;
}
#Override
public Predicate toPredicate(Root<Ticket> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
ticketFilter.getTitle().ifPresent(col1 -> predicates.add(getCol1Predicate(root, col1)));
ticketFilter.getDescription().ifPresent(col2 -> predicates.add(getCol2Predicate(root, col2)));
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
}
private Predicate getCol1Predicate(Root root, String title) {
return root.get("col1").in(col1);
}
}
Use your repository: ticketRepository.findAll(specification);
Use Spring Data JPA Specification
Detail Solution be patient
First create a SpecificationCriteria class to define your criterias means filtering column as key and filtering value as value
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class SpecificationCriteria {
private String key;
private Object value;
}
Then create SpecificationCriteriaBuilder to build your Criteria
#Service
public class SpecificationCriteriaBuilder {
public List<SpecificationCriteria> buildCriterias(String name) {
List<SpecificationCriteria> specificationCriterias = new ArrayList<SpecificationCriteria>();
if (!StringUtils.isEmpty(name)) {
specificationCriterias
.add(SpecificationCriteria.builder().key("name")
.value(name).build());
}
// Here you can add other filter one by one
return specificationCriterias;
}
}
Then create a SpecificationBuilder class to build your specifications.
You can build from the list of filter options(Criteria) to List of specification
import java.util.List;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
#Service
public class SpecificationBuilder<T> {
public Specification<T> buildSpecification(List<SpecificationCriteria> specificationCriterias) {
if (ObjectUtils.isEmpty(specificationCriterias)) {
return null;
}
Specification<T> specification = getSpecification(specificationCriterias.get(0));
for (int index = 1; index < specificationCriterias.size(); index++) {
SpecificationCriteria specificationCriteria = specificationCriterias.get(index);
specification =
Specification.where(specification).and(getSpecification(specificationCriteria));
}
return specification;
}
public Specification<T> getSpecification(SpecificationCriteria specificationCriteria) {
Specification<T> specification = new Specification<T>() {
private static final long serialVersionUID = 2089704018494438143L;
#Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
return builder.equal(root.get(specificationCriteria.getKey()),
specificationCriteria.getValue());
}
};
return specification;
}
}
In service first build criteria and then build specification using them. Then use specifications in repository call
#Service
#Transactional
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class UserService {
private final SpecificationCriteriaBuilder criteriaBuilder;
private final SpecificationBuilder<User> specificationBuilder;
private final UserRepository userRepository;
public List<User> getAll(String name) {
List<SpecificationCriteria> specificationCriterias =
criteriaBuilder.buildCriterias(name); // here you can pass other parameter as function argument
Specification<User> specification =
specificationBuilder.buildSpecification(specificationCriterias);
List<User> users = userRepository.findAll(specification);// pass the specifications
return users;
}
Repository extend JpaSpecificationExecutor
#Repository
public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> {
}

I Injected the service Interface in Resource but not able to figure out how to configure Resource Method Calling

enter image description hereThis is the code I wrote to start the Resource. Not able to register the Resouce Class. Used Mongo database and the basics of #Injected
#JsonIgnoreProperties(ignoreUnknown = true)
public class Student extends BaseModel {
private String firstName;
private String lastName;
private String marks;
private long dob;
#Email
private String email;
public Student() {
}
public Student(String firstName, String lastName, String marks, long dob, #Email String email) {
this.firstName = firstName;
this.lastName = lastName;
this.marks = marks;
this.dob = dob;
this.email = email;
}
//Getter Setter method
}
//Base Repository Interface
public interface BaseRepository<T extends BaseModel> extends GenericRepository<T> {
T save (T model);
List<T> getAll();
T getById(String Id);
void deleteById(String Id);
T updateById(String Id, T model);
}
Abstract Repository Method to perform the Basics method of CRUD Operation
public BaseRepositoryImpl(MongoDb mongoManager, Class<T> clazz) throws Exception{
this.mongoManager = mongoManager;
collectionName = clazz.getAnnotation(CollectionName.class);
collection = mongoManager.getMongoCollection(collectionName.name());
entityClass = clazz;
}
#Override
public T save(T model) {
if(model == null){
throw new RuntimeException("No data Found");
}
Object id = collection.save(model).getUpsertedId();
if(id != null && id instanceof ObjectId){
model.setId(((ObjectId) id).toStringMongod());
}
return model;
}
}
//service
public interface StudentService {
Student save(Student student);
}
//Resource
WebService
#Path("/student")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
#HK2Managed
#Timed
public class StudentResource {
#Inject
StudentService studentService;
/* #Inject
public StudentResource(StudentService studentService) {
this.studentService = studentService;
}*/
#Path("/getName")
#GET
#Produces(MediaType.APPLICATION_JSON)
public String getName(){
return "Puru";
}
}
Configuration of the Application to Register the Uri and port of the Main Class
private String template;
// private StudentService studentService;
private String defaultName = "Stranger";
public MongoConfiguration getMongoConfiguration() {
return mongoConfiguration;
}
public void setMongoConfiguration(MongoConfiguration mongoConfiguration) {
this.mongoConfiguration = mongoConfiguration;
}
#Valid
#JsonProperty("mongoserver")
public MongoConfiguration mongoConfiguration;
}
//Application Connector to bind the Class in interface of the Respected Class
public class ApplicationConnector extends AbstractModule /*AbstractBinder*/ {
private final ConfigurationContext context;
public ApplicationConnector(ConfigurationContext context) {
this.context = context;
}
#PostConstruct
#Override
protected void configure() {
bind(StudentService.class).to(StudentServiceImpl.class);
bind(StudentRepository.class).to(StudentRepositoryImpl.class);
}
// applicaytion Connector Class
public class App extends Application<AppConfiguration> {
GuiceBundle<AppConfiguration> guiceBundle = null;
private JAXWSBundle jaxwsBundle = new JAXWSBundle();
public static void main(final String[] args) throws Exception {
new App().run(args);
}
private JAXWSBundle<Object> jaxWsBundle = new JAXWSBundle<>("/api");
#Override
public String getName() {
return "student-DropWizard-demo";
}
#Override
public void initialize(final Bootstrap<AppConfiguration> bootstrap) {
// TODO: application initializatio
// bootstrap.getObjectMapper().enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
// bootstrap.addBundle(GuiceBundle.<AppConfiguration>builder()
// .enableAutoConfig("package.to.scan")
// .searchCommands(true)
// .build()
// );
/* GuiceBundle.Builder builder = GuiceBundle.builder() .noDefaultInstallers()
.enableAutoConfig("com.dzone");
guiceBundle = builder.build();*/
// bootstrap.addBundle(GuiceBundle.builder().enableAutoConfig("com.dzone")
// .build());
// Module[] modules = autoDiscoverModules();
bootstrap.getObjectMapper().registerSubtypes(DefaultServerFactory.class);
//bootstrap.getObjectMapper().registerModule(new FCSerializerModule());
bootstrap.getObjectMapper().enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
bootstrap.addBundle(jaxwsBundle);
GuiceBundle.Builder builder = GuiceBundle.builder()
// .modules(modules)
.noDefaultInstallers()
.installers(new Class[]{LifeCycleInstaller.class,
ManagedInstaller.class,
JerseyFeatureInstaller.class, ResourceInstaller.class,
JerseyProviderInstaller.class,
EagerSingletonInstaller.class,
HealthCheckInstaller.class,
TaskInstaller.class,
PluginInstaller.class
})
.enableAutoConfig(ApplicationConnector.class.getPackage().getName());
postInitialize(bootstrap, builder);
guiceBundle = builder.build();
bootstrap.addBundle(guiceBundle);
}
#Override
public void run(final AppConfiguration configuration,
final Environment environment) throws Exception {
// TODO: implement application
// FilterRegistration.Dynamic dFilter = environment.servlets().addFilter("student", CrossOriginFilter.class);
// AbstractServerFactory sf = (AbstractServerFactory) configuration.getServerFactory();
// Endpoint e = jaxWsBundle.publishEndpoint(
// new EndpointBuilder("student", new StudentResource()));
// environment.jersey().register(new StudentResource());
//environment.jersey().register(new StudentServiceImpl());
//environment.jersey().packages("service");
// environment.jersey().disable();
// environment.servlets().addServlet(StudentResource.class).addMapping("/student");
environment.getJerseyServletContainer().getServletInfo();
//environment.servlets().setBaseResource("");
///environment.servlets().addServlet("StudentResource",StudentResource.class);
//environment.jersey().register(new ResourceInstaller());
postRun(configuration,environment);
}
protected void postRun(final AppConfiguration configuration, final Environment environment) throws Exception {
// Sub-classes should
}
protected void postInitialize(Bootstrap<AppConfiguration> bootstrapm, GuiceBundle.Builder guiceBuilder) {
// Sub-classes should
}
/*public Module[] autoDiscoverModules() {
Reflections reflections =
new Reflections(
new ConfigurationBuilder()
.forPackages(
"com.dzone"));
Set<Class<? extends AbstractModule>> classes = reflections.getSubTypesOf(AbstractModule.class);
List<Module> discoveredModules = new ArrayList<>();
for (Class clazz : classes) {
try {
AbstractModule module = (AbstractModule) clazz.newInstance();
discoveredModules.add(module);
} catch (Exception e) {
e.printStackTrace();
}
}
return discoveredModules.toArray(new Module[]{});
}
}
This is the output of the Code. It's not able to register the the resource of the application
WARN [2019-09-09 05:32:31,707] io.dropwizard.setup.AdminEnvironment:
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! THIS APPLICATION HAS NO HEALTHCHECKS. THIS MEANS YOU WILL NEVER KNOW !
! IF IT DIES IN PRODUCTION, WHICH MEANS YOU WILL NEVER KNOW IF YOU'RE !
! LETTING YOUR USERS DOWN. YOU SHOULD ADD A HEALTHCHECK FOR EACH OF YOUR !
! APPLICATION'S DEPENDENCIES WHICH FULLY (BUT LIGHTLY) TESTS IT. !
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
INFO [2019-09-09 05:32:31,711] org.eclipse.jetty.server.handler.ContextHandler: Started i.d.j.MutableServletContextHandler#615db358{/,null,AVAILABLE}
INFO [2019-09-09 05:32:31,718] org.eclipse.jetty.server.AbstractConnector: Started application#f9cab00{HTTP/1.1,[http/1.1]}{0.0.0.0:8099}
INFO [2019-09-09 05:32:31,719] org.eclipse.jetty.server.AbstractConnector: Started admin#10272bbb{HTTP/1.1,[http/1.1]}{0.0.0.0:8091}
INFO [2019-09-09 05:32:31,719] org.eclipse.jetty.server.Server: Started #5573ms
INFO [2019-09-09 05:32:31,719] com.roskart.dropwizard.jaxws.JAXWSEnvironment: No JAX-WS service endpoints were registered.
In the example above you declare Resource as HK managed:
#HK2Managed
#Timed
public class StudentResource {
Which means that HK2 (jersey's DI) will instantiate object and not guice.
But your service is declared in guice module:
public class ApplicationConnector extends AbstractModule /*AbstractBinder*/ {
private final ConfigurationContext context;
public ApplicationConnector(ConfigurationContext context) {
this.context = context;
}
#PostConstruct
#Override
protected void configure() {
bind(StudentService.class).to(StudentServiceImpl.class);
bind(StudentRepository.class).to(StudentRepositoryImpl.class);
}
So it's normal that your service can't inject StudentService as HK2 is not aware of guice dependencies.
This could be fixed by activating HK2-guice bridge so HK could use guice bindings:
Add dependency: org.glassfish.hk2:guice-bridge:2.5.0-b32 (version must match HK2 version, used by dropwizard)
Enable option: .option(GuiceyOptions.UseHkBridge, true)
In your github project (student-gradle) you have an opposite situation:
Resource is guice managed (guice creates resource instance)
#Path("/student")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
#Timed
public class StudentResource {
And service is declared in HK:
public class ApplicationConnector extends /*AbstractModule*/ AbstractBinder {
#PostConstruct
#Override
protected void configure() {
bind(StudentService.class).to(StudentServiceImpl.class);
bind(StudentRepository.class).to(StudentRepositoryImpl.class);
}
}
(but this module is actually never registered)
My suggestion is to "live" in guice context: don't use #HKManaged (it was added only for edge cases) and use guice modules for services declaration. This way guice will be responsible for all DI related to your code.

Spring Data REST JPA: Integrate automatic CRUD operations with manual controller

I'm using Spring Data REST JPA to build a RESTful web service. So far, Spring is auto-generating all the responses for all the possible methods and for listing all the resources available and even for searches over them:
#RepositoryRestResource(collectionResourceRel = "scans", path = "scans")
public interface ScanRepository extends PagingAndSortingRepository<Scan, Long> {
List<Scan> findByProjectId(#Param("pid") String pid);
}
Now I would like to modify what is returned "only" to POST requests while leaving intact the support to all the others.
I thought I'd create a controller for this purpose like the following:
#Controller
public class ScanController {
#RequestMapping(value = "/scans", method = POST, produces = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody Result parseScan(#RequestParam String projectId, #RequestParam String tool) {
return null;
}
However when I do this, the JPA-data auto-generated responses for all the other methods and searches etc. ceases to exist. For instance, I get "Method not allowed" if I forward a GET request.
Besides, how could I access a JSON payload from the controller?
UPDATE
Now only one of the exposed resource does back to the default methods for requests not manually handled in my own controller. However I have no idea why it does and why this doesn't happen for any of the other resources.*
Despite they all only differ in their entity's attributes.
The following particular resource is the one that does back to the default request handlers for anything that is not POST scan/ or GET /scan/// which I declared in the controller:
#Controller
public class ScanController {
#Autowired
private ScanService scanService;
#RequestMapping(
value = "/scan",
method = POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody
Scan parseScan(#RequestBody Scan rbody) {
<...do something...>
}
#RequestMapping(value = "/scans/{id}/{totvuln}/{nth}", method = RequestMethod.GET,
produces = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody
Scan getScan(#PathVariable String id, #PathVariable int totvuln, #PathVariable int nth) throws ScanNotFound {
<...do something...>
}
It has the following repository interface:
public interface ScanRepository extends PagingAndSortingRepository<Scan, Long> {}
and the following service:
#Service
public class ScanServiceImpl implements ScanService {
#Resource
private ScanRepository scanRepository;
#Resource
private ResultRepository resultRepository;
#Override
#Transactional
public Scan create(Scan shop) {
<some code>
}
#Override
#Transactional
public Scan findById(long id) {
<some code>
}
#Override
#Transactional(rollbackFor = ScanNotFound.class)
public Scan delete(long id) throws ScanNotFound {
<some code>
}
#Override
#Transactional
public List<Scan> findAll() {
<some code>
}
#Override
#Transactional(rollbackFor = ScanNotFound.class)
public Scan update(Scan scan) throws ScanNotFound {
<some code>
}
}
and the resource itself has the following attributes:
#Entity
public class Scan {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private Long projectId;
#OneToMany
private Collection<Result> result;
private int totV;
<getters and setters>
}
While the following semi-identical resource "Rules" does not back to any of the default request handlers. It returns "Method not Allowed" for anything different from POST /rule:
#Controller
public class RulesController {
#Autowired
private RulesService rService;
#Resource
private ScanRepository scanRepository;
#RequestMapping(
value = "/rule",
method = POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody
Rules generateRules(#RequestBody Scan rbody) throws Exception {
<do something>
}
}
It has the same repository interface:
public interface RulesRepository extends PagingAndSortingRepository<Rules, Long> {}
and also the same service implementation:
#Service
public class RulesServiceImpl implements RulesService {
#Resource
private RulesRepository rRepository;
#Resource
private ResultRepository resultRepository;
#Override
#Transactional
public Rules create(Rules shop) {
<do something>
}
#Override
#Transactional
public Rules findById(long id) {
<do something>
}
#Override
#Transactional(rollbackFor = RulesNotFound.class)
public Rules delete(long id) throws RulesNotFound {
<do something>
}
#Override
#Transactional
public List<Rules> findAll() {
<do something>
}
#Override
#Transactional
public Rules findByScanId(long id) throws RulesNotFound {
<do something>
}
#Override
#Transactional(rollbackFor = RulesNotFound.class)
public Rules update(Rules scan) throws RulesNotFound {
<do something>
}
}
and the resource Rules itself has the following attributes:
#Entity
public class Rules {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne
private Scan scan;
#OneToMany
private Collection<Result> result;
private String rules;
<getters and setters>
}
Why isn't Spring exposing the default request handlers also for "Rules" for any request that hasn't been specified manually in my controller class?
I would truly appreciate if you could point out why. Thank you so much!
I've figured out how to access a JSON payload from the controller:
#RequestMapping(
value = "/scan",
method = POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody
Scan parseScan(#RequestBody Scan rbody) {
Scan scan = new Scan();
scan.setProjectId(rbody.getProjectId());
scan.setTool(rbody.getTool());
return scan;
}
Also I've realised the automatic CRUD operations were actually being already supported for every request not handled by my own controller: I was just requesting the wrong URL.
I got the list of correct URLs to use by requesting "curl http://localhost:8080"
However, the preferred URL for any of the auto-generated operations can be set with
#RepositoryRestResource(collectionResourceRel = pref_URL_suffix, path = pref_URL_suffix)
^^somehow during all the changes I tried, that line above went missing.

Categories

Resources