Microservice with Spring Boot - spring-boot

I'm working in Windows 7. I've Spring CLI v1.5.3.RELEASE installed. In a working directory, using command
spring init --build maven --groupId com.redhat.examples
--version 1.0 --java-version 1.8 --dependencies web
--name hola-springboot hola-springboot
I created holo-springboot app. Then navigated to hola-springboot directory,ran
$ mvn spring-boot:run
The application run. Going to http://localhost:8080, I do see Whitelabel error page. Whereafter, I tried to add helloworld fuctionality. That is, in the app, in the packeage com.example, I included the following java class.
#RestController
#RequestMapping("/api")
public class HolaRestController {
#RequestMapping(method = RequestMethod.GET, value = "/hola",
produces = "text/plain")
public String hola() throws UnknownHostException {
String hostname = null;
try {
hostname = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
hostname = "unknown";
}
return "Hola Spring Boot de " + hostname;
}
}
Re-built from hola-springboot dircetory,
mvn clean package
I get build failure as at
https://pastebin.com/77Ru0w52
I'm unable to figure out. Could somebody help?
I'm following the book Microservices for Java Developers by Christian Posta, Chapter 2, available free at developers Redhat.

Looks like you are missing a dependency on spring boot starter web in your maven pom.xml file https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web/1.5.3.RELEASE.
Or you are not importing the classes correctly.

You are accessing http://localhost:8080 but you have defined a mapping in your rest controller "/hola". So you will have to access the url http://localhost:8080/hola as you do not have any default method in your rest controller.

BuildFailure shows that you have not given import statements in you Class. statements missing are the below
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.net.InetAddress;
import java.net.UnknownHostException;
include these and you will be fine.

Related

Getting 404 Not Found for basic spring rest api tutorial - 'hello world'

I am trying to create a rest api web application that can be eventually deployed into a container environment. I downloaded quite a few tutorials from spring.io to other websites as well but each time I use the exact repos I get a 404 error for the simple request.
To simplify it further I reduced it to 2 classes in one package:
project hierarchy
Main class:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
#SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
Controller Class:
#RestController
#RequestMapping("/")
public class RESTController {
private final AtomicLong counter = new AtomicLong();
private static final String template = "Hello, %s!";
/*
* REST API Test Methods
*/
#RequestMapping( "/greeting" )
public String greeting() {
return "It's working...!";
}
And of course the actual request:
http://localhost:8080/test_rest_api/greeting
HTTP Status 404 – Not Found
Type Status Report
Message /test_rest_api/greeting
Description The origin server did not find a current representation for the target resource or is not
willing to disclose that one exists.
Apache Tomcat/8.5.43
That is running on server by Run As - ; if I select Run as java application and select springboot the following error occurs:
java.lang.IllegalArgumentException: Sources must not be empty at
org.springframework.util.Assert.notEmpty(Assert.java:467)
I finally figured it out and it was a stupid mistake. I added the name of the project as part of the domain of the request url.
http://localhost:8080/test_rest_api/greeting
vs
http://localhost:8080/greeting

How to use SpringBoot actuator over JMX

I am having existing Spring Boot application and I want to do monitoring the application through actuator.I tried with http endpoints and it is working fine for me. Instead of http end points I need JMX end points for my existing running application.
If you add spring-boot-starter-actuatordependency in your build.gradle or pom.xml file you will have JMX bean enabled by default as well as HTTP Endpoints.
You can use JConsole in order to view your JMX exposed beans. You'll find more info about this here.
More details about how to access JMX endpoints here.
Assuming you're using a Docker image where the entry point is the Spring Boot app using java in which case the PID is "1" and so would the Attach API's Virtual Machine ID. You can implement a health probe as follows.
import com.sun.tools.attach.spi.AttachProvider;
import java.util.Map;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
public class HealthProbe {
public static void main(String[] args) throws Exception {
final var attachProvider = AttachProvider.providers().get(0);
final var virtualMachine = attachProvider.attachVirtualMachine("1");
final var jmxServiceUrl = virtualMachine.startLocalManagementAgent();
try (final var jmxConnection = JMXConnectorFactory.connect(new JMXServiceURL(jmxServiceUrl))) {
final MBeanServerConnection serverConnection = jmxConnection.getMBeanServerConnection();
#SuppressWarnings("unchecked")
final var healthResult =
(Map<String, ?>)
serverConnection.invoke(
new ObjectName("org.springframework.boot:type=Endpoint,name=Health"),
"health",
new Object[0],
new String[0]);
if ("UP".equals(healthResult.get("status"))) {
System.exit(0);
} else {
System.exit(1);
}
}
}
}
This will use the Attach API and make the original process start a local management agent.
The org.springframework.boot:type=Endpoint,name=Health object instance would have it's health method invoked which will provide a Map version of the /actuator/health output. From there the value of status should be UP if things are ok.
Then exit with 0 if ok, or 1 otherwise.
This can be embedded in an existing Spring Boot app so long as loader.main is set. The following is the HEALTHCHECK probe I used
HEALTHCHECK --interval=5s --start-period=60s \
CMD ["java", \
"-Dloader.main=net.trajano.swarm.gateway.healthcheck.HealthProbe", \
"org.springframework.boot.loader.PropertiesLauncher" ]
This is the technique I used in distroless Docker Image.
Side note: Don't try to put this in a CommandLineRunner interface because it will try to pull the configuration from the main app and you likely won't need the whole web stack.

Running cucumber-groovy features against a spring boot api

I've been attempting to get cucumber-groovy working with spring-boot, but it's not been going well. I get the error org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://localhost:8080/applicants": Connection refused; nested exception is java.net.ConnectException: Connection refused which seems to indicate that it's hitting the endpoint, but that the service isn't running.
I've read that I need to have a cucumber.xml file, but my project is not using any xml config, it's all annotations, so instead I've got this:
package support
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#Configuration
#ComponentScan(basePackages = "com.base.package")
public class CucumberConfiguration {}
I've added it to the World, but this seems to be the wrong way of doing things (i.e. I don't know how to add an annotation on groovy step defs).
package support
import com.thing.app.Application
import org.junit.runner.RunWith
import org.springframework.boot.test.SpringApplicationContextLoader
import org.springframework.boot.test.WebIntegrationTest
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner
import org.springframework.test.context.web.WebAppConfiguration
import static cucumber.api.groovy.Hooks.*
//#RunWith(SpringJUnit4ClassRunner)
//#ContextConfiguration(classes = Application, loader = SpringApplicationContextLoader)
//#WebAppConfiguration
//#WebIntegrationTest
#ContextConfiguration(classes = CucumberConfiguration)
public class AbstractTest {
}
World() {
new AbstractTest()
}
Before() {}
After() {}
I left in my other annotations to kind of show what I've done so far. None of it has worked.
I've also tried setting up an AbstractDefs class as seen here https://github.com/jakehschwartz/spring-boot-cucumber-example/tree/master/src/test/java/demo, but that also hasn't worked, mostly because I'm not using the cucumber-java style of things, but instead the cucumber-groovy style, which doesn't use step definition classes.
Edit: Just discovered I was doing things wrong by having an env.groovy, I'm used to the ruby cucumber, so I'm having trouble finding all the little problems. Still am having the same issue though, I don't know how to execute in a Spring context.
You can instantiate Spring test context with io.cucumber.spring.SpringFactory and register adapter in World to allow groovy script has access to Spring beans:
env.groovy:
#ContextConfiguration(classes = TestConfiguration, loader = SpringBootContextLoader)
class CucumberContextConfiguration {
}
//adapter bypassing World properties to Spring context
class SpringFactoryWorldAdapter {
private final SpringFactory factory;
SpringFactoryWorldAdapter(SpringFactory factory) {
this.factory = factory;
}
#Override
Object getProperty(String s) {
return factory.testContextManager.getContext().getBean(s);
}
}
def factory; //Keep state to prevent repeated context initialization
World { args ->
if (factory == null) {
factory = new SpringFactory()
factory.addClass(CucumberContextConfiguration)
factory.start()
}
new SpringFactoryWorldAdapter(factory)
}

How to make webjars version agnostic in spring mvc

I have followed the documentation on their website as described here
First of all I added the required path
<mvc:resources mapping="/webjars/**" location="/webjars/"/>
then I created a controller with the following
#ResponseBody
#RequestMapping("/webjarslocator/{webjar}/**")
public ResponseEntity locateWebjarAsset(#PathVariable String webjar, HttpServletRequest request) {
try {
String mvcPrefix = "/webjarslocator/" + webjar + "/"; // This prefix must match the mapping path!
String mvcPath = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
String fullPath = assetLocator.getFullPath(webjar, mvcPath.substring(mvcPrefix.length()));
return new ResponseEntity(new ClassPathResource(fullPath), HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
Few dependencies were missing so I added in maven the following pom
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator</artifactId>
<version>0.28</version>
</dependency>
The above will import the following
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.core.io.ClassPathResource;
None of these has been imported from the external jar.
The error is: assetLocator cannot be resolved
EDIT: It could be that I need to create a filter rather than put it in a controller. Any thoughts on this?
The documentation is quite sparse, but you can create an instance of an asset locator with new WebJarAssetLocator().
Here's another solution, considering more recent versions of WebJar, in a similar vein for anyone else who stumbles upon this question. Using org.webjars.play.WebJarsUtil#locate(java.lang.String, java.lang.String) (which, in-turn, uses org.webjars.WebJarAssetLocator#getFullPath(java.lang.String, java.lang.String)) accepts the WebJar name (which acts as a scope) and the file without needing to provide a full path that necessitates including the version.

CXF OSGi bundle - register a filter against CXFServlet?

I've got the CXF OSGi bundle deployed on a karaf container with several endpoints each in their own bundle. The endpoints are a mix of JAX-RS and JAX-WS flavors. I'd like to enable some security on the endpoints with JOSSO and need to register a servlet filter to do so. Obviously, with no web.xml to declare the filter, I need to register them in the OSGi service registry.
I've attempted to use the pax-web http whiteboard to register a filter but the doFilter method is never invoked. I noticed the Distributed OSGI cxf implementation has a provision for setting a org.apache.cxf.httpservice.filter property to true on the filter and specifying a dummy string for servletNames so as not to confuse pax-web whiteboard. Is there something similar for the standard CXF (non-distributed) OSGi bundle that I can do to register a servlet filter?
After lots of digging I was able to set a filter to the CXF Servlet with Felix + PAX Web. The trick is to register the filter from within the CXF bundle (there is a distinct http context for each bundle).
In my code I fetched the bundle context, called getBundles(), located the cxf bundle and obtained the bundle context of the CXF bundle. Then I registered the filter on the CXF bundle's context. I feel very dirty now, but it works.
I recall to have seen a recommendation to create a fragement bundle for configuration of PAX's Jetty server, this would probably also work for registering a filter - however I didn't want to create another artifact in our project at the current time.
I found myself in the situation to write a global filter for Apache CXF, but couldn't figure out how to do it.
Some investigations revealed, that Apache CXF is using the HttpService from OSGi and registers the servlet directly. See https://github.com/apache/cxf/blob/90df32db98d8fc76f091723561f42c6d16021db4/rt/transports/http/src/main/java/org/apache/cxf/transport/http/osgi/ServletExporter.java#L126 for more details.
You can see, that a default http context is used, which is bound to the bundle. See https://github.com/ops4j/org.ops4j.pax.web/blob/d2172a91ad2579714d40b509d6c420e5c28fa2d0/pax-web-runtime/src/main/java/org/ops4j/pax/web/service/internal/HttpServiceStarted.java#L337
Therefore you cannot simply expose a filter and have it work for the CXF Servlet.
However, as froh42 was saying, it is possible to fetch the bundle, exporting the CXF Servlet, and use its bundle context to register the filter. If you are in doubt, which bundle you want to look out for, use the following command in the Karaf shell: service:list javax.servlet.ServletContext and it will reveal the symbolic name of the bundle exposing the contexts:
[javax.servlet.ServletContext]
------------------------------
osgi.web.contextname = default
osgi.web.contextpath = /
osgi.web.symbolicname = org.apache.cxf.cxf-rt-transports-http
osgi.web.version = 3.5.0
service.bundleid = 97
service.id = 218
service.scope = singleton
Provided by :
Apache CXF Runtime HTTP Transport (97)
Used by:
OPS4J Pax Web - Runtime (182)
Long story short, this is how you would write such registration logic. Be aware, that you need to use WebContainer from the pax-web-api module, as that allows you to register a filter. HttpService does not have that capability.
package org.example.cxf.filter;
import org.ops4j.pax.web.service.WebContainer;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.http.HttpContext;
import java.util.Collection;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
#Component
public class CxfFilterRegistrator {
#Activate
public void onActivate(BundleContext bundleContext) throws InvalidSyntaxException {
final WebContainer cxfWebContainer = findCxfWebContainerOrFail(bundleContext);
final HttpContext otherContext = cxfWebContainer.createDefaultHttpContext();
final Dictionary<String, String> initProperties = new Hashtable<>();
initProperties.put("key", "value");
cxfWebContainer.registerFilter(MyFilter.class, new String[]{"/*"}, null, initProperties, otherContext);
}
#Deactivate
public void onDeactivate(BundleContext bundleContext) throws InvalidSyntaxException {
final WebContainer cxfWebContainer = findCxfWebContainerOrFail(bundleContext);
cxfWebContainer.unregisterFilter(MyFilter.class);
}
private WebContainer findCxfWebContainerOrFail(final BundleContext bundleContext) throws InvalidSyntaxException {
final List<Bundle> cxf = Stream.of(bundleContext.getBundles())
.filter(bundle -> bundle.getSymbolicName().equals("org.apache.cxf.cxf-rt-transports-http"))
.collect(Collectors.toList());
if (cxf.isEmpty()) {
throw new IllegalStateException("CXF not found, Bailing");
}
final BundleContext cxfBundleContext = cxf.get(0).getBundleContext();
final Collection<ServiceReference<WebContainer>> serviceReferences = cxfBundleContext.getServiceReferences(WebContainer.class, null);
final WebContainer otherWebContainer = cxfBundleContext.getService(serviceReferences.iterator().next());
return otherWebContainer;
}
}

Resources