CXF OSGi bundle - register a filter against CXFServlet? - osgi

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;
}
}

Related

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.

Jersey on embedded Jetty with hk2 locator not loading ResourceConfig

I am using Jetty as an embedded server for Jersey.
ServletHolder jerseyServletHolder = new ServletHolder(ServletContainer.class);
jerseyServletHolder.setInitOrder(1);
jerseyServletHolder.setInitParameter(
org.glassfish.jersey.server.ServerProperties.PROVIDER_PACKAGES,
"com.my.package");
webAppContext.addServlet(jerseyServletHolder, "/rest/*");
I have a ResourceConfig implementation:
#ApplicationPath("/rest")
public class MyResourceConfig extends ResourceConfig {
static{
System.out.println("ResourceConfig loaded");
// this never gets calls
}
#Inject
public MyResourceConfig(ServiceLocator serviceLocator, Properties serverProps) {
packages("com.my.package");
}
}
The problem is that when I launch, the MyResourceConfig class is never loaded.
If I add:
jerseyServletHolder.setInitParameter(
ServletProperties.JAXRS_APPLICATION_CLASS,
MyResourceConfig.class.getName());
then the ResourceConfig does get loaded.
Why isn't MyResoureConfig getting picked up based on the #ApplicationPath annotation?
You just need annotation / bytecode scanning enabled.
Start by putting jetty-annotations-<version>.jar (and transitive dependencies) into your project.
Then, in your code, after you create your org.eclipse.jetty.server.Server object, do this.
Server server = new Server(8080);
// Enable parsing of jndi-related parts of web.xml and jetty-env.xml
Configuration.ClassList classlist = Configuration.ClassList
.setServerDefault(server);
classlist.addAfter("org.eclipse.jetty.webapp.FragmentConfiguration",
"org.eclipse.jetty.plus.webapp.EnvConfiguration",
"org.eclipse.jetty.plus.webapp.PlusConfiguration");
// Enable annotation/bytecode scanning and ServletContainerInitializer usages
classlist.addBefore(
"org.eclipse.jetty.webapp.JettyWebXmlConfiguration",
"org.eclipse.jetty.annotations.AnnotationConfiguration");
WebAppContext webAppContext = createWebAppContext();
// ....
server.start();
That will enable the configurations needed to perform bytecode scanning and annotation scanning, along with enabling the ability to load any javax.servlet.ServletContainerInitializer found within your webapp, including the critical one from Jersey (org.glassfish.jersey.servlet.init.JerseyServletContainerInitializer)

Microservice with 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.

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)
}

Using Jackson as Jersey client serializer

Is it possible to use Jackson as the serializer/marshaller for JSON data instead of JAXB when using Jersey Client API?
If so how to configure it?
OK, I found it out, it turns out to be quite simple after all:
ClientConfig cc = new DefaultClientConfig();
cc.getClasses().add(JacksonJsonProvider.class);
Client clientWithJacksonSerializer = Client.create(cc);
The JacksonJsonProvider comes from the jackson-jaxrs package.
You may skip the creation of external config and register the provider directly:
Client client = ClientBuilder.newClient().register(JacksonJsonProvider.class)
Solution with JacksonJaxbJsonProvider
Common way how to use Jackson with custom configuration in Jersey client was to use JacksonJaxbJsonProvider for example like this
JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider();
provider.setMapper(yourObjectMapper());
Client client = ClientBuilder.newClient(new ClientConfig(provider));
Unfortunately in Jersey 2.26 they copied JacksonJaxbJsonProvider class
from com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider artifact (Jackson)
to org.glassfish.jersey.media:jersey-media-json-jackson artifact (Jersey)
and changed package
from com.fasterxml.jackson.jaxrs.json
to org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.
It is still possible to use this approach it's just needed to change JacksonJaxbJsonProvider import.
Apart from JacksonJaxbJsonProvider being now in internal package drawback is also
that you must know on which Jersey version your code runs which might be a problem when different dependencies require different Jersey versions.
Better solution with ContextResolver<ObjectMapper>
Better possibility how to configure Jackson in Jersey client is to use the same way how it is configured in Jersey server which is to create ObjectMapper provider like this:
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
private ObjectMapper objectMapper = yourObjectMapper();
#Override
public ObjectMapper getContext(Class<?> objectType) {
return objectMapper;
}
}
and use it for example like this:
ClientConfig clientConfig = new ClientConfig();
clientConfig.register(JacksonFeature.class); // usually auto-discovered
clientConfig.register(new ObjectMapperProvider());
Client client = ClientBuilder.newClient(clientConfig);
If you have both the server and the client you can reuse ObjectMapperProvider class.
It seems that this approach works from Jersey version 2.9.
You might also want to try org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider (jackson-jaxrs 1.6.1).
I ran into similar issue, but for me none of the suggestions given here worked.
What worked for me was below piece of code:
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Client;
...
ClientBuilder clientBuilder = ClientBuilder.newBuilder()
clientBuilder.register(JacksonFeature.class);
...
Client client = clientBuilder.build();
The key change was usage of JacksonFeature.class - it comes from jersey-media-json-jackson-x.yy.jar
I got clue to use this solution from this article - http://www.baeldung.com/jersey-jax-rs-client
For jersey 2.22.2 and Jackson 2.7.2 gradle dependencies are:
dependencies {
compile("com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.7.2")
compile("org.glassfish.jersey.core:jersey-client:2.22.2")
}
Sample client code is:
final String name = "world";
final Client client = ClientBuilder.newClient().register(JacksonJaxbJsonProvider.class);
final WebTarget target = client.target("http://localhost:8080").path("hello").path(name);
final Message message = target.request().get(Message.class);
System.out.println(message.getWelcomeMessage()); // hello world

Resources