#Retryable is not working when calling from a method - spring-boot

Below is my application class. The flow is like the DEToken class from here and from DEToken I call RestConnection where I have the #retryable method.
#SpringBootApplication
#EnableRetry
public class SpringBootTrfficApplication implements CommandLineRunner {
Enter code here
#Autowired
DEToken deToken;
#Autowired
SyncService syncService;
public static void main(String[] args) {
SpringApplication.run(SpringBootTrfficApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
deToken.getToken();
}
}
DEToken class: from getToken I am calling RestConnect where I have the #Retrable method:
#Service
public class DEToken {
private Logger logger = LogManager.getLogger(getClass());
#Autowired
RestConnection restConnection;
#Autowired
private Environment env;
public String accessToken;
public void getToken() {
System.out.println("hello from get token");
//String getJsonPayload = "{\"Query\":{\"RegisterExtensionWithDE\":{\"pid\": \"\",\"providerInsName\":" +
//env.getProperty("provider.ins") + "}}}";
//String str = restConnection.restPost(
// env.getProperty("rest.de.url"), getJsonPayload);
try {
String getJsonPayload =
"{\"Query\":{\"RegisterExtensionWithDE\":{\"pid\": \"\",\"providerInsName\":" +
env.getProperty("provider.ins") + "}}}";
StringBuffer tokenResult =
restConnection.restPost(env.getProperty("rest.de.url"),
getJsonPayload);
System.out.println(tokenResult);
JSONObject xmlJSONObj = XML.toJSONObject(tokenResult.toString());
JSONObject registration = new JSONObject();
if (xmlJSONObj.has("Registration")) {
registration = xmlJSONObj.getJSONObject("Registration");
if (registration.has("accessToken")) {
accessToken = registration.get("accessToken").toString();
}
else
logger.info("no accessToken from DE");
}
else
logger.info("no Registration object from DE");
}
catch (Exception e) {
logger.error("Exception while fetching accesstoken from DE ");
logger.error(e.getMessage());
}
}
}
My REST connection class where I have retryable method:
#Service
public class RestConnection {
private Logger logger = LogManager.getLogger(getClass());
#Autowired
private Environment env;
public void setBaseUrl(String value, String ip) {
//baseUrl = value;
HttpsURLConnection.setDefaultHostnameVerifier(
(hostname, session) -> hostname.equals(ip));
}
/*
* REST post call
*/
#Retryable(value = {IOException.class, ConnectException.class},
maxAttempts = 4,
backoff = #Backoff(5000))
public StringBuffer restPost(String restUrl, String payload) {
StringBuffer sb = new StringBuffer();
HttpURLConnection conn = null;
try {
URL url = new URL(restUrl);
String protocol = url.getProtocol();
if (protocol.toLowerCase().equals("http")) {
conn = (HttpURLConnection)url.openConnection();
}
else if (protocol.toLowerCase().equals("https")) {
//setTrustedCert();
conn = (HttpsURLConnection)url.openConnection();
}
else {
logger.info("Protocol is neither HTTP nor HTTPS");
}
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("version", env.getProperty("de.version"));
conn.setRequestProperty("accessToken", env.getProperty("access.token"));
conn.setRequestProperty("requestHost", env.getProperty("server.de.host"));
conn.setRequestProperty("requestPort", env.getProperty("server.port"));
conn.setRequestProperty("requestProtocol",
env.getProperty("server.de.protocol"));
PrintWriter pout =
new PrintWriter(
new OutputStreamWriter(
conn.getOutputStream(), "UTF-8"),
true);
pout.print(payload);
pout.flush();
pout.close();
InputStream isi = conn.getInputStream();
InputStreamReader isr = new InputStreamReader(isi);
int numCharsRead1;
char[] charArray1 = new char[1024];
while ((numCharsRead1 = isr.read(charArray1)) > 0) {
sb.append(charArray1, 0, numCharsRead1);
}
isr.close();
isi.close();
}
catch (MalformedURLException e) {
logger.error("MalformedURLException in restAccessTokenPOST..." +
e.getMessage());
//e.printStackTrace();
}
catch (IOException e) {
logger.error("IOException in restAccessTokenPOST..." +
e.getMessage());
e.printStackTrace();
}
catch (Exception e) {
logger.error("Exception in restAccessTokenPOST..." +
e.getMessage());
e.printStackTrace();
}
finally {
if (null != conn)
conn.disconnect();
}
return sb;
}
#Recover
public String helpHere(ConnectException cause) {
System.out.println("Recovery place! ConnectException");
return "Hello";
}
#Recover
public String helpHere(IOException cause) {
System.out.println("Recovery place! ArithmeticException");
return "Hello";
}
#Recover
public String helpHere(Exception cause) {
System.out.println("Recovery place! Exception");
return "Hello";
}
#Recover
public String helpHere() {
System.out.println("Recovery place! Exception");
return "Hello";
}
#Recover
public String helpHere(Throwable cause) {
System.out.println("Recovery place! Throwable");
return "Hello";
}
}

Considering you see your function restPost() implementation,
#Retryable(value = {IOException.class, ConnectException.class},
maxAttempts = 4,
backoff = #Backoff(5000))
public StringBuffer restPost(String restUrl, String payload) {
try {
// Your code
}
catch(IOException ex){ // These catch block handles the exception
// and nothing to throw to retryable.
}
catch(MalformedURLException ex){ // More catch blocks that you
// define to handle exception.
}
}
Here you handle all of the exceptions that can be a cause to revoke the retry and recover methods.
Note: Recoverable methods only execute when a exception is thrown, not handled by any try-catch block.
Whatever exception is raised by method restPost() is handled by the method try-catch block itself and there are no exceptions that had been rethrow by a catch block.
Now, Spring-Retry is unable to get any exception (because it is handled by the method try-catch block). So, no recovery method will be executed.
Solution: you should remove those catch blocks from the method definition on which you want to perform retry or recover.
Please do the needful and it will work like a charm... :)

Related

Embedded Jetty: how to run security handler as soon as http requests arrive?

I am using embedded Jetty along with Jersey. My question is: is it possible to make the SecurityHandler of jetty take effect before the HTTP request reaching the Jersey class?
Here is my code: (I am sorry it may be too much.)
The class where the jetty server initialized:
public class JettyHttpComponent extends AbstractLifeCycleComponent {
private static final String REST_SOURCE_KEY = "jersey.config.server.provider.classnames";
//TODO Security config and implementation
public int start() throws RuntimeException {
Server jettyServer = new Server(8080);
ServletContextHandler context = new ServletContextHandler(jettyServer, "/", ServletContextHandler.SESSIONS|ServletContextHandler.SECURITY);
context.setContextPath("/");
context.setSecurityHandler(basicAuth());
ServletHolder jerseyServlet = context.addServlet(
org.glassfish.jersey.servlet.ServletContainer.class, "/*");
jerseyServlet.setInitOrder(0);
//load rest resources
jerseyServlet.setInitParameter(REST_SOURCE_KEY, IndexService.class.getCanonicalName());
try {
jettyServer.start();
jettyServer.join();
} catch(Exception e) {
e.printStackTrace();
} finally {
jettyServer.destroy();
}
return 0;
}
public int stop() throws RuntimeException {
//close resources
try {
close();
} catch (IOException e) {
throw new RuntimeException(e);
}
System.out.println("Server stopped.");
return 0;
}
private SecurityHandler basicAuth() {
ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
LoginService loginService = new LDAPLoginService();
securityHandler.setLoginService(loginService);
return securityHandler;
}
}
The LDAPLoginService class in basicAuth() is my customized login class extending AbstractLoginService.
The Jersey class handling http request:
#Path("/index")
public class IndexService extends BaseRestService {
#PUT
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response index(#Context SecurityContext securityContext,
#Context HttpHeaders headers,
#Context HttpServletRequest httpRequest,
#QueryParam("algorithm") String algorithm,
#QueryParam("executionMode") String mode,
String request) {
long t1 = System.currentTimeMillis();
String response = null;
IndexContext context = null;
try {
init();
//setup context with security, headers, options and request
ServiceUserContext suc = buildServiceUserContext(securityContext, httpRequest);
if (suc == null) {
return Response.status(Status.UNAUTHORIZED).entity(response).build();
}
ServiceDataContext sdc = buildServiceDataContext(request);
context = IndexContext.builder().algorithm(algorithm).serviceDataContext(sdc).
serviceUserContext(suc).build();
//dispatch service to entity matching core services
dispatch(context);
} catch(Throwable t) {
handlerErrors(t, context);
} finally {
if (context != null) {
close(context);
response = context.getServiceDataContext().getResponse();
System.out.println("Index takes: " + (System.currentTimeMillis() - t1) + " ms");
}
}
return Response.status(Status.OK).entity(response).build();
}
}
In the method buildServiceDataContext(), I called securityContext.getUserPrincipal(), and the LDAPLoginService class extending AbstractLoginService does nothing until securityContext.getUserPrincipal() is reached. Is it possible to run the security check at the very beginning, even before Jersey class begins to handle the request? Thanks.
As #Paul Samsotha suggested, ContainerRequestFilter is a good choice.

How can I add information to a JAXBElement SOAP request?

I have a class generated with JAXB2 form a WSDL. The elements defined in the WSDL are NOT declared as XmlRootElement.
#Service
public class ProblemService extends WebServiceGatewaySupport {
public ProblemResponse addProblem(final Problem problem, final String aNumber) {
final String namespacePrefix = "soapenv";
final String action = "Problem";
final ObjectFactory factory = new ObjectFactory();
final JAXBElement<Problem> request = factory.createProblem(problem);
try {
StringResult result = new StringResult();
getMarshaller().marshal(request, result);
System.out.println(result.toString());
} catch (Exception e) {
e.printStackTrace(System.err);
}
final WebServiceTemplate wst = this.getWebServiceTemplate();
#SuppressWarnings("unchecked")
final JAXBElement<ProblemResponse> response = (JAXBElement<ProblemResponse>) wst
.marshalSendAndReceive(abcConfiguration.getEndpoint(), request, new WebServiceMessageCallback() {
#Override
public void doWithMessage(final WebServiceMessage message) {
try {
prepareSoapHeader(message, namespacePrefix, action);
final SaajSoapMessage ssMessage = (SaajSoapMessage) message;
final SOAPEnvelope envelope = ssMessage.getSaajMessage().getSOAPPart().getEnvelope();
envelope.getBody().setPrefix(namespacePrefix);
final NodeList nl = ssMessage.getSaajMessage().getSOAPPart().getEnvelope().getBody().getChildNodes();
ssMessage.getSaajMessage().getSOAPPart().getEnvelope().getBody().removeChild(nl.item(0));
final SOAPElement se = ssMessage.getSaajMessage().getSOAPPart().getEnvelope().getBody()
.addBodyElement(new QName(action));
se.setPrefix(NAMESPACE_PREFIX_V2);
addUserAuthentification(se);
try {
StringResult result = new StringResult();
getAbcConfiguration().marshaller().marshal(request, result);
System.out.println(result.toString());
} catch (Exception e) {
e.printStackTrace(System.err);
}
System.out.println();
} catch (SoapFaultClientException e) {
logger.error("Error on client side during marshalling of the SOAP request for {}.", action, e);
} catch (SOAPException e) {
logger.error("Error during marshalling of the SOAP request for {}.", action, e);
}
}
});
return response.getValue();
}
}
The generated StringResult looks quiet good but I need to replace some parts in the resulting XML (for instance the prefix) and I need to add some stuff into the SoapBody which are not part of the base class (Problem) before sending the SOAP request to the remote service.
Furthermore I want to modify the header part of the envelope...
How can I achieve this? My application is a SpringBoot application and in the configuration class being used in my service the un-/marshaller are defined this way:
#Bean
public Jaxb2Marshaller marshaller() {
final Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
//setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setContextPath(contextPath);
//marshaller.afterPropertiesSet();
marshaller.setMarshallerProperties(new HashMap<String, Object>() {{
put(javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT, true);
}});
return marshaller;
}
#Bean
public ProblemService problemService(final Jaxb2Marshaller marshaller) throws Exception {
final ProblemService client = new ProblemService();
client.setDefaultUri(this.endpoint);
client.setMarshaller(marshaller);
client.setUnmarshaller(marshaller);
final HttpsUrlConnectionMessageSender msgSender = new HttpsUrlConnectionMessageSender();
client.setMessageSenders(new WebServiceMessageSender[] {msgSender, httpComponentsMessageSender()});
//client.setMessageSender(msgSender);
return client;
}
With this little piece of code I was able to add information to the SoapBody as demanded:
try {
getKpmConfiguration().marshaller().marshal(request, ssMessage.getPayloadResult());
ssMessage. writeTo(System.out);
} catch (/*JAXB*/Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}

Spring Integration: Custom Splitter with Header Enrichment

Is it possible to have an implementation of a message splitter that can return an Iterator AND add custom header information?
For instance if I have the following class
public class CsvFileToIteratorSplitter extends AbstractMessageSplitter {
#Override
protected Object splitMessage(Message<?> message) {
Object payload = message.getPayload();
Assert.isInstanceOf(File.class, payload, "Expected java.io.File in the message payload");
try {
InputStream source = new FileInputStream((File) payload);
BufferedReader reader = new BufferedReader(new InputStreamReader(source));
String header = reader.lines().findFirst().orElse(null);
return MessageBuilder.withPayload(reader.lines().iterator())
.setHeaderIfAbsent("HEADER", header)
.build();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
Then I can add to the header but the payload is actually an instance of Iterator and the split fails
If I modify so that the class is now
public class CsvFileToIteratorSplitter extends AbstractMessageSplitter {
#Override
protected Object splitMessage(Message<?> message) {
log.debug("{}", message.toString());
Object payload = message.getPayload();
Assert.isInstanceOf(File.class, payload, "Expected java.io.File in the message payload");
try {
InputStream source = new FileInputStream((File) payload);
BufferedReader reader = new BufferedReader(new InputStreamReader(source));
return reader.lines().iterator();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
The split works but I lose the header info.
Is there any way to have a functioning split with the ability to add to the header?
You should return an Iterator<MessageBuilder<String>> ...
#SpringBootApplication
public class So44604817Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(So44604817Application.class, args);
context.getBean("in", MessageChannel.class).send(new GenericMessage<>(new File("/tmp/foo.txt")));
context.close();
}
#Bean
#Splitter(inputChannel = "in")
public MySplitter splitter() {
MySplitter splitter = new MySplitter();
splitter.setOutputChannelName("out");
return splitter;
}
#Bean
public MessageChannel out() {
return new MessageChannel() {
#Override
public boolean send(Message<?> message) {
return send(message, -1);
}
#Override
public boolean send(Message<?> message, long timeout) {
System.out.println(message);
return true;
}
};
}
public static class MySplitter extends AbstractMessageSplitter {
#SuppressWarnings("resource")
#Override
protected Object splitMessage(Message<?> message) {
Object payload = message.getPayload();
Assert.isInstanceOf(File.class, payload, "Expected java.io.File in the message payload");
try {
InputStream source = new FileInputStream((File) payload);
final BufferedReader reader = new BufferedReader(new InputStreamReader(source));
final String header = reader.lines().findFirst().orElse(null);
final Iterator<String> iterator = reader.lines().iterator();
Iterator<MessageBuilder<String>> builderIterator = new Iterator<MessageBuilder<String>>() {
private String next;
#Override
public boolean hasNext() {
if (this.next != null) { // handle multiple hasNext() calls.
return true;
}
if (!iterator.hasNext()) {
try {
reader.close();
}
catch (IOException e) {
e.printStackTrace();
}
return false;
}
else {
this.next = iterator.next();
// Handle empty last line
if (next.length() == 0 && !iterator.hasNext()) {
try {
reader.close();
}
catch (IOException e) {
e.printStackTrace();
}
return false;
}
return true;
}
}
#Override
public MessageBuilder<String> next() {
String line = this.next;
this.next = null;
return MessageBuilder
.withPayload(line).setHeaderIfAbsent("HEADER", header);
}
};
return builderIterator;
}
catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
}
Note that your skip(1) is incorrect, since the first line has already been consumed from the reader.
With file:
FOO,BAR
foo,bar
baz.qux
result:
GenericMessage [payload=foo,bar, headers={sequenceNumber=1, HEADER=FOO,BAR, correlationId=42ce2e1f-5337-1f75-d4fe-0d7f366f76f1, id=94e98261-fd49-b4d0-f6a0-3181b27f145b, sequenceSize=0, timestamp=1497713691192}]
GenericMessage [payload=baz.qux, headers={sequenceNumber=2, HEADER=FOO,BAR, correlationId=42ce2e1f-5337-1f75-d4fe-0d7f366f76f1, id=c0b1edd6-adb9-3857-cb7c-70f603f376bc, sequenceSize=0, timestamp=1497713691192}]
JIRA Issue INT-4297 to add this functionality to FileSplitter.

Spring MVC - calling methods in #ResponseBody

I am Spring MVC beginner and I want to call rest in #ResponseBody. My external node server doesn't react on that method. I don't got message about request in my server console. Without UserRest it works. I would be grateful for your help
#Controller
public class AjaxController {
#RequestMapping(value= "user", method=RequestMethod.GET)
public #ResponseBody String login (){
UserRest ur = new UserRest();
Response r = ur.getUserName(2);
Gson gs = new Gson();
String str = gs.toJson(r);
return str;
}
}
Response getUserName(int userID){
Response response = new Response();
StringBuilder total = new StringBuilder();
try {
URL url = new URL(Properties.SERVER_SECURE_URL + "users/" + userID);
urlConnection = (HttpsURLConnection) url.openConnection();
urlConnection.setDoOutput(false);
urlConnection.setDoInput(true);
urlConnection.setRequestMethod("GET");
urlConnection.setRequestProperty("Authorization","1Strajk");
response.setMessageCode(urlConnection.getResponseCode());
if(response.getMessageCode()==Response.MESSAGE_OK) {
InputStream in = urlConnection.getInputStream();
BufferedReader r = new BufferedReader(new InputStreamReader(in));
String line;
while ((line = r.readLine()) != null) {
total.append(line);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(!total.toString().isEmpty()){
response.setObject(total.toString());
}
urlConnection.disconnect();
}
return response;
}
I resolve it. I forgot about SSL connection. Before calling rest I called that method:
public class SSLUtils {
public static void trustEveryone() {
try {
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new X509TrustManager[]{new X509TrustManager(){
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {}
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}}}, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(
context.getSocketFactory());
} catch (Exception e) { // should never happen
e.printStackTrace();
}
}
}

Transactions with Guice and JDBC - Solution discussion

In my application, I need to use pure JDBC together with Guice. However, Guice doesn't provide any built-in support to manage transactions. guice-persist only provides support based on JPA, which I cannot use.
so I tried to implement a simple solution to manage transactions with Guice and JDBC. here is the first version:
use TransactionHolder to store the transaction per thread.
public class JdbcTransactionHolder {
private static ThreadLocal<JdbcTransaction> currentTransaction = new ThreadLocal<JdbcTransaction>();
public static void setCurrentTransaction(JdbcTransaction transaction) {
currentTransaction.set(transaction);
}
public static JdbcTransaction getCurrentTransaction() {
return currentTransaction.get();
}
public static void removeCurrentTransaction() {
currentTransaction.remove();
}
}
implements a transaction manager for JDBC, for now only begin(), getTransaction(), commit() and rollback() method:
public class JdbcTransactionManager implements TransactionManager {
#Inject
private DataSource dataSource;
#Override
public void begin() throws NotSupportedException, SystemException {
logger.debug("Start the transaction");
try {
JdbcTransaction tran = JdbcTransactionHolder.getCurrentTransaction();
Connection conn = null;
if(tran == null) {
conn = dataSource.getConnection();
}
else {
conn = tran.getConnection();
}
// We have to put the connection in the holder so that we can get later
// from the holder and use it in the same thread
logger.debug("Save the transaction for thread: {}.", Thread.currentThread());
JdbcTransactionHolder.setCurrentTransaction(new JdbcTransaction(conn));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
#Override
public void commit() throws RollbackException, HeuristicMixedException,
HeuristicRollbackException, SecurityException,
IllegalStateException, SystemException {
logger.debug("Commit the transaction");
try {
logger.debug("Get the connection for thread: {}.", Thread.currentThread());
Transaction transaction = JdbcTransactionHolder.getCurrentTransaction();
transaction.commit();
}
catch(Exception e) {
throw new RuntimeException(e);
}
finally {
JdbcTransactionHolder.removeCurrentTransaction();
}
}
#Override
public Transaction getTransaction() throws SystemException {
logger.debug("Get transaction.");
final JdbcTransaction tran = JdbcTransactionHolder.getCurrentTransaction();
if(tran == null) {
throw new DBException("No transaction is availble. TransactionManager.begin() is probably not yet called.");
}
return tran;
}
#Override
public void rollback() throws IllegalStateException, SecurityException,
SystemException {
logger.debug("Rollback the transaction");
try {
logger.debug("Get the transaction for thread: {}.", Thread.currentThread());
Transaction conn = JdbcTransactionHolder.getCurrentTransaction();
conn.commit();
}
catch(Exception e) {
throw new RuntimeException(e);
}
finally {
JdbcTransactionHolder.removeCurrentTransaction();
}
}
}
implement a wrapper for DataSource which can get the current connection from the transaction holder if a transaction has been started:
public class JdbcDataSource implements DataSource {
private final static org.slf4j.Logger logger = LoggerFactory.getLogger(JdbcDataSource.class);
private DataSource dataSource;
public JdbcDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
#Override
public PrintWriter getLogWriter() throws SQLException {
return dataSource.getLogWriter();
}
#Override
public int getLoginTimeout() throws SQLException {
return dataSource.getLoginTimeout();
}
#Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return dataSource.getParentLogger();
}
#Override
public void setLogWriter(PrintWriter out) throws SQLException {
this.dataSource.setLogWriter(out);
}
#Override
public void setLoginTimeout(int seconds) throws SQLException {
this.dataSource.setLoginTimeout(seconds);
}
#Override
public boolean isWrapperFor(Class<?> arg0) throws SQLException {
return this.isWrapperFor(arg0);
}
#Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return this.unwrap(iface);
}
#Override
public Connection getConnection() throws SQLException {
JdbcTransaction transaction = JdbcTransactionHolder.getCurrentTransaction();
if(transaction != null) {
// we get the connection from the transaction
logger.debug("Transaction exists for the thread: {}.", Thread.currentThread());
return transaction.getConnection();
}
Connection conn = this.dataSource.getConnection();
conn.setAutoCommit(false);
return conn;
}
#Override
public Connection getConnection(String username, String password)
throws SQLException {
JdbcTransaction transaction = JdbcTransactionHolder.getCurrentTransaction();
if(transaction != null) {
// we get the connection from the transaction
logger.debug("Transaction exists for the thread: {}.", Thread.currentThread());
return transaction.getConnection();
}
return this.dataSource.getConnection(username, password);
}
}
then create a DataSourceProvider so that we can inject DataSource to any POJO using guice:
public class DataSourceProvider implements Provider {
private static final Logger logger = LoggerFactory.getLogger(DataSourceProvider.class);
private DataSource dataSource;
public DataSourceProvider() {
JdbcConfig config = getConfig();
ComboPooledDataSource pooledDataSource = new ComboPooledDataSource();
try {
pooledDataSource.setDriverClass(config.getDriver());
} catch (Exception e) {
throw new RuntimeException(e);
}
pooledDataSource.setJdbcUrl(config.getUrl());
pooledDataSource.setUser(config.getUsername());
pooledDataSource.setPassword(config.getPassword() );
pooledDataSource.setMinPoolSize(config.getMinPoolSize());
pooledDataSource.setAcquireIncrement(5);
pooledDataSource.setMaxPoolSize(config.getMaxPoolSize());
pooledDataSource.setMaxStatements(config.getMaxStatementSize());
pooledDataSource.setAutoCommitOnClose(false);
this.dataSource = new JdbcDataSource(pooledDataSource);
}
private JdbcConfig getConfig() {
JdbcConfig config = new JdbcConfig();
Properties prop = new Properties();
try {
//load a properties file from class path, inside static method
prop.load(JdbcConfig.class.getResourceAsStream("/database.properties"));
//get the property value and print it out
config.setDriver(prop.getProperty("driver"));
config.setUrl(prop.getProperty("url"));
config.setUsername(prop.getProperty("username"));
config.setPassword(prop.getProperty("password"));
String maxPoolSize = prop.getProperty("maxPoolSize");
if(maxPoolSize != null) {
config.setMaxPoolSize(Integer.parseInt(maxPoolSize));
}
String maxStatementSize = prop.getProperty("maxStatementSize");
if(maxStatementSize != null) {
config.setMaxStatementSize(Integer.parseInt(maxStatementSize));
}
String minPoolSize = prop.getProperty("minPoolSize");
if(minPoolSize != null) {
config.setMinPoolSize(Integer.parseInt(minPoolSize));
}
}
catch (Exception ex) {
logger.error("Failed to load the config file!", ex);
throw new DBException("Cannot read the config file: database.properties. Please make sure the file is present in classpath.", ex);
}
return config;
}
#Override
public DataSource get() {
return dataSource;
}
and then implement TransactionalMethodInterceptor to manage the transaction for the method with Transactional annotation:
public class TransactionalMethodInterceptor implements MethodInterceptor {
private final static Logger logger = LoggerFactory.getLogger(TransactionalMethodInterceptor.class);
#Inject
private JdbcTransactionManager transactionManager;
#Override
public Object invoke(MethodInvocation method) throws Throwable {
try {
// Start the transaction
transactionManager.begin();
logger.debug("Start to invoke the method: " + method);
Object result = method.proceed();
logger.debug("Finish invoking the method: " + method);
transactionManager.commit();
return result;
} catch (Exception e) {
logger.error("Failed to commit transaction!", e);
try {
transactionManager.rollback();
}
catch(Exception ex) {
logger.warn("Cannot roll back transaction!", ex);
}
throw e;
}
}
}
Finally, the code to put all together so that Guice can inject the instances:
bind(DataSource.class).toProvider(DataSourceProvider.class).in(Scopes.SINGLETON);
bind(TransactionManager.class).to(JdbcTransactionManager.class);
TransactionalMethodInterceptor transactionalMethodInterceptor = new TransactionalMethodInterceptor();
requestInjection(transactionalMethodInterceptor);
bindInterceptor(Matchers.any(), Matchers.annotatedWith(Transactional.class), transactionalMethodInterceptor);
bind(TestDao.class).to(JdbcTestDao.class);
bind(TestService.class).to(TestServiceImpl.class);
I use c3p0 for the datasource pool. so, it works just fine in my test.
I find another related question: Guice, JDBC and managing database connections
but so far I haven't find any similar approach, except something in SpringFramework. but even the implementation in Spring seems quite complex.
I would like to ask if anyone has any suggestion for this solution.
thanks.

Resources