List all rest service urls in spring boot cxf - java

is it possible to list all REST services when using cxf with spring-boot? I've created ApplicationListener<ContextRefreshedEvent> and in there I would like to list all REST service urls which were registered for my cxf servlet. I've tried to poke around CXFServlet, ServletContext, cxf Endpoint and cxf Server classes but I can't figure it out. I've also tried to review wadl generator (feature) and swagger2 feature but they create url and content when request comes. Is it possible?
Thanks.

I would scan the #WebService annotations on the classpath, maybe it will help you:
#Autowired
private ClassPathScanningCandidateComponentProvider annotationScanner;
public List<ClassDocument> generate(String basePackage) throws ClassNotFoundException {
Set<BeanDefinition> candidateComponents = annotationScanner.findCandidateComponents(basePackage);
List<ClassDocument> classDocuments = new ArrayList<>();
for (BeanDefinition component : candidateComponents) {
ClassDocument classDocument = new ClassDocument();
Class<?> beanClass = Class.forName(component.getBeanClassName());
classDocument.setClassName(beanClass.getName());
String[] baseUrl = beanClass.getAnnotation(javax.jws.WebService.class).value();
addMethods(classDocument, beanClass, baseUrl);
classDocuments.add(classDocument);
}
return classDocuments;
}

Related

How to connect SOAP server and JSON client through Spring Integration?

Good day. I am new to spring integration. I wrote a simple SOAP server, and I need to connect a client that communicates through JSON and a server that communicates via SOAP, but I’ve got confused in the technology that this framework provides. As I understand it there are JsonToObjectTransformer and ObjectToMapTransformer transformers. As I understand it is necessary to transform the data before transmitting it to the controller. Is it possible to do this with the help of transformers, or I can use other technologies in the spring integration. And can this be done only with the help of DSL?
Controller:
#Endpoint
public class CityEndpoint {
private static final String NAMESPACE_URI = "http://weather.com/senchenko";
private CityRepository cityRepository;
#Autowired
public CityEndpoint(CityRepository cityRepository) {
this.cityRepository = cityRepository;
}
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "getCityRequest")
#ResponsePayload
public GetCityResponse getCityResponse(#RequestPayload GetCityRequest request){
GetCityResponse response = new GetCityResponse();
response.setCity(cityRepository.findCity(request.getName()));
return response;
}
}
Config:
#EnableWs
#Configuration
public class WebServiceConfig extends WsConfigurerAdapter {
#Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, "/ws/*");
}
#Bean(name = "city")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema citySchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("CityPort");
wsdl11Definition.setLocationUri("/ws");
wsdl11Definition.setTargetNamespace("http://weather.com/senchenko");
wsdl11Definition.setSchema(citySchema);
return wsdl11Definition;
}
#Bean
public XsdSchema citySchema() {
return new SimpleXsdSchema(new ClassPathResource("xsd/weather.xsd"));
}
#Bean
#Transformer()
JsonToObjectTransformer jsonToObjectTransformer() {
return new JsonToObjectTransformer();
}
#Bean
#Transformer()
ObjectToMapTransformer objectToMapTransformer(){
return new ObjectToMapTransformer();
}
}
Addition
I solved the problem with redirection to SOAP, but still do not know the best way to convert JSON into an SOAP Envelope and back.
#Bean
public IntegrationFlow httpProxyFlow() {
return IntegrationFlows
.from(Http.inboundGateway("/service"))
.transform(t -> TEST_ENVOLOPE)
.enrichHeaders(h -> h.header("Content-Type", "text/xml; charset=utf-8"))
.handle(Http.outboundGateway("http://localhost:8080/ws")
.expectedResponseType(String.class))
.transform(t -> TEST_RESPONSE)
.get();
}
Your question isn't clear or you are not fully familiar with technologies you need to work.
The SOAP is fully about XML messages exchange. On the server side you have a specific MessageDispatcherServlet which converts an incoming HTTP request to the SOAP envelop fully in XML. There is just nothing about JSON at all.
Your CityEndpoint.getCityResponse() is triggered by the Spring WS Framework when an incoming SOAP request is unmarshalled from the XML into the domain model via JaxB according your XSD definition and generated model. There is just nothing about Spring Integration at all.
Your JsonToObjectTransformer and ObjectToMapTransformer just don't make any sense in this scenario. They are not involved in the SOAP request process.
Sorry to disappoint you in my answer, but it even not clear by your question how that JSON client is going to call SOAP service when JSON and XML are fully different and not compatible protocols.

BeanConfig (or similar?) in Swagger 2.0 (OpenApi 3.0)

I am currently migrating our API docs (which were Swagger 1.5) to Swagger 2.0 (OpenApi 3.0)
The API docs are Swagger docs which get generated with java annotations using maven packages swagger-annotations and swagger-jaxrs. I have already updated the pom.xml with new versions so it looks like:
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-jaxrs2</artifactId>
<version>2.0.6</version>
</dependency>
And also all the old annotations are replaced with the new ones (which change quite a lot) and looks fine.
The thing is we were using a BeanConfig to define the docs general config and auto-scan all the REST resources so the documentation got generated automatically at /swagger.json.
The problem is I can't find the "new way" of doing such thing as creating a BeanConfig and auto-scan the resources so everything gets generated at /swagger.json or /openapi.json (maybe now is something like OpenAPIDefinition?)
If somebody could point me to the right direction I would be very grateful...
After some research, I could find some documentation about it in their Github for JAX-RS application, so the result is something similar to what I was doing but now instead of using a BeanConfig, it uses OpenAPI and Info:
#ApplicationPath("/sample")
public class MyApplication extends Application {
public MyApplication(#Context ServletConfig servletConfig) {
super();
OpenAPI oas = new OpenAPI();
Info info = new Info()
.title("Swagger Sample App bootstrap code")
.description("This is a sample server Petstore server. You can find out more about Swagger " +
"at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, " +
"you can use the api key `special-key` to test the authorization filters.")
.termsOfService("http://swagger.io/terms/")
.contact(new Contact()
.email("apiteam#swagger.io"))
.license(new License()
.name("Apache 2.0")
.url("http://www.apache.org/licenses/LICENSE-2.0.html"));
oas.info(info);
SwaggerConfiguration oasConfig = new SwaggerConfiguration()
.openAPI(oas)
.prettyPrint(true)
.resourcePackages(Stream.of("io.swagger.sample.resource").collect(Collectors.toSet()));
try {
new JaxrsOpenApiContextBuilder()
.servletConfig(servletConfig)
.application(this)
.openApiConfiguration(oasConfig)
.buildContext(true);
} catch (OpenApiConfigurationException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
Though OP has answered their own question, but adding a few more details for people like me who landed on this post as I wanted to migrate from swagger 1.x to swagger 2.0 (openAPI 3) and needed complete config.
(This example is for embedded jetty)
// Jetty configuration
// ContextHandlerCollection contexts
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/api");
context.addFilter(GzipFilter.class, "/*", EnumSet.allOf(DispatcherType.class));
ResourceConfig resourceConfig = new ResourceConfig(ImmutableSet.<Class<?>>builder()
.add(MyRestService.class)
.build());
resourceConfig.registerClasses(OpenApiResource.class,AcceptHeaderOpenApiResource.class); // for swagger, this will cerate openapi.json at <host>/api/openapi.json
context.addServlet(new ServletHolder(new ServletContainer(resourceConfig)), "/*");
contexts.addHandler(context);
If you need to change default swagger config, that can be done by what OP has described in their answer:
OpenAPI oas = new OpenAPI();
Info info = new Info()
.title("Swagger Sample App bootstrap code")
.description("This is a sample server Petstore server. You can find out more about Swagger " +
"at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, " +
"you can use the api key `special-key` to test the authorization filters.")
.termsOfService("http://swagger.io/terms/")
.contact(new Contact()
.email("apiteam#swagger.io"))
.license(new License()
.name("Apache 2.0")
.url("http://www.apache.org/licenses/LICENSE-2.0.html"));
oas.info(info);
SwaggerConfiguration oasConfig = new SwaggerConfiguration()
.openAPI(oas)
.prettyPrint(true)
.resourcePackages(Stream.of("io.swagger.sample.resource").collect(Collectors.toSet()));
try {
new JaxrsOpenApiContextBuilder()
.servletConfig(servletConfig)
.application(this)
.openApiConfiguration(oasConfig)
.buildContext(true);
} catch (OpenApiConfigurationException e) {
throw new RuntimeException(e.getMessage(), e);
}
There is a much simpler solution for the above requirement.
import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Contact;
import io.swagger.v3.oas.annotations.info.Info;
import org.glassfish.jersey.server.ResourceConfig;
#OpenAPIDefinition(
info =
#Info(
title = "Sample rest service",
version = "1.0.0",
description = "Sample rest service",
contact =
#Contact(
url = "https://jira2.cerner.com/projects/Dey",
name = "ADey")))
public class SampleRestApplication extends ResourceConfig {
public SampleRestApplication() {
register(OpenApiResource.class);
}
}
your service will load your API spec at /openApi.yaml|json.
Another variant on the same theme. You can package up your openAPI config generation logic into a stand-alone class like so:
#Provider
public class SwaggerInfoBlackMagic implements Feature {
#Context ServletConfig config;
#Context Application app;
#Override
public boolean configure(FeatureContext context) {
//The aim here is to force construction of a (convincing) OpenApiContext before swagger does!
//This has been lifted from BaseOpenApiResource
String ctxId = getContextIdFromServletConfig(config);
try {
OpenApiContext ctx = new JaxrsOpenApiContextBuilder()
.servletConfig(config)
.application(app)
//Might need more of these depending on your setup..
//.resourcePackages(resourcePackages)
//.configLocation(configLocation)
.openApiConfiguration(getOpenApi())
.ctxId(ctxId)
.buildContext(true); //this also stores the instance statically
} catch (OpenApiConfigurationException e) {
throw new RuntimeException(e);
}
return true;
}
private OpenAPIConfiguration getOpenApi() {...}
}
Then whenever you need it you can simply add:
jersey().register(SwaggerInfoBlackMagic.class);
It's the same as the above but slightly tidier.

Jersey JAX-RS register more controllers on embedded Jetty

I'm trying to implement a restful web service using Jersey JAX-RS.
I embedded a Jetty web server and wanted to register all the controllers on it.
I based on this example:
https://nikgrozev.com/2014/10/16/rest-with-embedded-jetty-and-jersey-in-a-single-jar-step-by-step/
in which EntryPoint is the controller:
#Path("/entry-point")
public class EntryPoint {
#GET
#Path("test")
#Produces(MediaType.TEXT_PLAIN)
public String test() {
return "Test";
}
}
and this is registered using the key name "jersey.config.server.provider.classnames" as follows:
public class App {
public static void main(String[] args) throws Exception {
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
Server jettyServer = new Server(8080);
jettyServer.setHandler(context);
ServletHolder jerseyServlet = context.addServlet(
org.glassfish.jersey.servlet.ServletContainer.class, "/*");
jerseyServlet.setInitOrder(0);
// Tells the Jersey Servlet which REST service/class to load.
jerseyServlet.setInitParameter(
"jersey.config.server.provider.classnames",
EntryPoint.class.getCanonicalName());
try {
jettyServer.start();
jettyServer.join();
} finally {
jettyServer.destroy();
}
}
}
How can I register many controllers?
If I add other controller classes as params I don't know what key name I must give to each one, because only "jersey.config.server.provider.classnames" seems to work and works once.
Thanks.
Because you can only use the property once, you need to use a comma delimited list as the value classOne, classTwo, classThree.
Another option is to use the property jersey.config.server.provider.packages and just give it a package to recursively scan
jerseyServlet.setInitParam(ServerProperties.PROVIDER_PACKAGES, "my.package.to.scan");
See ServerProperties for more properties you can set. Here PROVIDER_PACAKGES is a constant, whose string value is jersey.config.server.provider.packages. Same with the classnames property there is a constant PROVIDER_CLASSNAMES.
By declaring the package to scan, Jersey will scan that package recursively (by default) and register all #Path and #Provider annotated classes it finds in the scan.

Apache CXF make addtitinal classes "known" to JAXB context

How to configure Apache CXF client and server to pass additional classes to JAXBContext when it is serializing DTO to XML?
I can't use #XmlSeeAlso annotations because those classes are not known at compile time of jar with data contracts, but known when client compiles.
On client side I tried using:
Service service = Service.create(wsdlURL, serviceName, new UsesJAXBContextFeature(MyFactory.class));
T client = service.getPort(clazz);
But I got exception telling me that CXF doesn't support this feature.
You can do it with annotations also.
Works with Spring Boot CXF starter
#Autowired
private Bus bus;
#Bean
public Endpoint createMyEndpoint() {
JaxWsServerFactoryBean factory = new JaxWsServerFactoryBean();
Map<String, Object> properties = new HashMap<>();
properties.put("jaxb.additionalContextClasses", getExtraClasses());
factory.setProperties(properties);
Endpoint endpoint = new EndpointImpl(bus, new MyWebService(),factory);
endpoint.setProperties(new HashMap<>());
endpoint.publish("/v1/service");
return endpoint;
}
#SuppressWarnings("rawtypes")
private Class[] getExtraClasses() {
List<Class> extraClassList = new ArrayList<>();
extraClassList.add(A.class);
extraClassList.add(B.class);
return extraClassList.toArray(new Class[extraClassList.size()]);
}
...
#javax.jws.WebService
public class MyWebService implements MyPortType {
//...
}
I figured it out with
https://issues.apache.org/jira/browse/CXF-340
https://github.com/apache/cxf/blob/5578e0b82bcd4ea19c1de5b4a008af35f9c8451b/rt/frontend/jaxws/src/main/java/org/apache/cxf/jaxws/EndpointImpl.java#L164
if you configure cxf with cxf.xml (spring-xml) you can use the following:
<jaxws:endpoint/client>
<jaxws:properties>
<entry key="jaxb.additionalContextClasses">
<array value-type="java.lang.Class">
<value type="java.lang.Class">fullQualifiedClassName</value>
</array>
</entry>
</jaxws:properties>
</jaxws:endpoint>
or any other way to write the org.apache.cxf.jaxb.JAXBDataBinding property "extraClass" (a Class[]) like . See http://cxf.apache.org/docs/jaxb.html

Testing Spring managed servlet

I need to test a servlet, which is working fine now.
The servlet needs to use a Spring service, so it is modified for that this way:
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(
this, config.getServletContext()); // ImageServlet.java line 49
After migration to Spring 4, the test broke and currently it throws this exception:
java.lang.IllegalStateException:
No WebApplicationContext found: no ContextLoaderListener registered?
at org.springframework.web.context.support.WebApplicationContextUtils.
getRequiredWebApplicationContext(WebApplicationContextUtils.java:84)
at org.springframework.web.context.support.SpringBeanAutowiringSupport.
processInjectionBasedOnServletContext(SpringBeanAutowiringSupport.java:107)
at package.ImageServlet.init(ImageServlet.java:49)
at in.nasv.utils.ImageServletTest.accessingImageViaHttp(ImageServletTest.java:45)
Here is the portion of code of ImageServletTest:
// prepare servlet instance
MockServletConfig config = new MockServletConfig(
new MockServletContextPatched());
ImageServlet servlet = new ImageServlet();
servlet.init( config ); // ImageServletTest, line 45
And this patched class (is not actually patched now):
public class MockServletContextPatched extends MockServletContext{ }
What am I supposed to do to avoid this "IllegalStateException: No WebApplicationContext found: no ContextLoaderListener registered?" ?
I found an solution. But clear enough, but an solution.
Now servlet initialization is:
MockServletContext servletContext = new MockServletContextPatched();
MockServletConfig config = new MockServletConfig( servletContext );
ImageServlet servlet = new ImageServlet();
ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext( "spring-data-app-context.xml" );
DefaultListableBeanFactory dlbf = new DefaultListableBeanFactory(appContext.getBeanFactory());
GenericWebApplicationContext gwac = new GenericWebApplicationContext(dlbf);
servletContext.setAttribute(GenericWebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, gwac);
gwac.setServletContext(servletContext);
gwac.refresh();
servlet.init( config );
Preparing request and response in standard way:
MockHttpServletResponse response = new MockHttpServletResponse();
URL serverUrl = new URL( propertyExtendedService.getServerAddress(true) );
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI( "/what-you-want" );
request.setPathInfo( "/" + TEST_IMAGE );
request.setContentType("image/jpeg");
request.addHeader("Accept", "image/jpeg;image/jpg;" );
Final step is to call the filter and assert returned values:
servlet.doGet( request, response );
assertEquals( response.getStatus(), 200 );
// assert everything you want
Update: the updated documentation for getServletContext() is now online.
It is not necessary to implement a custom MockServletContextPatched class just to configure a custom MIME type in Spring's MockServletContext.
Since Spring's MockServletContext uses the Java Activation Framework (JAF) to implement the ServletContext.getMimeType(String) method, it is quite easy to configure a custom MIME type via JAF's MimetypesFileTypeMap.addMimeTypes(String) method as follows.
MockServletContext mockServletContext = new MockServletContext();
MimetypesFileTypeMap mimetypesFileTypeMap =
(MimetypesFileTypeMap) MimetypesFileTypeMap.getDefaultFileTypeMap();
mimetypesFileTypeMap.addMimeTypes("text/enigma enigma");
assertEquals("text/enigma", mockServletContext.getMimeType("filename.enigma"));
In the above JUnit based test code, I configured a custom MIME type "text/enigma" for files that have the extension .enigma.
Hope this helps!
Regards,
Sam (author of the Spring TestContext Framework)
p.s. I created JIRA issue SPR-12126 in order to improve the documentation of MockServletContext.

Categories

Resources