I have to call a microservice from a batch launched from another microservice using spring-batch and openfeign - spring

I don't know if it's possible, but this is my question:
I hava a batch developed using spring-boot and spring-batch, and I have to call another microservice using Feign...
...help!
this is my class Reader
package it.batch.step;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.NonTransientResourceException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.beans.factory.annotation.Autowired;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import it.client.feign.EmailAccountClient;
import it.dto.feign.mail.account.AccountOutDto;
import it.dto.feign.mail.account.SearchAccountFilterDto;
import it.dto.feign.mail.account.SearchAccountResponseDto;
public class Reader implements ItemReader <String> {
private static final Logger LOGGER = LoggerFactory.getLogger(Reader.class);
private int count = 0;
#Autowired
private EmailAccountClient emailAccountClient;
#Override
public String read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
LOGGER.info("read - begin ");
SearchAccountResponseDto clientResponse = emailAccountClient.searchAccount(getFilter());
if (count < clientResponse.getAccounts().size()) {
return convertToJsonString(clientResponse.getAccounts().get(count++));
} else {
count = 0;
}
return null;
}
private static SearchAccountFilterDto getFilter() {
SearchAccountFilterDto filter = new SearchAccountFilterDto();
return filter;
}
private String convertToJsonString(AccountOutDto account) {
ObjectMapper mapper = new ObjectMapper();
String jsonString = "";
try {
jsonString = mapper.writeValueAsString(account);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
LOGGER.info("Contenuto JSON: " + jsonString);
return jsonString;
}
}
...
when I launch the batch I have this error:
java.lang.NullPointerException: null
at it.batch.step.Reader.read(Reader.java:32) ~[classes/:?]
where line 32 is:
SearchAccountResponseDto clientResponse = emailAccountClient.searchAccount(getFilter());
EmailAccountClient is null

Your client is null: your reader is not a #Component so Spring can't autowire the client. You must use a workaround like passing the autowired client through the constructor when you instantiate the reader, like this:
private EmailAccountClient client;
public reader(EmailAccountClient client){
this.client=client;
}
in the other class:
#Autowired
private EmailAccountClient client;
#Bean
public ItemReader<String> reader(){
return new Reader(client)
}

Related

Spring rsocket security with Webflux security

My Application is a Spring Webflux application with Spring boot version 2.6.6.
Since, I have a chat and notification requirement for the logged in user, trying to use RSocket over websocket for notification & messaging along with Webflux for web based application.
Using Spring security for my web application with the config below and it is working. Now, not sure if I will be able to use the same security for RSocket as RSocket over websocket will be established when the user is logged in.
My Webflux security,
/**
*
*/
package com.TestApp.service.admin.spring.security;
import static java.util.stream.Collectors.toList;
import static org.springframework.security.config.Customizer.withDefaults;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.security.reactive.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.messaging.rsocket.RSocketStrategies;
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.config.annotation.rsocket.EnableRSocketSecurity;
import org.springframework.security.config.annotation.rsocket.RSocketSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.messaging.handler.invocation.reactive.AuthenticationPrincipalArgumentResolver;
import org.springframework.security.rsocket.core.PayloadSocketAcceptorInterceptor;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
import org.springframework.security.web.server.authentication.logout.LogoutWebFilter;
import org.springframework.security.web.server.authentication.logout.RedirectServerLogoutSuccessHandler;
import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler;
import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
import org.springframework.security.web.server.authorization.HttpStatusServerAccessDeniedHandler;
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
import org.springframework.security.web.server.csrf.CookieServerCsrfTokenRepository;
import org.springframework.security.web.server.util.matcher.AndServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.OrServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import org.springframework.web.server.WebSession;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.testapp.service.admin.spring.TestAppProperties;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
#EnableWebFluxSecurity
public class AdminSecurityConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(AdminSecurityConfig.class);
private static final String[] DEFAULT_FILTER_MAPPING = new String[] { "/**" };
private static final String authenticateHeaderValue = "TestApp";
private static final String unauthorizedJsonBody = "{\"message\": \"You are not authorized\"}";
#Autowired
private TestAppProperties testAppProps;
#Bean
public SecurityWebFilterChain securitygWebFilterChain(final ServerHttpSecurity http,
final ReactiveAuthenticationManager authManager,
final ServerSecurityContextRepository securityContextRepository,
final TestAppAuthenticationFailureHandler failureHandler,
final ObjectProvider<TestAppLogoutHandler> availableLogoutHandlers) {
http.securityContextRepository(securityContextRepository);
return http.authorizeExchange().matchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.pathMatchers(TestAppProps.getSecurity().getIgnorePatterns()).permitAll()
.anyExchange().authenticated().and().formLogin().loginPage(TestAppProps.getSecurity().getLoginPath())
.authenticationSuccessHandler(authSuccessHandler()).and().exceptionHandling()
.authenticationEntryPoint((exchange, exception) -> Mono.error(exception))
.accessDeniedHandler(new HttpStatusServerAccessDeniedHandler(HttpStatus.UNAUTHORIZED)).and().build();
}
#Bean
public ServerAuthenticationSuccessHandler authSuccessHandler() {
return new TestAppAuthSuccessHandler("/");
}
#Bean
public ServerLogoutSuccessHandler logoutSuccessHandler(String uri) {
RedirectServerLogoutSuccessHandler successHandler = new RedirectServerLogoutSuccessHandler();
successHandler.setLogoutSuccessUrl(URI.create(uri));
return successHandler;
}
#Bean(name = "failure-handler-bean")
public TestAppAuthenticationFailureHandler defaultFailureHandler() {
try {
new ObjectMapper().reader().readTree(unauthorizedJsonBody);
} catch (final IOException e) {
throw new IllegalArgumentException("'unauthorizedJsonBody' property is not valid JSON.", e);
}
return new TestAppAdminAuthFailureHandler(authenticateHeaderValue, unauthorizedJsonBody);
}
#Bean
public AuthenticatedPrinciplaProvider TestAppSecurityPrincipalProvider() {
return new TestAppSecurityContextPrincipleProvider();
}
}
public class TestAppSecurityContextPrincipleProvider implements AuthenticatedPrinciplaProvider {
#Override
public Mono<WhskrUserDetails> retrieveUser() {
return principalMono.flatMap(principal -> {
if (principal instanceof UsernamePasswordAuthenticationToken) {
final TestAppUserDetails user = (TestAppUserDetails) ((UsernamePasswordAuthenticationToken) principal)
.getPrincipal();
LOGGER.debug("User principal found for ID {} Org {} ", user.getUserId(), user.getOrgId());
return Mono.just(user);
}
return Mono.error(() -> new IllegalArgumentException(NO_USER_AUTH_ERROR));
})
}
}
This is working as expected. Have a login page and user gets redirected to the home page after the successful login.
Now, I am adding RSocket over websocket for messaging and notification for the logged in user.
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.security:spring-security-messaging'
implementation 'org.springframework.security:spring-security-rsocket'
implementation 'org.springframework.boot:spring-boot-starter-rsocket'
RSocketSecurityConfig,
#EnableWebFluxSecurity
public class AdminRSocketSecurityConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(AdminRSocketSecurityConfig.class);
private static final String[] DEFAULT_FILTER_MAPPING = new String[] { "/**" };
private static final String authenticateHeaderValue = "TestApp";
private static final String unauthorizedJsonBody = "{\"message\": \"You are not authorized\"}";
#Autowired
private TestAppProperties TestAppProps;
#Autowired
private AuthenticatedPrinciplaProvider secContext;
static final String RSOCKET_CONVERTER_BEAN_NAME = "RSocketAuthConverter";
private static final String HEADERS = "headers";
private static final MimeType COMPOSITE_METADATA_MIME_TYPE = MimeTypeUtils
.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.getString());
private static final MimeType APPLICATION_JSON_MIME_TYPE = MimeTypeUtils
.parseMimeType(WellKnownMimeType.APPLICATION_JSON.getString());
#Bean
public RSocketStrategies rsocketStrategies() {
return RSocketStrategies.builder()
.encoders(encoders -> encoders.add(new Jackson2CborEncoder()))
.decoders(decoders -> decoders.add(new Jackson2CborDecoder()))
.routeMatcher(new PathPatternRouteMatcher())
.build();
}
#Bean
public RSocketMessageHandler messageHandler(RSocketStrategies strategies) {
RSocketMessageHandler handler = new RSocketMessageHandler();
HandlerMethodArgumentResolver resolver = new AuthenticationPrincipalArgumentResolver();
handler.getArgumentResolverConfigurer().addCustomResolver(resolver);
handler.setRSocketStrategies(strategies);
return handler;
}
#Bean
public PayloadSocketAcceptorInterceptor authorization(final ReactiveAuthenticationManager authManager,
final RSocketSecurity security) {
security.authorizePayload(authorize -> authorize.setup().authenticated()).authenticationManager(authManager);
return security.build();
}
}
RSocketController,
#Controller
public class RSocketController {
private static final Logger LOGGER = LoggerFactory.getLogger(RSocketController.class);
private static final Map<Integer, Map<Integer, RSocketRequester>> CLIENT_REQUESTER_MAP = new HashMap<>();
static final String SERVER = "Server";
static final String RESPONSE = "Response";
static final String STREAM = "Stream";
static final String CHANNEL = "Channel";
#Autowired
private AuthenticatedPrinciplaProvider secContext;
#ConnectMapping
// void onConnect(RSocketRequester rSocketRequester, #Payload Integer userId) {
void onConnect(RSocketRequester rSocketRequester) {
secContext.retrieveUser().flatMap(usr -> {
LOGGER.info("Client connect request for userId {} ", usr.getUserId());
rSocketRequester.rsocket().onClose().doFirst(() -> {
CLIENT_REQUESTER_MAP.put(usr.getUserId(), rSocketRequester);
}).doOnError(error -> {
LOGGER.info("Client connect request for userId {} ", usr.getUserId());
}).doFinally(consumer -> {
LOGGER.info("Removing here for userId {} ", usr.getUserId());
if (CLIENT_REQUESTER_MAP.get(usr.getBranchId()) != null) {
CLIENT_REQUESTER_MAP.remove(usr.getUserId(), rSocketRequester);
}
}).subscribe();
return Mono.empty();
}).subscribe();
}
}
From the RSocket over WebSocket client, the call is not going to the controller as auth is failing.
But, When I set "authorize.setup().permitAll()" in my RSocketSecurityConfig authorization(), the call goes to the controller, but the retrieveUser() fails.
I am not sure, How can I use the same security which is being used for my web based application for RSocket security as well?
So, When user is not logged in to my web app, the rsocket over websocket should fail and it should work only when the user is logged in. The RSocket initial call is happening once the user is logged in.

Reactive way of reading YAML with Jackson using Spring boot webflux

The yamlObjectMapper in configuration
#Bean
public ObjectMapper yamlObjectMapper() {
ObjectMapper yamlObjectMapper = new ObjectMapper(new YAMLFactory().disable(YAMLGenerator.Feature
.WRITE_DOC_START_MARKER));
yamlObjectMapper.findAndRegisterModules();
return yamlObjectMapper;
}
The Service to parse yaml file
#Service
public class CustomerService {
#Autowired
#Qualifier("yamlObjectMapper")
private ObjectMapper yamlObjectMapper;
public Customer get() {
try {
InputStream inputStream = ResourceUtils.getURL("classpath:/files/test.yaml").openStream();
return yamlObjectMapper.readValue(inputStream, Customer.class);
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
#Data
public static class Customer {
private String name;
private String surname;
private String email;
}
}
I guess IO operations are blocking, how this can be done using reactive way?
I would rather use configuration binding since probably you need to read it once.
package com.vob.webflux.webfilter.controller;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
#Component
#PropertySource(value = "classpath:config.yml", factory= YamlPropertySourceFactory.class)
#Getter
public class YamlFooProperties {
#Value("${test}")
private String test;
}
Factory
package com.vob.webflux.webfilter.controller;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
import java.io.IOException;
import java.util.Properties;
public class YamlPropertySourceFactory implements PropertySourceFactory {
#Override
public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource)
throws IOException {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(encodedResource.getResource());
Properties properties = factory.getObject();
return new PropertiesPropertySource(encodedResource.getResource().getFilename(), properties);
}
}
Source factory from

How to call other eureka client in a Zuul server

application.properties
zuul.routes.commonservice.path=/root/path/commonservice/**
zuul.routes.commonservice.service-id=commonservice
zuul.routes.customer.path=/root/path/customer/**
zuul.routes.customer.service-id=customer
zuul.routes.student.path=/root/path/student/**
zuul.routes.student.service-id=student
and below is my custom filter
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.openreach.gateway.common.constant.CommonConstant;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
#Component
public class HeaderFilter extends ZuulFilter {
private static final Logger log = LoggerFactory.getLogger(HeaderFilter.class);
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 1;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpSession httpSession = context.getRequest().getSession();
String idOrEmail = context.getRequest().getHeader("coustom");
if (httpSession.getAttribute("someAttributes") == null) {
if (idOrEmail != null) {
//call the common-service and get details and set it first
//then call the customer service with common-service details
} else {
//call the customer service
}
} else {
log.info("data excits");
// routrs the request to the backend with the excisting data details
}
context.addZuulResponseHeader("Cookie", "JSESSIONID=" + httpSession.getId());
return null;
}
}
I'm using the ribbon load balancer with zuul. My problem is that how should I call the common-service first? I need all my requests to check the header value and then call the actual service end point.
First, use the #LoadBalanced qualifier to create your RestTemplate bean which is load balanced.
#LoadBalanced
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
And Inject the bean into the filter
#Autowired
RestTemplate restTemplate;
Then you can get result by restTemplate's method like below
String result = restTemplate.postForObject("http://commonservice/url", object, String.class);
ref: http://cloud.spring.io/spring-cloud-static/spring-cloud.html#_spring_resttemplate_as_a_load_balancer_client

How to use spring to marshal and unmarshal xml?

I have a spring boot project. I have a few xsds in my project. I have generated the classes using maven-jaxb2-plugin. I have used this tutorial to get a sample spring boot application running.
import org.kaushik.xsds.XOBJECT;
#SpringBootApplication
public class JaxbExample2Application {
public static void main(String[] args) {
//SpringApplication.run(JaxbExample2Application.class, args);
XOBJECT xObject = new XOBJECT('a',1,2);
try {
JAXBContext jc = JAXBContext.newInstance(User.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(xObject, System.out);
} catch (PropertyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JAXBException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
But my concern is that I need to have all the jaxb classes of the schema mapped. Also is there something in Spring that I can use to make my task easier. I have looked at the Spring OXM project but it had application context configured in xml. Does spring boot have anything that I can use out of the box. Any examples will be helpful.
Edit
I tried xerx593's answer and I ran a simple test using main method
JaxbHelper jaxbHelper = new JaxbHelper();
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setClassesToBeBound(XOBJECT.class);
jaxbHelper.setMarshaller(marshaller);
XOBJECT xOBJECT= (PurchaseOrder)jaxbHelper.load(new StreamSource(new FileInputStream("src/main/resources/PurchaseOrder.xml")));
System.out.println(xOBJECT.getShipTo().getName());
It ran perfectly fine. Now I just need to plug it in using spring boot.
OXM is definitely the right for you!
A simple java configuration of a Jaxb2Marshaller would look like:
//...
import java.util.HashMap;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
//...
#Configuration
public class MyConfigClass {
#Bean
public Jaxb2Marshaller jaxb2Marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setClassesToBeBound(new Class[]{
//all the classes the context needs to know about
org.kaushik.xsds.All.class,
org.kaushik.xsds.Of.class,
org.kaushik.xsds.Your.class,
org.kaushik.xsds.Classes.class
});
// "alternative/additiona - ly":
// marshaller.setContextPath(<jaxb.context-file>)
// marshaller.setPackagesToScan({"com.foo", "com.baz", "com.bar"});
marshaller.setMarshallerProperties(new HashMap<String, Object>() {{
put(javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT, true);
// set more properties here...
}});
return marshaller;
}
}
In your Application/Service class you could approach like this:
import java.io.InputStream;
import java.io.StringWriter;
import javax.xml.bind.JAXBException;
import javax.xml.transform.Result;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
#Component
public class MyMarshallerWrapper {
// you would rather:
#Autowired
private Jaxb2Marshaller marshaller;
// than:
// JAXBContext jc = JAXBContext.newInstance(User.class);
// Marshaller marshaller = jc.createMarshaller();
// marshalls one object (of your bound classes) into a String.
public <T> String marshallXml(final T obj) throws JAXBException {
StringWriter sw = new StringWriter();
Result result = new StreamResult(sw);
marshaller.marshal(obj, result);
return sw.toString();
}
// (tries to) unmarshall(s) an InputStream to the desired object.
#SuppressWarnings("unchecked")
public <T> T unmarshallXml(final InputStream xml) throws JAXBException {
return (T) marshaller.unmarshal(new StreamSource(xml));
}
}
See Jaxb2Marshaller-javadoc, and a related Answer
If you just want serializing/deserializing bean with XML. I think jackson fasterxml is one good choice:
ObjectMapper xmlMapper = new XmlMapper();
String xml = xmlMapper.writeValueAsString(new Simple()); // serializing
Simple value = xmlMapper.readValue("<Simple><x>1</x><y>2</y></Simple>",
Simple.class); // deserializing
maven:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
Refer: https://github.com/FasterXML/jackson-dataformat-xml
Spring BOOT is very smart and it can understand what you need with a little help.
To make XML marshalling/unmarshalling work you simply need to add annotations #XmlRootElement to class and #XmlElement to fields without getter and target class will be serialized/deserialized automatically.
Here is the DTO example
package com.exmaple;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;
import java.util.Date;
import java.util.Random;
#AllArgsConstructor
#NoArgsConstructor
#ToString
#Setter
#XmlRootElement
public class Contact implements Serializable {
#XmlElement
private Long id;
#XmlElement
private int version;
#Getter private String firstName;
#XmlElement
private String lastName;
#XmlElement
private Date birthDate;
public static Contact randomContact() {
Random random = new Random();
return new Contact(random.nextLong(), random.nextInt(), "name-" + random.nextLong(), "surname-" + random.nextLong(), new Date());
}
}
And the Controller:
package com.exmaple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
#Controller
#RequestMapping(value="/contact")
public class ContactController {
final Logger logger = LoggerFactory.getLogger(ContactController.class);
#RequestMapping("/random")
#ResponseBody
public Contact randomContact() {
return Contact.randomContact();
}
#RequestMapping(value = "/edit", method = RequestMethod.POST)
#ResponseBody
public Contact editContact(#RequestBody Contact contact) {
logger.info("Received contact: {}", contact);
contact.setFirstName(contact.getFirstName() + "-EDITED");
return contact;
}
}
You can check-out full code example here: https://github.com/sergpank/spring-boot-xml
Any questions are welcome.
You can use StringSource / StringResult to read / read xml source with spring
#Autowired
Jaxb2Marshaller jaxb2Marshaller;
#Override
public Service parseXmlRequest(#NonNull String xmlRequest) {
return (Service) jaxb2Marshaller.unmarshal(new StringSource(xmlRequest));
}
#Override
public String prepareXmlResponse(#NonNull Service xmlResponse) {
StringResult stringResult = new StringResult();
jaxb2Marshaller.marshal(xmlResponse, stringResult);
return stringResult.toString();
}

Spring QueryDSL set Session from Spring Context

I'm studying the QueryDSL library and implementing it into my DAL project.
What is clear to me is that I need to instantiate a HibernateQuery object and use the QueryDSL methods to define the source of data (from() clause) and the conditions (where() with the BooleanExpressions).
For example consider a User Entity which has a name field and suppose we want to test whether the user with a name equal to "Richie" exists into the DB. I would write the following code to make things done
public boolean richieExists()
{
QUser qUser = QUser.user;
HibernateQuery query = new HibernateQuery(session); // I need a session instance here!
User richie = query.from(qUser).where(qUser.name.eq("Richie")).uniqueResult(qUser);
return (richie!=null);
}
The problem is that the above code should be the method of a Spring's Service object which uses a Repository to execute CRUD operations. This means that I need to retrieve the Session from the EntityManager instance I'm using in the Application Context to instantiate the HibernateQuery object, and this is a problem because the Service object doesn't have a way to return the used EntityManager.
What is the right way/place to write QueryDSL queries?
Here is my DAOConfig.java class with the Spring configuration (here we define the EntityManagerFactoryBean used by Spring for the Repository operations)
package my.dal.service.dal.config;
import java.util.Properties;
import javax.annotation.Resource;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.hibernate4.HibernateExceptionTranslator;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
#Configuration
#ComponentScan(basePackages = { "my.dal" })
#PropertySource("classpath:dbconnection.properties")
#EnableJpaRepositories("my.dal.repository")
#EnableTransactionManagement
public class DALConfig {
private static final String PROPERTY_NAME_DATABASE_DRIVER = "db.driver_class";
private static final String PROPERTY_NAME_DATABASE_PASSWORD = "db.password";
private static final String PROPERTY_NAME_DATABASE_URL = "db.url";
private static final String PROPERTY_NAME_DATABASE_USERNAME = "db.username";
private static final String PROPERTY_NAME_POOL_INITIAL_SIZE = "pool.initialsize";
private static final String PROPERTY_NAME_POOL_MAX_IDLE = "pool.maxidle";
private static final String PROPERTY_NAME_DAL_CLASSES_PACKAGE = "entities.packages_to_scan";
private static final String PROPERTY_NAME_HIBERNATE_DIALECT = "hibernate.dialect";
private static final String PROPERTY_NAME_HIBERNATE_SHOW_SQL = "hibernate.showsql";
private static final String PROPERTY_NAME_HIBERNATE_FORMAT_SQL = "hibernate.format_sql";
#Resource
private Environment environment;
#Bean
public DataSource dataSource()
{
Properties props = new Properties();
props.put("driverClassName", environment.getRequiredProperty(PROPERTY_NAME_DATABASE_DRIVER));
props.put("url", environment.getRequiredProperty(PROPERTY_NAME_DATABASE_URL));
props.put("username", environment.getRequiredProperty(PROPERTY_NAME_DATABASE_USERNAME));
props.put("password", environment.getRequiredProperty(PROPERTY_NAME_DATABASE_PASSWORD));
props.put("initialSize", environment.getRequiredProperty(PROPERTY_NAME_POOL_INITIAL_SIZE));
props.put("maxIdle", environment.getRequiredProperty(PROPERTY_NAME_POOL_MAX_IDLE));
BasicDataSource bds = null;
try {
bds = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return bds;
}
#Bean
public PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor()
{
PersistenceExceptionTranslationPostProcessor b = new PersistenceExceptionTranslationPostProcessor();
return b;
}
#Bean
public HibernateExceptionTranslator hibernateExceptionTranslator(){
return new HibernateExceptionTranslator();
}
#Bean
public PlatformTransactionManager transactionManager() throws ClassNotFoundException {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return transactionManager;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws ClassNotFoundException {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource());
entityManagerFactoryBean.setPackagesToScan(environment.getRequiredProperty(PROPERTY_NAME_DAL_CLASSES_PACKAGE));
entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.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;
}
}
This is my repository interface
package my.dal.repository;
import my.domain.dal.User;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface IUserRepository extends CrudRepository<User, String>{
}
This is the UserService Service class in which I have to implement the "richieExists" query method
package my.dal.service;
import my.dal.repository.IUserRepository;
import my.domain.dal.QUser;
import my.domain.dal.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import com.mysema.query.jpa.hibernate.HibernateQuery;
#Service
public class UserService {
#Autowired
private IUserRepository repository;
public User find(String username) throws DataRetrievalFailureException
{
User user = null;
user= repository.findOne(username);
if (user == null)
throw new DataRetrievalFailureException("User with username = \"" + username + "\" not found");
else
return user;
}
public User insert(User user) throws DuplicateKeyException
{
if (repository.findOne(user.getUsername()) != null)
throw new DuplicateKeyException("User with username = \"" + user.getUsername() + "\" already exists");
return repository.save(user);
}
public void delete(String username) throws DataRetrievalFailureException
{
if (repository.findOne(username) == null)
throw new DataRetrievalFailureException("User with username =\"" + username + "\" not found");
repository.delete(username);
}
public User update(User user) throws DataRetrievalFailureException
{
if (repository.findOne(user.getUsername()) == null)
throw new DataRetrievalFailureException("User with username = \"" + user.getUsername() + "\" not found");
return repository.save(user);
}
public boolean richieExists()
{
QUser qUser = QUser.user;
HibernateQuery query = new HibernateQuery(session); // I need a session instance here!
User richie = query.from(qUser).where(qUser.username.eq("richie")).uniqueResult(qUser);
return (richie!=null);
}
}
Thank you
I solved this way:
Got the current EntityManager from the persistence context by mean of the proper annotation
#PersistenceContext
EntityManager em;
Next I used the JPAQuery class (not the HibernateQuery one) to build the queries
JPQLQuery query = new JPAQuery(em); // Now just use the query object
Hope this help

Resources