Access the service layer from the controller - spring

I am using a REST web service that queries data from Apache Solr. I'm using Spring boot + data with Solr repositories.
I do not know how to interconnect as service, repository, and controller layers.
I have the following structure
Controller:
#RestController
public class ImageSearchController {
private final Logger log = LoggerFactory.getLogger( this.getClass( ) );
#Resource ImageService imageService;
....
}
Main to SpringBootApplication:
#SpringBootApplication
public class Application extends SpringBootServletInitializer{
public static void main( String[ ] args ) {
SpringApplication.run( Application.class , args );
}
}
Service:
public interface ImageService {
List< Image > searchTerm( String searchTerm );
List< Image > search(String imgSrc , String imgTitle);
List< Image > searchByImgSrc( String searchTerm );
List< Image > findAll( );
}
Service Impl:
#Service
public class ImageServiceImpl implements ImageService {
#Resource
private ImageRepository repository;
#Override
public List< Image > search( String imgSrc , String imgTitle ) {
return repository.findByImgSrcContainsOrImgTitleContains( imgSrc , imgTitle );
}
....
}
Repository (Spring Data):
public interface ImageRepository extends SolrCrudRepository< Image , String > {
public List< Image > findAll( );
...
}
Pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.solr/solr-solrj -->
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-solrj</artifactId>
<version>5.1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-solr -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-solr</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
</dependencies>
...
<repositories>
<repository>
<id>spring-releases</id>
<url>https://repo.spring.io/libs-release</url>
</repository>
<repository>
<id>spring-milestone</id>
<name>Spring Milestone Maven Repository</name>
<url>http://repo.springsource.org/libs-milestone</url>
</repository>
</repositories>
Logs of application
How to create the bean and access the controller in the service layer?
How to configure solr client with rest web service ?
UPDATE
I upgraded the spring boot version from 1.4.3 to 1.5.1 and it worked. Honestly, I do not understand why.
Now I get another error, when accessing solr, the spring data duplicates the name of the core in solr. Any idea ?
Thanks

1st you don't need the service interface if you only have one implementation.
You do not access the controller in the service layer it's the other way around. Controller > Service >Repository(solr in your case)
Controller defines your API, Service defines your business layer and repository you should be using to access external resources (solr in your case)
So in the Controller you #Autowire the service and in the serice you #Autowire the Repository

First, you should inject your dependencies. I recommend constructor injection. In your case:
#Service
public class ImageServiceImpl implements ImageService {
private final ImageRepository repository;
#Autowired // no necessary in spring 4.3+
public ImageServiceImpl(ImageRepository repository) {
this.repository = repository;
}

The ImageRepository-Bean could not be found (Maybe you miss #EnableJpaRepositories) so spring is looking for a BeanFactory and finds a SolrRepositoryFactoryBean that requires a constructor-parameter of type (Class). Usually a Scanner of #EnableJpaRepositories constructs the SolrRepositoryFactoryBean but as long as #EnableJpaRepositories is missing (or configured wrong) the ImageRepository could not be constructed.
The Fix:
Add #EnableJpaRepositories to the Application and configure the parameters of the annotation.

Related

NoClassDeffFoundError: org/springframework/data/jpa/repository/Repository

I am using JpaRepository, here is my code
public Interface EmpRepository extends JpaRepository<Employee, Integer> {}
class EmployeeServicImpl {
private EmpRepository empRepository;
#Autowired
EmployeeServicImpl (EmpRepository theRepository) {
this.empRepository = theRepository;
}
}
added below dependencies in my pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
While start the application, resolution of declared constructor of bean from class loader : NoClassDeffFoundError : /org/springframework/data/jpa/repository/Repository
Some points,
Keep your repository interface in a separate package.
Use #Repository on your repository.
Use #Service or #Component annotation at EmployeeServicImpl class.
It appears as when you're starting the application, Spring is trying to find EmpRepository dependency to instantiate your service but is not able to find the repository since it is not declared as a #Repository by you.
Further, reason for using #Service is so that EmployeeServicImpl becomes available to Spring too.

Not able to inject #Service and #Contract dependency in my resource class

On base of the guide from this blog, Roll your own Auto Discovery with Jersey and HK2, I have the follow resource POJO:
#Path("Test")
public class TestResource {
#Inject
private TestService service;
#GET
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Set<Test> getTests() {
return service.getAllTests();
}
}
The TestService:
#Contract
public interface TestService {
public Set<Test> getAllTests();
}
The TestServiceImpl
#Service
public class TestServiceImpl implements TestService {
#Override
public Set<Test> getAllTests() {
Set<Test> tests = new HashSet<>();
Test c = new Test();
c.setName("test");
tests.add(c);
return tests;
}
}
The Jersey dependency in pom.xml is of version 2.25.1
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey</groupId>
<artifactId>jersey-bom</artifactId>
<version>${jersey.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
<!-- use the following artifactId if you don't need servlet 2.x compatibility -->
<!-- artifactId>jersey-container-servlet</artifactId -->
</dependency>
<dependency>
<groupId>org.glassfish.jersey.bundles</groupId>
<artifactId>jaxrs-ri</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.hk2</groupId>
<artifactId>hk2</artifactId>
<version>2.5.0-b36</version>
</dependency>
In order to make Jersey scan the #Service and #Contract classes automatically, I used the inhabitant-generator plugin with version 2.5.0-b36:
<plugin>
<groupId>org.glassfish.hk2</groupId>
<artifactId>hk2-inhabitant-generator</artifactId>
<version>2.5.0-b36</version>
<executions>
<execution>
<goals>
<goal>generate-inhabitants</goal>
</goals>
</execution>
</executions>
</plugin>
There is the corresponding Feature implementation:
public class AutoServiceDiscovery implements Feature {
#Override
public boolean configure(FeatureContext context) {
ServiceLocator locator = ServiceLocatorProvider.getServiceLocator(context);
DynamicConfigurationService dcs = locator.getService(DynamicConfigurationService.class);
Populator populator = dcs.getPopulator();
try {
populator.populate(new ClasspathDescriptorFileFinder(this.getClass().getClassLoader()),
new DuplicatePostProcessor());
} catch (IOException | MultiException ex) {
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
}
return true;
}
}
And it is indeeded registered through my ResourceConfig class:
#ApplicationPath("/*")
public class ApplicationConfig extends ResourceConfig {
public ApplicationConfig() {
packages("resources");
register(new AutoServiceDiscovery());
}
}
However, I send request to the /test, got the following error:
MultiException has 3 exceptions. They are:
1. org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for
injection at SystemInjecteeImpl(requiredType=TestService,parent=TestResource,qualifiers=
{},position=-1,optional=false,self=false,unqualified=null,1947073589)
2. java.lang.IllegalArgumentException: While attempting to resolve the dependencies of
rx.practice.ee.jaxrs.resources.TestResource errors were found
3. java.lang.IllegalStateException: Unable to perform operation: resolve on
rx.practice.ee.jaxrs.resources.TestResource
org.jvnet.hk2.internal.Collector.throwIfErrors(Collector.java:89)
org.jvnet.hk2.internal.ClazzCreator.resolveAllDependencies(ClazzCreator.java:250)
org.jvnet.hk2.internal.ClazzCreator.create(ClazzCreator.java:358)
org.jvnet.hk2.internal.SystemDescriptor.create(SystemDescriptor.java:487)
org.glassfish.jersey.process.internal.RequestScope.findOrCreate(RequestScope.java:162)
...
Question: Anyone knows why the #Service class cannot be injected? I am using Tomcat server
After a couple of days research on the source code of inhabitat-generator, I figured out that in case of web application package,war, the locator file is not generated in META-INF/hk2-locator as demonstracted in the HK2 Inhabitant Generator office site in case of using jar as deployment package. The source code of AbstractInhabitantsGeneratorMojo.java told that in case of war, locator files are generated in hk2-locator, and this is not mentioned in the HK2 Inhabitant Generator office site.
However, when constructing the ClasspathDescriptorFileFinder without the directory names argument in the bootstrap class, AutoServiceDiscovery, it is only compatible with jar as deployment package, meaning it is only finding files in META-INF/hk2-locator.
So the better solution would be not to use inhabitant-generator plugin but the metadata-generator dependency, which is an annotation processor at compile time and, it is proved out-of-the-box.
If someone is persistent to using this plugin, he/she could create his/her own ClasspathDescriptorFileFinder so that it is able to find locator files from hk2-locator
Last but not least, I also tried to use the inhabitants-generator plugin's options to generate the locator files in hk2-locator, but this seems to be next to impossible as well

How to make both validation annotations in a ConfigurationProperties bean and a #FeignClient interface work together?

Let's say I have this application.yml (which will be environment-dependent e.g. via Spring profiles):
app.remote:
url: http://whatever.url.it.is:8080/
and matching Java-style configuration properties class:
#Configuration
#ConfigurationProperties("app.remote")
public class MyRemoteProperties {
#NotBlank
private String url;
// matching getter/setter...
}
I want some kind of client for my remote url:
#Service
#FeignClient(value = "remote", url = "${app.remote.url}")
public interface MyRemote {
#GetMapping("/what/ever/rest/api")
String stuff();
}
Unfortunately I can't get the validation work for MyRemoteProperties e.g. when the app.remote.url property is blank (empty) the application doesn't start (Spring fails at wiring the MyRemote bean) and I get this error:
Caused by: java.lang.IllegalStateException: No Feign Client for
loadBalancing defined. Did you forget to include
spring-cloud-starter-netflix-ribbon?
(and I don't want load-balancing; I assume this is because the URL is empty at some point, then it expects some load-balancer config hence Ribbon here in the error message).
Or maybe I don't known how to plug it into the MyRemote interface's configuration, e.g. I also tried:
#FeignClient(value = "remote", configuration = MyRemoteProperties.class)
But same result.
How do I get this validation thing to work?
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
At some point where the interface is called:
#Service
public RandomServiceOrController {
#Autowired
private MyRemote myRemote;
public void processMyStuff() {
// ...
String myStuff = myRemote.stuff();
// ...
}
}
Don't forget the #Validated annotation on your Java properties class:
#Validated
#Configuration
#ConfigurationProperties("app.remote")
public class MyRemoteProperties {
#NotBlank
private String url;
// matching getter/setter...
}
Your application won't start because of the missing property, not because of a non-defined-loadbalancing-client-you-don't-need (thus making its error message more awkward).

spring boot fails to start-- define a bean of type 'TopicRepository' in configuration

I was following this JavaBrains tutorials of Spring Boot.
My project structure is as follows:
CourseApiApp.java:
#SpringBootApplication
#ComponentScan(basePackages = {
"com.bloodynacho.rishab.topics"
})
#EntityScan("com.bloodynacho.rishab.topics")
public class CourseApiApp {
public static void main(String[] args) {
SpringApplication.run(CourseApiApp.class, args);
}
}
TopicController.java:
#RestController
public class TopicController {
#Autowired
private TopicService topicService;
#RequestMapping(
value = "/topics"
)
public List<Topic> getAllTopcs() {
return topicService.getAllTopics();
}
}
TopicService.java:
#Service
public class TopicService {
#Autowired
private TopicRepository topicRepository;
public List<Topic> getAllTopics() {
List<Topic> topics = new ArrayList<>();
this.topicRepository
.findAll()
.forEach(topics::add);
return topics;
}
}
Topic.java:
#Entity
public class Topic {
#Id
private String id;
private String name;
private String description;
}
TopicRepository.java:
#Repository
public interface TopicRepository extends CrudRepository<Topic, String>{
}
pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
I was using the lombok #Getter, #Getter and #AllArgsConstructor in Topic.java but I removed it after reading one of the answers here.
I read this1, this2, this3
Still, I get
***************************
APPLICATION FAILED TO START
***************************
Description:
Field topicRepository in com.bloodynacho.rishab.topics.TopicService required a bean of type 'com.bloodynacho.rishab.topics.TopicRepository' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'com.bloodynacho.rishab.topics.TopicRepository' in your configuration.
Process finished with exit code 1
EDIT: I read this explaining how even without actually implementing the interface the #Autowired works. I understand the solution, but I don't understand how to solve my issue. Clearly, there is some problem with the way Spring Data is set up and configured (as mentioned in the answer)
Because if your other packages hierarchies are below your main application with the #SpringBootApplication annotation, you’re covered by implicit components scan.
Therefore, one simple solution can be done by following 2 steps:
Rename the package of main class to be com.bloodynacho.rishab.
(That is what I suggest that the complete package name of main app. is supposed to be root of other packages.)
Remove #ComponentScan and #EntityScan annotation.
(Although #ComponentScan is different from #EntityScan, it can be also removed in my experience.)

Jersey setup without web.xml

I'm attempting to set up a simple REST web application that uses Jersey. In the documentation, it seems that I should be able to create my application without using a web.xml file. From the site:
JAX-RS provides a deployment agnostic abstract class Application for declaring root resource and provider classes, and root resource and provider singleton instances. A Web service may extend this class to declare root resource and provider classes.
The example that follows shows this code:
public class MyApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> s = new HashSet<Class<?>>();
s.add(HelloWorldResource.class);
return s;
}
}
To me, this says that I can use an Application class to do all of my servlet setup. This seems to be the configuration that reads my resource class's annotations and sets up the correct URL handling mechanisms. Is that correct? I don't have to do any other setup?
I ask because I created the following and it didn't work (I get a 404 from localhost:8080/{context}/test):
pom.xml:
<dependencies>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-core</artifactId>
<version>1.12</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.12</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.12</version>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
Application class:
#ApplicationPath("/")
public class JerseyTestApp extends Application
{
#Override
public Set<Class<?>> getClasses()
{
final Set<Class<?>> classes = new HashSet<>();
classes.add(JerseyTestController.class);
return classes;
}
}
Resource class:
#Path("/test")
public class JerseyTestController
{
#GET
#Produces(MediaType.TEXT_PLAIN)
public String getTestMsg()
{
return "It works";
}
}
Dumb. All I had to do was include the jersey-servlet jar, as prescribed by this answer.

Resources