Spring Boot Soap :How to initiate #WebService into Spring Boot Application? - java

I am migrating a web service into spring boot.
From wsdl i am able to generate following interface
#WebService(targetNamespace = "http://tsb.hcl.com/terp/ws/v2", name = "Terp-v2")
#XmlSeeAlso({com.hcl.psi.tsb.schema.common.ObjectFactory.class, com.hcl.psi.tsb.schema.offering.ObjectFactory.class, com.hcl.psi.kil.system.message.ObjectFactory.class, com.hcl.tsb.terp.ws.schema.ObjectFactory.class, com.hcl.psi.tsb.schema.task.ObjectFactory.class, com.hcl.psi.tsb.schema.servicecontract.ObjectFactory.class, com.hcl.psi.tsb.schema.project.ObjectFactory.class, com.hcl.tsb.tsb.ObjectFactory.class, com.hcl.psi.tsb.schema.customer.ObjectFactory.class})
#SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
public interface TerpV2 {
#WebMethod(operationName = "GetServiceContracts")
#WebResult(name = "GetServiceContractsResponseType", targetNamespace = "http://tsb.hcl.com/terp/ws/schema", partName = "payload")
public com.hcl.tsb.terp.ws.schema.GetServiceContractsResponseType getServiceContracts(
#WebParam(partName = "payload", name = "GetServiceContractsType", targetNamespace = "http://tsb.hcl.com/terp/ws/schema")
com.hcl.tsb.terp.ws.schema.GetServiceContractsType payload
) throws TerpServiceFault;
I am not sure how to initiate it in Spring Boot application form baeldung website. I tried implementing following code:
#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 = "TerpService")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema countriesSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("Terp-v2");
wsdl11Definition.setLocationUri("/tsb/tone/ws/v2/TerpService/");
wsdl11Definition.setTargetNamespace("http://www.baeldung.com/springsoap/gen");
wsdl11Definition.setSchema(countriesSchema);
return wsdl11Definition;
}
#Bean
public XsdSchema countriesSchema() {
return new SimpleXsdSchema(new ClassPathResource("countries.xsd"));
}
}
But I am not sure how to initiate above interface or it's implementation into spring boot context?
Do i need to write whole contract classes manually?
Can anyone guide me about same. Thanks

I have a similar project and it works.
Try making your interface instead of
#WebService(targetNamespace = "http://tsb.hcl.com/terp/ws/v2", name = "Terp-v2")
#XmlSeeAlso({com.hcl.psi.tsb.schema.common.ObjectFactory.class, com.hcl.psi.tsb.schema.offering.ObjectFactory.class, com.hcl.psi.kil.system.message.ObjectFactory.class, com.hcl.tsb.terp.ws.schema.ObjectFactory.class, com.hcl.psi.tsb.schema.task.ObjectFactory.class, com.hcl.psi.tsb.schema.servicecontract.ObjectFactory.class, com.hcl.psi.tsb.schema.project.ObjectFactory.class, com.hcl.tsb.tsb.ObjectFactory.class, com.hcl.psi.tsb.schema.customer.ObjectFactory.class})
#SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
public interface TerpV2 {
#WebMethod(operationName = "GetServiceContracts")
#WebResult(name = "GetServiceContractsResponseType", targetNamespace = "http://tsb.hcl.com/terp/ws/schema", partName = "payload")
public com.hcl.tsb.terp.ws.schema.GetServiceContractsResponseType getServiceContracts(
#WebParam(partName = "payload", name = "GetServiceContractsType", targetNamespace = "http://tsb.hcl.com/terp/ws/schema")
com.hcl.tsb.terp.ws.schema.GetServiceContractsType payload
) throws TerpServiceFault;
like this
#Endpoint
public class TerpV2 {
private static final String NAMESPACE_URI = "Terp-v2";
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "GetServiceContractsType")
#ResponsePayload
public YourCustomResponseObject getServiceContracts(#RequestPayload GetServiceContractsType getServiceContractsType) {
//this could be constructed in a service class
YourCustomResponseObject response = new YourCustomResponseObject();
return response;
}
Also in your configurations
wsdl11Definition.setTargetNamespace("Terp-v2");
Also in your pom.xml configure the build like following
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!-- Configuration excecutable=true i have added this to make the jar excecutable-->
<configuration>
<executable>true</executable>
</configuration>
<executions>
<execution>
<phase>install</phase>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<!-- Configuration classifier=exec i have added this to force maven create one Fat
excecutable jar but also another jar for the dependencies of the payload modules-->
<!-- <classifier>exec</classifier>-->
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<version>2.3</version>
<executions>
<execution>
<id>xjc</id>
<goals>
<goal>xjc</goal>
</goals>
</execution>
</executions>
<configuration>
<sources>
<source>src/main/resources/countries.xsd</source>
</sources>
</configuration>
</plugin>
</plugins>
</build>
After you build your project it will compile your xsd and will produce in target some DTO classes. Those DTO classes you can copy then in your project and use them like actual DTOs. That's the easies way to convert your XSD to DTO to be used by spring boot.
To do this you must add the following dependency in your pom.xml
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
</dependency>

Related

openapi codegen - generating List<byte[]> instead of byte[]

I have an application with the following structure (simplified for brevity):
project structure
myApp
src/main/java/
com.something
controllers
UserApi
UserController
model
UpsertRequest
pom.xml
myApp-generated-client
target
pom.xml
UserApi
#PutMapping(USER_UPSERT_PATH)
UpsertResponse userUpsert(
#RequestBody UpsertRequest upsertRequest,
#RequestHeader(name = COMPRESSED_DATA, required = false) byte[] compressedData);
pom.xml (myApp):
<plugin>
<groupId>com.github.kongchen</groupId>
<artifactId>swagger-maven-plugin</artifactId>
<version>3.1.8</version>
<configuration>
<skipSwaggerGeneration>false</skipSwaggerGeneration>
<apiSources>
<apiSource>
<schemes>http,https</schemes>
<swaggerDirectory>
${project.basedir}/../myApp-generated-client/
</swaggerDirectory>
<locations>
<location>com.something.controllers</location>
</locations>
<springmvc>true</springmvc>
<outputFormats>json</outputFormats>
<attachSwaggerArtifact>true</attachSwaggerArtifact>
</apiSource>
</apiSources>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
pom.xml (myApp-generated-client)
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>4.1.2</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>
${project.basedir}/swagger.json
</inputSpec>
<supportingFilesToGenerate>
ApiClient.java,JSON.java,OAuth.java,Authentication.java,ApiKeyAuth.java,HttpBasicAuth.java,ApiException.java,ApiResponse.java,Configuration.java,Pair.java,RFC3339DateFormat.java,StringUtil.java,CustomInstantDeserializer.java,Client.java
</supportingFilesToGenerate>
<skipValidateSpec>true</skipValidateSpec>
<apiPackage>com.something.api</apiPackage>
<generatorName>java</generatorName>
<templateDirectory>${project.basedir}/templates</templateDirectory>
<generateApiTests>false</generateApiTests>
<generateModelTests>false</generateModelTests>
<generateModels>true</generateModels>
<configOptions>
<library>jersey2</library>
</configOptions>
<typeMappings>Resource=File</typeMappings>
<importMappings>
<importMapping>UpsertRequest=com.something.model.upsertRequest</importMapping>
</importMappings>
<modelPackage>com.something.api</modelPackage>
<modelsToGenerate/>
</configuration>
</execution>
</executions>
</plugin>
The problem is after compiling, my generated client keeps using List<byte[]> instead of byte[] for the upsert endpoint:
public ApiResponse<UpsertResponse> userUpsertWithHttpInfo(List<byte[]> compressedData, UpsertRequest body) throws ApiException
I read here that
Arrays become lists when you use the Swagger Codegen tool to generate Java clients.
For example, a field of type String[] becomes List.
Is there any way of overcoming this limitation (i.e., some converter class or some annotation)? For that matter, is there any good explanations/documentation about how to use converters to resolve types?
Update:
After seeing this link, I added the following code and updated my generated-client pom, but am getting the same results:
package com.something.converter;
public class ByteArrayFixerModelConverter extends AbstractModelConverter {
public ByteArrayFixerModelConverter() {
super(Json.mapper());
}
#Override
public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterator<ModelConverter> chain) {
if (isByteArray(type)) {
//bypass the chain! It would convert the ByteArrayProperty to an Array of ByteArrayProperty (bug in ModelModifier I think)
return new ByteArraySchema();
}
return chain.hasNext()
? chain.next().resolve(type, context, chain)
: null;
}
private boolean isByteArray(AnnotatedType annotatedType) {
Type type = annotatedType.getType();
boolean ret = type instanceof Class && type == byte[].class;
if (!ret && type instanceof ArrayType) {
ArrayType at = (ArrayType) type;
JavaType contentType = at.getContentType();
if (contentType instanceof SimpleType) {
SimpleType st = (SimpleType) contentType;
ret = st.getRawClass() == byte.class;
}
}
return ret;
}
}
pom.xml (myApp-generated-client, updated)
<plugin>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-maven-plugin</artifactId>
<version>2.1.1</version>
<configuration>
<modelConverterClasses>com.something.converter.ByteArrayFixerModelConverter</modelConverterClasses>
</configuration>
</plugin>
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
... //same as before
</plugin>

OSGI - How to solve: ClassNotFoundException? (org.eclipse.osgi.internal.loader.BundleLoader.findClass)

When using three bundles A = first.bundle B = second.bundle and C = third.bundle, where A depends on B and C, C is not able to find a class of B when calling the method java.lang.Class.forName
Is there a missing configuration in any pom.xml file or any other way to equip the ClassLoader of the bunlde C with the class B.SecondClass?
org.example.first.bundle.FirstClass
public void topLevelMethod() {
ThirdClass thirdObject = new ThirdClass();
thirdObject.resolveTheClassName("org.example.second.bundle.SecondClass")
}
org.example.third.bundle.ThirdClass
public <T> Class<T> resolveTheClassName(String className) throws ClassNotFoundException {
return (Class<T>) Class.forName(className);
}
java.lang.ClassNotFoundException: org.example.second.bundle.SecondClass cannot be found by third.bundle_1.0.0.SNAPSHOT at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:516)
at org.eclipse.osgi.internal.loader.ModuleClassLoader.loadClass(ModuleClassLoader.java:171)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:264)
at org.example.third.bundle.ThirdClassA.getContentType(ThirdClassA.java:100)
at org.example.third.bundle.ThirdClassB$ReceivingTask.run(ThirdClassB.java:135)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
first.bundle/pom.xml
<dependencies>
<groupId>${project.groupId}</groupId>
<artifactId>second.package</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>provided</scope>
<groupId>${project.groupId}</groupId>
<artifactId>third.package</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>provided</scope>
</dependencies>
second.bundle/pom.xml
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Bundle-SymbolicName>${project.name}</Bundle-SymbolicName>
<Embed-Dependency>*;scope=compile|runtime</Embed-Dependency>
<Embed-Directory>OSGI-INF/lib</Embed-Directory>
<Embed-Transitive>true</Embed-Transitive>
<Import-Package>
*;resolution:=optional
</Import-Package>
<Export-Package>
org.example.second.bundle
</Export-Packlage>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
third.bundle/pom.xml
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Bundle-SymbolicName>${project.name}</Bundle-SymbolicName>
<Embed-Dependency>*;scope=compile|runtime</Embed-Dependency>
<Embed-Directory>OSGI-INF/lib</Embed-Directory>
<Embed-Transitive>true</Embed-Transitive>
<Import-Package>
*;resolution:=optional
</Import-Package>
<Export-Package>
org.example.third.bundle
</Export-Packlage>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
The class loaders are configured in a graph with edges for each package imported. So if C does not have an import edge for a package in B, then C cannot class load a class from that package in B.
If C has an object from B, it could use the class loader for the class of the object from B in the Class.forName call. But generally one should not be using Class.forName in OSGi. It would be better to use OSGi services. Then C can locate interesting objects in the OSGi service registry.
Found a solution!
public <T> Class<T> resolveTheClassName(String className) throws ClassNotFoundException {
BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass()).getBundleContext();
List<Class<?>> foundClasses = findClass(bundleContext, serializedContentType);
if(foundClasses.isEmpty()) {
return (Class<T>)Class.forName(serializedContentType);
} else {
return (Class<T>)foundClasses.get(0);
}
}
private List<Class<?>> findClass(BundleContext context, String name) {
List<Class<?>> result = new ArrayList<Class<?>>();
for (Bundle b : context.getBundles()) {
try {
Class<?> c = b.loadClass(name);
result.add(c);
} catch (ClassNotFoundException e) {
// No problem, this bundle doesn't have the class
}
}
return result;
}
How to load a class by classname string from OSGi runtime environment?

Spring RestDocs generating pages for frontend

My spring RestApi Application successfully generates snippets using RestDoc but I'm not able use the snippets to automatically generate pages to run on frontend. http://localhost/docs returns 404 and no html is generated in static/docs/
so far my pom.xml looks like this
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<scope>test</scope>
</dependency>
<plugins>
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<version>1.5.3</version>
<executions>
<execution>
<id>generate-docs</id>
<phase>prepare-package</phase>
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<backend>html</backend>
<doctype>book</doctype>
<outputDirectory>
${project.build.outputDirectory}/static/docs
</outputDirectory>
<attributes>
<snippets>${project.build.directory}/generated-snippets</snippets>
</attributes>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
My Junit Api Test looks like this
#AutoConfigureMockMvc
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = AppLoader.class)
public class ApiDocumentationJUnit5IntegrationTest {
#Rule
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("target/generated-snippets");
#Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
private ObjectMapper objectMapper = new ObjectMapper();
private RestDocumentationResultHandler documentationHandler;
#Before
public void setUp() {
this.documentationHandler = document("{method-name}",
preprocessRequest(removeHeaders("Authorization")),
preprocessResponse(prettyPrint()));
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
}
#Test
public void responseCodeTest() throws Exception {
this.mockMvc.perform(
get("/cms/status")
.accept(MediaType.APPLICATION_JSON)
)
.andExpect(status().isOk())
.andDo(
document("status")
);
}
#Test
public void retrieveDocumentTest() throws Exception {
this.mockMvc.perform(get("/api/active", 1L))
.andExpect(status().isOk()).andDo(
document("active")
);
}
}

How to generate XSDs from java beans having java.time.LocalDate?

I'm trying to use jaxb2-maven-plugin to generate xsd from java beans.
But I constantly get the following error:
java.time.LocalDate is a non-static inner class, and JAXB can't handle those.
this problem is related to the following location:
at java.time.LocalDate (Unknown Source)
The bean is like:
public class MyBean {
private LocalDate date;
}
I therefore created a jaxb mapping file and an adapter converting between java.time.LocalDate and xs:date. But the error just remains the same.
public class LocalDateAdapter {
private static DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyyMMdd");
public static LocalDate unmarshal(String v) throws Exception {
return fmt.parseLocalDate(v);
}
public static String marshal(LocalDate v) throws Exception {
return v.toString("yyyyMMdd");
}
}
src/main/resources/xsdbindings.xml:
<?xml version="1.0" encoding="UTF-8"?>
<bindings xmlns="http://java.sun.com/xml/ns/jaxb"
xmlns:xsi="http://www.w3.org/2000/10/XMLSchema-instance"
xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
xsi:schemaLocation="http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd"
version="2.1">
<globalBindings>
<javaType name="java.time.LocalDate" xmlType="xs:date"
parseMethod="my.path.LocalDateAdapter.unmarshal"
printMethod="my.path.LocalDateAdapter.marshal"
/>
</globalBindings>
</bindings>
maven pom.xml
...
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<version>2.3.1</version>
<executions>
<execution>
<id>schemagen</id>
<goals>
<goal>schemagen</goal>
</goals>
</execution>
</executions>
<configuration>
<bindingFiles>xsdbindings.jaxb</bindingFiles>
<verbose>true</verbose>
</configuration>
</plugin>
</plugins>
</build>
</project>

configure liquibase rollback command from maven

I am trying to perform a liquibase rollback command on an application
I have my changeset:
<changeSet id="dmsTestSQL" author="ahmed"
dbms="mysql">
<sqlFile path="db/changelog/sql/add.sql"
encoding="UTF-8"
splitStatements="true"
stripComments="true"/>
<rollback>
<sqlFile path="db/changelog/sql/rollback.sql"/>
</rollback>
</changeSet>
and I have my configuration class:
#Configuration
public class LiquibaseConfiguration {
#Value("${com.autogravity.dms.liquibase.changelog}")
private String changelog;
#Value("${spring.datasource.url}")
private String url;
#Value("${spring.datasource.username}")
private String username;
#Value("${spring.datasource.password}")
private String password;
/*#Value("${liquibase.rollback-file}")
private String rollback;*/
#Bean
public SpringLiquibase liquibase() {
SpringLiquibase liquibase = new SpringLiquibase();
liquibase.setDataSource(dataSource());
liquibase.setChangeLog(changelog);
//liquibase.setRollbackFile(new File(rollback));
return liquibase;
}
public MysqlDataSource dataSource() {
MysqlDataSource ds=new MysqlDataSource();
ds.setURL(url);
ds.setUser(username);
ds.setPassword(password);
ds.setAutoReconnect(true);
ds.setCreateDatabaseIfNotExist(true);
return ds;
}
}
I also have created mvn plugin in my pom file:
<plugin>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-maven-plugin</artifactId>
<version>2.0.2</version>
<executions>
<execution>
<phase>process-resources</phase>
<configuration>
<tag>${project.version}</tag>
<dropFirst>false</dropFirst>
<changeLogFile>classpath:/db/changelog/db.changelog.xml</changeLogFile>
<driver>com.mysql.jdbc.Driver</driver>
<url>jdbc:mysql://localhost:3306/db</url>
<username>root</username>
<password>test</password>
</configuration>
<goals>
<goal>rollback</goal>
<goal>tag</goal>
</goals>
</execution>
</executions>
</plugin>
I try to run command liquibase:rollback -Dliquibase.rollbackTag=1.0 but then I get the error :
Failed to execute goal org.liquibase:liquibase-maven-plugin:2.0.2:rollback (default-cli) on project dealer-microservice: The driver has not been specified either as a parameter or in a properties file. -> [Help 1]
I don't understand the previous error and I tried to search it but found nothing. can I get help in interpreting this error

Categories

Resources