Spring MVC 5 Rest API convert XML to Bean using JAXB - spring

We are migrating Jersey Endpoints to Spring MVC Rest endpoints. These endpoints consumes and produces XML. But Spring MVC is unable to convert the XML input bean because XML tag and Bean's property name is different. I have to take request as String and Convert it using JAXB explicitly. Is there any better way to do that?
Web Configuration
#EnableWebMvc
#Configuration
public class WebConfig implements WebMvcConfigurer {
public static final String JAXB_PACKAGE = "com.comp.platform.dataholder";
#Autowired
private Jackson2ObjectMapperBuilder builder;
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new StringHttpMessageConverter());
converters.add(new FormHttpMessageConverter());
converters.add(createXmlHttpMessageConverter());
converters.add(createMappingJacksonHttpMessageConverter(builder));
// converters.add(createMappingJackson2XmlHttpMessageConverter(xmlMapper));
Jaxb2RootElementHttpMessageConverter jaxb2RootElementHttpMessageConverter = new Jaxb2RootElementHttpMessageConverter();
jaxb2RootElementHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_XML));
converters.add(jaxb2RootElementHttpMessageConverter);
}
#Bean
public Jackson2ObjectMapperBuilder customJson() {
return new Jackson2ObjectMapperBuilder().indentOutput(true).serializationInclusion(JsonInclude.Include.NON_NULL)
.propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE).failOnUnknownProperties(false);
}
#Bean
#Primary
public ObjectMapper createObjectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper mapper = builder.build();
mapper.setSerializationInclusion(Include.NON_NULL);
return mapper;
}
#Bean
public Jaxb2Marshaller jaxb2Marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setClassesToBeBound(NonFinancialDataHolder.class, JaxbDataHolder.class, ViestimateDataHolder.class,CTRequest.class,com.app.rest.PolicyDetailRequest.class,com.app.rest.TransactionSubmitRequest.class,com.app.rest.AgentSuppressInfoRequest.class);
return marshaller;
}
/**
* #return
*/
#Bean
public MappingJackson2HttpMessageConverter createMappingJacksonHttpMessageConverter(
Jackson2ObjectMapperBuilder builder) {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(builder.build());
return converter;
}
#Autowired
Jaxb2Marshaller jaxb2Marshaller;
private HttpMessageConverter<Object> createXmlHttpMessageConverter() {
Assert.notNull(jaxb2Marshaller, "Jaxb Marshaller dependency required");
MarshallingHttpMessageConverter xmlConverter = new MarshallingHttpMessageConverter(jaxb2Marshaller);
xmlConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_XML));
return xmlConverter;
}
public JAXBContext getMarshaller() throws JAXBException {
return JAXBContext.newInstance(JAXB_PACKAGE);
}
}
Bean
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"_UserId",
"_Password",
"_RequestDateTime",
"_Policy"
})
#XmlRootElement(name = "PolicyDetailRequest")
public class PolicyDetailRequest
{
#XmlElement(name = "UserId")
protected String _UserId;
#XmlElement(name = "Password")
protected String _Password;
#XmlElement(name = "RequestDateTime")
protected String _RequestDateTime;
#XmlElement(name = "Policy")
protected String _Policy;
}
Rest Endpoint
#Slf4j
#RestController
#RequestMapping("/api")
public class api
{
#Autowired
Jaxb2Marshaller jaxb2Marshaller;
#PostMapping(value="/PolicyResponse",produces=MediaType.APPLICATION_XML_VALUE,consumes = MediaType.APPLICATION_XML_VALUE)
#ResponseBody
public PolicyDetailResponse getPolicyResponse(#RequestBody String data)
{
PolicyDetailRequest request = (PolicyDetailRequest) jaxb2Marshaller.unmarshal(new StreamSource(new StringReader(data)));
PolicyDetailResponse response = new PolicyDetailResponse();
InternetRSHandler handler = new InternetRSHandler();
response= handler.getPolicyRSResponse(request);
return response;
}
}
Input Request
<PolicyDetailRequest>
<UserId>1234567890</UserId>
<Password>password</Password>
<RequestDateTime>20200420-15:07:23</RequestDateTime>
<Policy>A76ABCD12</Policy>
</PolicyDetailRequest>

Related

Sprinboot Jackson Date Serialize

i face a litle problem with Jackson on springboot web, i'm expecting this kind of response.
{
'date': 'string of date'
}
but i get back this
[
0: java.sql.date,
1: long number of timestamp
]
please how to configure springboot to avoid this?
#Configuration
public class BootConfiguration {
#Bean
#Primary
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
#Bean
public JavaTimeModule javaTimeModule() {
return new JavaTimeModule();
}
#Bean
public Jdk8Module jdk8TimeModule() {
return new Jdk8Module();
}
#Bean
public CoreJackson2Module coreJackson2Module() {
return new CoreJackson2Module();
}
#Bean
public OAuth2AuthorizationServerJackson2Module authorizationServerJackson2Module(){
return new OAuth2AuthorizationServerJackson2Module();
}
public List<Module> securityModules(){
ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader();
return SecurityJackson2Modules.getModules(classLoader);
}
#Bean
#Primary
#Order(Ordered.HIGHEST_PRECEDENCE)
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper().findAndRegisterModules();
mapper.registerModule(coreJackson2Module());
mapper.registerModule(javaTimeModule());
mapper.registerModule(jdk8TimeModule());
mapper.registerModules(securityModules());
mapper.registerModule(authorizationServerJackson2Module());
mapper.addMixIn(UserPrincipal.class, Object.class);
return mapper;
}
}
this is the controller method where
#GetMapping(value = "/authentications", params = {"pge","lmt", "slug"})
public ResponseEntity<?> getLastLogins(
Authentication authentication,
#RequestParam("lmt")Integer limit,
#RequestParam("pge")Integer page,
#RequestParam("slug")String slug){
return new ResponseEntity<>(loginManager.findAllAuthentications(authentication.getName(),slug,limit,page), HttpStatus.OK);
}
}
and this is the model containing the properties i try to get back
#Data
public class User implements Serializable {
private String firstName;
private String lastName;
private Date date;
private Role role;
}

Spring WS - return non-MTOM on MTOM request

I have a SOAP service that receives a binary attachment (MTOM). The issue is that I would like to return a non-MTOM response (a "clean" SOAP return). The current code return a multipart response and uses "xop+xml" content-type.
The code is:
#Configuration
public class MyWebServiceConfig {
#Value("${config1}")
private String contextPath;
#Value("${config2}")
private String namespace;
#Value("${config3}")
private String wsdl;
#Bean
ServletRegistrationBean<?> webServicesRegistration(ApplicationContext ctx) {
MessageDispatcherServlet messageDispatcherServlet = new MessageDispatcherServlet();
messageDispatcherServlet.setApplicationContext(ctx);
messageDispatcherServlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean<>(messageDispatcherServlet, contextPath, "*.wsdl");
}
#Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath(namespace);
marshaller.setMtomEnabled(true);
return marshaller;
}
#Bean
public MarshallingPayloadMethodProcessor methodProcessor(Jaxb2Marshaller marshaller) {
return new MarshallingPayloadMethodProcessor(marshaller);
}
#Bean
DefaultMethodEndpointAdapter endpointAdapter(MarshallingPayloadMethodProcessor methodProcessor) {
DefaultMethodEndpointAdapter adapter = new DefaultMethodEndpointAdapter();
adapter.setMethodArgumentResolvers(Collections.singletonList(methodProcessor));
adapter.setMethodReturnValueHandlers(Collections.singletonList(methodProcessor));
return adapter;
}
#Bean
public SimpleWsdl11Definition contentStore() {
SimpleWsdl11Definition definition = new SimpleWsdl11Definition();
definition.setWsdl(new ClassPathResource(wsdl));
return definition;
}
}
I have tried changing to setMtomEnabled(false) - but then the request is not handled correctly.
Is the anyway of receiving MTOM - but returning a non-MTOM response?
Edit:
Endpoint looks like this:
#Endpoint
public class MyEndpoint {
private ObjectFactory objectFactory;
public MyEndpoint() {
this.objectFactory = new ObjectFactory();
}
#PayloadRoot(localPart = "SendMyRequest", namespace = "somenamespace")
#ResponsePayload
public JAXBElement<MyReceipt> store(#RequestPayload JAXBElement<MyRequest> storeContentRequest) throws IOException {
MyReceipt receipt = new MyReceipt();
receipt.setf1(123);
receipt.setf2(456);
return this.objectFactory.createMyResponse(receipt);
}
}

PayloadRootSmartSoapEndpointInterceptor Intercepts multiple EndPoints

I'm trying to add a Custom Interceptors to the interceptors List in my EndPoint Config, but i have a problem where PayloadRootSmartSoapEndpointInterceptor intercepts 2 of my Endpoints instead of one, I have Defined 2 SOAP EndPoints using spring-ws.
#EnableWs
#Configuration
#Order(1)
public class Config extends WsConfigurerAdapter {
private String namespaceBti = "http://tarim.bull.ro/BullTarimWS/BTIService";
private String namespaceBtiLst = "http://tarim.bull.ro/BullTarimWS/BTILSTService";
#Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, "/public/btiWS/*");
}
//Service 1
#Bean(name = "BTIService")
public DefaultWsdl11Definition defaultWsdl11DefinitionBti(#Qualifier("BTISchema") XsdSchema certificateSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("BtiPort");
wsdl11Definition.setLocationUri("/public/btiWS"); //<context-path>
wsdl11Definition.setTargetNamespace(namespaceBti);
wsdl11Definition.setRequestSuffix("Input");
wsdl11Definition.setResponseSuffix("Output");
wsdl11Definition.setSchema(certificateSchema);
return wsdl11Definition;
}
#Bean(name="BTISchema")
public XsdSchema certificateSchemaBti() {
return new SimpleXsdSchema(new ClassPathResource("xml-resources/GETBTI.xsd"));
}
// Service 2
#Bean(name = "BTILSTService") //name of the wsdl in the URL
public DefaultWsdl11Definition defaultWsdl11DefinitionBtiLst(#Qualifier("BTILSTSchema") XsdSchema certificateSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("BtiLstPort");
wsdl11Definition.setLocationUri("/public/btiWS"); //<context-path>
wsdl11Definition.setTargetNamespace(namespaceBtiLst);
wsdl11Definition.setRequestSuffix("Input");
wsdl11Definition.setResponseSuffix("Output");
wsdl11Definition.setSchema(certificateSchema);
return wsdl11Definition;
}
#Bean(name="BTILSTSchema")
public XsdSchema certificateSchemaBtiLst() {
return new SimpleXsdSchema(new ClassPathResource("xml-resources/GETBTILST.xsd"));
}
#Autowired
private WriteBtiDto writeBtiDto;
Adding a Custom Interceptor to the list>
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
interceptors.add(new PayloadRootSmartSoapEndpointInterceptor(
new BtiEndpointInterceptor(), //let Spring Build and Manage The Bean, not me
BtiEndpoint.getNamespaceUri(),
BtiEndpoint.getLocalPart()
));
}
BTI EndPoint
#Endpoint()
public class BtiEndpoint {
private static final String NAMESPACE_URI="http://tarim.bull.ro/BullTarimWS/BTIService";
private static final String LOCAL_PART = "CXMLTYPE-GETBTIInput";
#PayloadRoot(namespace = NAMESPACE_URI, localPart = LOCAL_PART)
#ResponsePayload
public CXMLTYPEGETBTIOutput getBTI(#RequestPayload CXMLTYPEGETBTIInput request){
CXMLTYPEGETBTIOutput response = new CXMLTYPEGETBTIOutput();
return response;
}
// GETTERS AND SETTER FOR NAMESPACE AND LOCAL PART
BTILST EndPoint
#Endpoint()
public class BtiLstEndpoint {
private static final String NAMESPACE_URI="http://tarim.bull.ro/BullTarimWS/BTILSTService";
private static final String LOCAL_PART = "CXMLTYPE-GETBTILSTInput";
#PayloadRoot(namespace = NAMESPACE_URI, localPart = LOCAL_PART)
#ResponsePayload
public CXMLTYPEGETBTILSTOutput getBTI(#RequestPayload CXMLTYPEGETBTILSTInput request){
CXMLTYPEGETBTILSTOutput response = new CXMLTYPEGETBTILSTOutput();
return response;
}
// GETTERS AND SETTER FOR NAMESPACE AND LOCAL PART
EndpointInterceptor
#Component
public class BtiEndpointInterceptor implements EndpointInterceptor {
private static final Log LOG = LogFactory.getLog(BtiEndpointInterceptor.class);
#Override
public boolean handleRequest(MessageContext messageContext, Object o) throws Exception {
LOG.info("1. Global Request Handling");
return true;
}
#Override
public boolean handleResponse(MessageContext messageContext, Object o) throws Exception {
LOG.info("2. Global Response Handling");
return true;
}
#Override
public boolean handleFault(MessageContext messageContext, Object o) throws Exception {
LOG.info("Global Exception Handling");
return true;
}
#Override
public void afterCompletion(MessageContext messageContext, Object endpoint, Exception ex) {
}

Spring JPA Custom Validator Bean Inyection

Any bean inyected into CustomValidator Implementation are always null. Im using Spring Boot 2, the applciation is a REST API, i'm not using MVC.
I have tried everything I have read about this with no luck so far.
this topic here for example did not work for me
Should I validate otherwise?, I've been stuck with this for 2 days already.
This is my Config class:
#Configuration
public class Beans {
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:idiomas/idioma");
messageSource.setUseCodeAsDefaultMessage(true);
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
#Bean
public LocaleResolver localeResolver() {
AcceptHeaderLocaleResolver resolver = new AcceptHeaderLocaleResolver();
resolver.setDefaultLocale(new Locale("es"));
return resolver;
}
#Bean
public LocalValidatorFactoryBean getValidator() {
LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
validatorFactoryBean.setValidationMessageSource(messageSource());
return validatorFactoryBean;
}
#Bean
public CommonsRequestLoggingFilter logFilter() {
CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter();
filter.setIncludeQueryString(true);
filter.setIncludePayload(true);
filter.setMaxPayloadLength(10000);
filter.setIncludeHeaders(true);
filter.setAfterMessagePrefix("REQUEST: ");
return filter;
}
}
My CustomValidator looks like this:
public class AlmacenValidator implements ConstraintValidator {
#Autowired
private AlmacenService servicio;
#Autowired
private ApplicationContext contexto;
#Override
public void initialize(AlmacenValido constraintAnnotation) {
}
#Override
public boolean isValid(Almacen item, ConstraintValidatorContext context) {
//Database calls
}
JPA Entity:
#Entity
#AlmacenValido
#Table(name = "almacenes")
public class Almacen {
//Entity fields
}
The Annotation:
#Documented
#Retention(RUNTIME)
#Target({ TYPE, FIELD, ANNOTATION_TYPE, PARAMETER })
#Constraint(validatedBy = AlmacenValidator.class)
public #interface AlmacenValido {
String message() default "{validacion.almacen}";
Class[] groups() default {};
Class[] payload() default {};
}
I've created test example for you. Please check it https://github.com/alex-petrov81/stackoverflow-answers/tree/master/validation-bean-injection

How to fix xml-less autowiring of service

When I call a service directly in my main() I can query the database and things work fine. When a jersey request comes in and maps the JSON to NewJobRequest I can't use my service because the #Autowire failed.
My app:
public class Main {
public static final URI BASE_URI = getBaseURI();
private static URI getBaseURI() {
return UriBuilder.fromUri("http://localhost/").port(9998).build();
}
protected static HttpServer startServer() throws IOException {
ResourceConfig rc = new PackagesResourceConfig("com.production.api.resources");
rc.getFeatures()
.put(JSONConfiguration.FEATURE_POJO_MAPPING, true);
return GrizzlyServerFactory.createHttpServer(BASE_URI, rc);
}
public static void main(String[] args) throws IOException {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
//if this is uncommented, it'll successfully query the database
//VendorService vendorService = (VendorService)ctx.getBean("vendorService");
//Vendor vendor = vendorService.findByUUID("asdf");
HttpServer httpServer = startServer();
System.out.println(String.format("Jersey app started with WADL available at " + "%sapplication.wadl\nTry out %shelloworld\nHit enter to stop it...", BASE_URI, BASE_URI));
System.in.read();
httpServer.stop();
}
}
My Resource (controller):
#Component
#Path("/job")
public class JobResource extends GenericResource {
#Path("/new")
#POST
public String New(NewJobRequest request) {
return "done";
}
}
Jersey is mapping the JSON post to:
#Component
public class NewJobRequest {
#Autowired
private VendorService vendorService;
#JsonCreator
public NewJobRequest(Map<String, Object> request) {
//uh oh, can't do anything here because #Autowired failed and vendorService is null
}
}
VendorService:
#Service
public class VendorService extends GenericService<VendorDao> {
public Vendor findByUUID(String uuid) {
Vendor entity = null;
try {
return (Vendor)em.createNamedQuery("Vendor.findByUUID")
.setParameter("UUID", uuid)
.getSingleResult();
} catch (Exception ex) {
return null;
}
}
}
-
#Service
public class GenericService<T extends GenericDao> {
private static Logger logger = Logger.getLogger(Logger.class.getName());
#PersistenceContext(unitName = "unit")
public EntityManager em;
protected T dao;
#Transactional
public void save(T entity) {
dao.save(entity);
}
}
My service config:
#Configuration
public class Config {
#Bean
public VendorService vendorService() {
return new VendorService();
}
}
My config
#Configuration
#ComponentScan(basePackages = {
"com.production.api",
"com.production.api.dao",
"com.production.api.models",
"com.production.api.requests",
"com.production.api.requests.job",
"com.production.api.resources",
"com.production.api.services"
})
#Import({
com.production.api.services.Config.class,
com.production.api.dao.Config.class,
com.production.api.requests.Config.class
})
#PropertySource(value= "classpath:/META-INF/application.properties")
#EnableTransactionManagement
public class Config {
private static final String PROPERTY_NAME_DATABASE_URL = "db.url";
private static final String PROPERTY_NAME_DATABASE_USER = "db.user";
private static final String PROPERTY_NAME_DATABASE_PASSWORD = "db.password";
private static final String PROPERTY_NAME_HIBERNATE_DIALECT = "hibernate.dialect";
private static final String PROPERTY_NAME_HIBERNATE_FORMAT_SQL = "hibernate.format_sql";
private static final String PROPERTY_NAME_HIBERNATE_SHOW_SQL = "hibernate.show_sql";
private static final String PROPERTY_NAME_ENTITYMANAGER_PACKAGES_TO_SCAN = "entitymanager.packages.to.scan";
#Resource
Environment environment;
#Bean
public DataSource dataSource() {
MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setUrl(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_URL));
dataSource.setUser(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_USER));
dataSource.setPassword(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_PASSWORD));
return dataSource;
}
#Bean
public JpaTransactionManager transactionManager() throws ClassNotFoundException {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactoryBean().getObject());
return transactionManager;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() throws ClassNotFoundException {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource());
entityManagerFactoryBean.setPersistenceUnitName("unit");
entityManagerFactoryBean.setPackagesToScan(environment.getRequiredProperty(PROPERTY_NAME_ENTITYMANAGER_PACKAGES_TO_SCAN));
entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistence.class);
Properties jpaProperties = new Properties();
jpaProperties.put(PROPERTY_NAME_HIBERNATE_DIALECT, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_DIALECT));
jpaProperties.put(PROPERTY_NAME_HIBERNATE_FORMAT_SQL, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_FORMAT_SQL));
jpaProperties.put(PROPERTY_NAME_HIBERNATE_SHOW_SQL, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_SHOW_SQL));
entityManagerFactoryBean.setJpaProperties(jpaProperties);
return entityManagerFactoryBean;
}
}
The #Path and #POST annotations are JAX-RS, not Spring. So the container is instantiating your endpoints on its own, without any knowledge of Spring beans. You are most likely not getting any Spring logging because Spring is not being used at all.
I've figured out the issue and blogged about it here: http://blog.benkuhl.com/2013/02/how-to-access-a-service-layer-on-a-jersey-json-object/
In the mean time, I'm also going to post the solution here:
I need to tap into the bean that Spring already created so I used Spring's ApplicationContextAware
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext applicationContext;
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public void setApplicationContext (ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
}
And then used that static context reference within my object to be mapped to so I can perform lookups in the service:
public class NewJobRequest {
private VendorService vendorService;
public NewJobRequest() {
vendorService = (VendorService) ApplicationContextProvider.getApplicationContext().getBean("vendorService");
}
#JsonCreator
public NewJobRequest(Map<String, Object> request) {
setVendor(vendorService.findById(request.get("vendorId")); //vendorService is null
}
....
}

Resources