diff --git a/build.gradle b/build.gradle index 442ac31e3e..12d11ba208 100644 --- a/build.gradle +++ b/build.gradle @@ -403,7 +403,7 @@ project('spring-integration-jdbc') { dependencies { compile project(":spring-integration-core") compile "org.springframework:spring-jdbc:$springVersion" - compile "com.google.guava:guava:$guavaVersion" + compile ("com.google.guava:guava:$guavaVersion", optional) testCompile "com.h2database:h2:$h2Version" testCompile "org.hsqldb:hsqldb:$hsqldbVersion" diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/StoredProcExecutor.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/StoredProcExecutor.java index 70c97d21e0..825d7bceb6 100644 --- a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/StoredProcExecutor.java +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/StoredProcExecutor.java @@ -12,14 +12,15 @@ */ package org.springframework.integration.jdbc; + import java.sql.ResultSet; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; - import javax.sql.DataSource; import org.springframework.beans.factory.BeanFactory; @@ -47,6 +48,7 @@ import org.springframework.jmx.export.annotation.ManagedResource; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; @@ -69,6 +71,9 @@ import com.google.common.cache.LoadingCache; @IntegrationManagedResource public class StoredProcExecutor implements BeanFactoryAware, InitializingBean { + private static final boolean guavaPresent = ClassUtils.isPresent("com.google.common.cache.LoadingCache", + StoredProcExecutor.class.getClassLoader()); + private volatile EvaluationContext evaluationContext; private volatile BeanFactory beanFactory = null; @@ -76,9 +81,13 @@ public class StoredProcExecutor implements BeanFactoryAware, InitializingBean { private volatile int jdbcCallOperationsCacheSize = 10; /** - * Uses the {@link SimpleJdbcCall} implementation for executing Stored Procedures. + * For {@code optional} Google Guava library in the CLASSPATH */ - private volatile LoadingCache jdbcCallOperationsCache = null; + private volatile GuavaCacheWrapper guavaCacheWrapper; + + private final Object jdbcCallOperationsMapMonitor = new Object(); + + private volatile Map jdbcCallOperationsMap; private volatile Expression storedProcedureNameExpression; @@ -134,7 +143,9 @@ public class StoredProcExecutor implements BeanFactoryAware, InitializingBean { private volatile ListprocedureParameters; private volatile boolean isFunction = false; + private volatile boolean returnValueRequired = false; + private volatile Map> returningResultSetRowMappers = new HashMap>(0); private final DataSource dataSource; @@ -152,7 +163,6 @@ public class StoredProcExecutor implements BeanFactoryAware, InitializingBean { Assert.notNull(dataSource, "dataSource must not be null."); this.dataSource = dataSource; - } /** @@ -210,24 +220,25 @@ public class StoredProcExecutor implements BeanFactoryAware, InitializingBean { } - jdbcCallOperationsCache = CacheBuilder.newBuilder() - .maximumSize(jdbcCallOperationsCacheSize) - .recordStats() - .build(new CacheLoader() { - @Override - public SimpleJdbcCall load(String storedProcedureName) { - return createSimpleJdbcCall(storedProcedureName); - } - }); + if (guavaPresent) { + this.guavaCacheWrapper = new GuavaCacheWrapper(this, this.jdbcCallOperationsCacheSize); + } + else { + this.jdbcCallOperationsMap = + new LinkedHashMap(this.jdbcCallOperationsCacheSize + 1, 0.75f, + true) { - if (this.storedProcedureNameExpression instanceof LiteralExpression) { - String storedProcedureName = this.storedProcedureNameExpression.getValue(String.class); - final SimpleJdbcCall simpleJdbcCall = createSimpleJdbcCall(storedProcedureName); - this.jdbcCallOperationsCache.put(storedProcedureName, simpleJdbcCall); + private static final long serialVersionUID = 3801124242820219131L; + + @Override + protected boolean removeEldestEntry(Entry eldest) { + return size() > jdbcCallOperationsCacheSize; + } + + }; } - this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(beanFactory); - + this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.beanFactory); } private SimpleJdbcCall createSimpleJdbcCall(String storedProcedureName) { @@ -314,7 +325,7 @@ public class StoredProcExecutor implements BeanFactoryAware, InitializingBean { * Execute the Stored Procedure using the passed in {@link Message} as a source * for parameters. * - * @param message The message is used to extract parameters for the stored procedure. + * @param input The message is used to extract parameters for the stored procedure. * @return A map containing the return values from the Stored Procedure call if any. */ private Map executeStoredProcedureInternal(Object input, String storedProcedureName) { @@ -322,15 +333,34 @@ public class StoredProcExecutor implements BeanFactoryAware, InitializingBean { Assert.notNull(sqlParameterSourceFactory, "Property sqlParameterSourceFactory " + "was Null. Did you call afterPropertiesSet()?"); - SimpleJdbcCallOperations localSimpleJdbcCall = this.jdbcCallOperationsCache.getUnchecked(storedProcedureName); + SimpleJdbcCallOperations localSimpleJdbcCall = obtainSimpleJdbcCall(storedProcedureName); SqlParameterSource storedProcedureParameterSource = - sqlParameterSourceFactory.createParameterSource(input); + sqlParameterSourceFactory.createParameterSource(input); return localSimpleJdbcCall.execute(storedProcedureParameterSource); } + private SimpleJdbcCallOperations obtainSimpleJdbcCall(String storedProcedureName) { + if (guavaPresent) { + return this.guavaCacheWrapper.jdbcCallOperationsCache.getUnchecked(storedProcedureName); + } + else { + SimpleJdbcCallOperations operations = this.jdbcCallOperationsMap.get(storedProcedureName); + if (operations == null) { + synchronized (this.jdbcCallOperationsMapMonitor) { + operations = this.jdbcCallOperationsMap.get(storedProcedureName); + if (operations == null) { + operations = createSimpleJdbcCall(storedProcedureName); + this.jdbcCallOperationsMap.put(storedProcedureName, operations); + } + } + } + return operations; + } + } + //~~~~~Setters for Properties~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /** @@ -548,19 +578,25 @@ public class StoredProcExecutor implements BeanFactoryAware, InitializingBean { } /** - * Allows for the retrieval of metrics ({@link CacheStats}}) for the - * {@link StoredProcExecutor#jdbcCallOperationsCache}, which is used to store + * Allows for the retrieval of metrics ({@link CacheStats}) for the + * {@link GuavaCacheWrapper#jdbcCallOperationsCache}, which is used to store * instances of {@link SimpleJdbcCallOperations}. * - * @return Cache statistics for {@link StoredProcExecutor#jdbcCallOperationsCache} + * @return {@link CacheStats} object for {@link GuavaCacheWrapper#jdbcCallOperationsCache}. + * Since Google Guava is an optional dependency for Spring Integration this method can't + * return Guava {@link CacheStats} type directly because of some reflection manipulation + * by the Spring bean definition phase. */ - public CacheStats getJdbcCallOperationsCacheStatistics() { - return this.jdbcCallOperationsCache.stats(); + public Object getJdbcCallOperationsCacheStatistics() { + if (!guavaPresent) { + throw new UnsupportedOperationException("The Google Guava library isn't present in the classpath."); + } + return this.guavaCacheWrapper.jdbcCallOperationsCache.stats(); } /** - * Allows for the retrieval of metrics ({@link CacheStats}}) for the - * {@link StoredProcExecutor#jdbcCallOperationsCache}. + * Allows for the retrieval of metrics ({@link CacheStats}) for the + * {@link GuavaCacheWrapper#jdbcCallOperationsCache}. * * Provides the properties of {@link CacheStats} as a {@link Map}. This allows * for exposing the those properties easily via JMX. @@ -571,7 +607,10 @@ public class StoredProcExecutor implements BeanFactoryAware, InitializingBean { */ @ManagedMetric public Map getJdbcCallOperationsCacheStatisticsAsMap() { - final CacheStats cacheStats = this.getJdbcCallOperationsCacheStatistics(); + if (!guavaPresent) { + throw new UnsupportedOperationException("The Google Guava library isn't present in the classpath."); + } + final CacheStats cacheStats = (CacheStats) getJdbcCallOperationsCacheStatistics(); final Map cacheStatistics = new HashMap(11); cacheStatistics.put("averageLoadPenalty", cacheStats.averageLoadPenalty()); cacheStatistics.put("evictionCount", cacheStats.evictionCount()); @@ -590,7 +629,7 @@ public class StoredProcExecutor implements BeanFactoryAware, InitializingBean { /** * Defines the maximum number of {@link SimpleJdbcCallOperations} * ({@link SimpleJdbcCall}) instances to be held by - * {@link StoredProcExecutor#jdbcCallOperationsCache}. + * {@link GuavaCacheWrapper#jdbcCallOperationsCache}. * * A value of zero will disable the cache. The default is 10. * @@ -611,8 +650,35 @@ public class StoredProcExecutor implements BeanFactoryAware, InitializingBean { */ @Override public void setBeanFactory(BeanFactory beanFactory) { - Assert.notNull(returningResultSetRowMappers, "returningResultSetRowMappers must not be null."); this.beanFactory = beanFactory; } + /** + * The lazy-load workaround class to avoid {@link NoClassDefFoundError} + * for {@link CacheLoader} class, when Google Guava isn't present in the CLASSPATH. + * + * @since 4.2 + */ + private static class GuavaCacheWrapper { + + private final LoadingCache jdbcCallOperationsCache; + + private GuavaCacheWrapper(final StoredProcExecutor executor, int size) { + this.jdbcCallOperationsCache = CacheBuilder.newBuilder() + .maximumSize(size) + .recordStats() + .build(new CacheLoader() { + + @Override + public SimpleJdbcCallOperations load(String key) throws Exception { + return executor.createSimpleJdbcCall(key); + } + + }); + } + + } + } + + diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/StoredProcJmxManagedBeanTests.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/StoredProcJmxManagedBeanTests.java index ed7f092afe..436dcd1a4e 100644 --- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/StoredProcJmxManagedBeanTests.java +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/StoredProcJmxManagedBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,8 @@ package org.springframework.integration.jdbc; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import java.util.ArrayList; import java.util.Collection; @@ -71,32 +72,38 @@ public class StoredProcJmxManagedBeanTests { // MessageHandler - final Set messageHandlerObjectNames = server.queryNames(ObjectName.getInstance("org.springframework.integration.jdbc.test:name=outboundChannelAdapter.adapter.storedProcExecutor,*"), null); + final Set messageHandlerObjectNames = server.queryNames( + ObjectName.getInstance("org.springframework.integration.jdbc.test:name=outboundChannelAdapter.adapter.storedProcExecutor,*"), + null); assertEquals(1, messageHandlerObjectNames.size()); ObjectName messageHandlerObjectName = messageHandlerObjectNames.iterator().next(); - Map messageHandlerCacheStatistics = (Map) server.getAttribute(messageHandlerObjectName, "JdbcCallOperationsCacheStatisticsAsMap"); + Map messageHandlerCacheStatistics = + (Map) server.getAttribute(messageHandlerObjectName, "JdbcCallOperationsCacheStatisticsAsMap"); assertEquals(11, messageHandlerCacheStatistics.size()); - assertEquals(Long.valueOf(0), messageHandlerCacheStatistics.get("hitCount")); - assertEquals(Long.valueOf(0), messageHandlerCacheStatistics.get("loadCount")); - assertEquals(Long.valueOf(0), messageHandlerCacheStatistics.get("loadExceptionCount")); - assertEquals(Long.valueOf(0), messageHandlerCacheStatistics.get("loadSuccessCount")); - assertEquals(Long.valueOf(0), messageHandlerCacheStatistics.get("missCount")); + assertEquals(0L, messageHandlerCacheStatistics.get("hitCount")); + assertEquals(0L, messageHandlerCacheStatistics.get("loadCount")); + assertEquals(0L, messageHandlerCacheStatistics.get("loadExceptionCount")); + assertEquals(0L, messageHandlerCacheStatistics.get("loadSuccessCount")); + assertEquals(0L, messageHandlerCacheStatistics.get("missCount")); // StoredProcOutboundGateway - final Set storedProcOutboundGatewayObjectNames = server.queryNames(ObjectName.getInstance("org.springframework.integration.jdbc.test:name=my gateway.storedProcExecutor,*"), null); + final Set storedProcOutboundGatewayObjectNames = server.queryNames( + ObjectName.getInstance("org.springframework.integration.jdbc.test:name=my gateway.storedProcExecutor,*"), + null); assertEquals(1, storedProcOutboundGatewayObjectNames.size()); ObjectName storedProcOutboundGatewayObjectName = storedProcOutboundGatewayObjectNames.iterator().next(); - Map storedProcOutboundGatewayCacheStatistics = (Map) server.getAttribute(storedProcOutboundGatewayObjectName, "JdbcCallOperationsCacheStatisticsAsMap"); + Map storedProcOutboundGatewayCacheStatistics = + (Map) server.getAttribute(storedProcOutboundGatewayObjectName, "JdbcCallOperationsCacheStatisticsAsMap"); assertEquals(11, messageHandlerCacheStatistics.size()); - assertEquals(Long.valueOf(0), storedProcOutboundGatewayCacheStatistics.get("hitCount")); - assertEquals(Long.valueOf(0), storedProcOutboundGatewayCacheStatistics.get("loadCount")); - assertEquals(Long.valueOf(0), storedProcOutboundGatewayCacheStatistics.get("loadExceptionCount")); - assertEquals(Long.valueOf(0), storedProcOutboundGatewayCacheStatistics.get("loadSuccessCount")); - assertEquals(Long.valueOf(0), storedProcOutboundGatewayCacheStatistics.get("missCount")); + assertEquals(0L, storedProcOutboundGatewayCacheStatistics.get("hitCount")); + assertEquals(0L, storedProcOutboundGatewayCacheStatistics.get("loadCount")); + assertEquals(0L, storedProcOutboundGatewayCacheStatistics.get("loadExceptionCount")); + assertEquals(0L, storedProcOutboundGatewayCacheStatistics.get("loadSuccessCount")); + assertEquals(0L, storedProcOutboundGatewayCacheStatistics.get("missCount")); // StoredProcPollingChannelAdapter @@ -107,11 +114,11 @@ public class StoredProcJmxManagedBeanTests { assertEquals(11, storedProcPollingChannelAdapterCacheStatistics.size()); - assertEquals(Long.valueOf(0), storedProcPollingChannelAdapterCacheStatistics.get("hitCount")); - assertEquals(Long.valueOf(0), storedProcPollingChannelAdapterCacheStatistics.get("loadCount")); - assertEquals(Long.valueOf(0), storedProcPollingChannelAdapterCacheStatistics.get("loadExceptionCount")); - assertEquals(Long.valueOf(0), storedProcPollingChannelAdapterCacheStatistics.get("loadSuccessCount")); - assertEquals(Long.valueOf(0), storedProcPollingChannelAdapterCacheStatistics.get("missCount")); + assertEquals(0L, storedProcPollingChannelAdapterCacheStatistics.get("hitCount")); + assertEquals(0L, storedProcPollingChannelAdapterCacheStatistics.get("loadCount")); + assertEquals(0L, storedProcPollingChannelAdapterCacheStatistics.get("loadExceptionCount")); + assertEquals(0L, storedProcPollingChannelAdapterCacheStatistics.get("loadSuccessCount")); + assertEquals(0L, storedProcPollingChannelAdapterCacheStatistics.get("missCount")); } @@ -124,18 +131,21 @@ public class StoredProcJmxManagedBeanTests { final MBeanServer server = servers.iterator().next(); - final Set objectNames = server.queryNames(ObjectName.getInstance("org.springframework.integration.jdbc.test:name=my gateway.storedProcExecutor,*"), null); + final Set objectNames = server.queryNames( + ObjectName.getInstance("org.springframework.integration.jdbc.test:name=my gateway.storedProcExecutor,*"), + null); assertEquals(1, objectNames.size()); ObjectName name = objectNames.iterator().next(); - Map cacheStatistics = (Map) server.getAttribute(name, "JdbcCallOperationsCacheStatisticsAsMap"); + Map cacheStatistics = + (Map) server.getAttribute(name, "JdbcCallOperationsCacheStatisticsAsMap"); assertEquals(11, cacheStatistics.size()); - assertEquals(Long.valueOf(0), cacheStatistics.get("hitCount")); - assertEquals(Long.valueOf(0), cacheStatistics.get("loadCount")); - assertEquals(Long.valueOf(0), cacheStatistics.get("loadExceptionCount")); - assertEquals(Long.valueOf(0), cacheStatistics.get("loadSuccessCount")); - assertEquals(Long.valueOf(0), cacheStatistics.get("missCount")); + assertEquals(0L, cacheStatistics.get("hitCount")); + assertEquals(0L, cacheStatistics.get("loadCount")); + assertEquals(0L, cacheStatistics.get("loadExceptionCount")); + assertEquals(0L, cacheStatistics.get("loadSuccessCount")); + assertEquals(0L, cacheStatistics.get("missCount")); userService.createUser(new User("myUsername", "myPassword", "myEmail")); @@ -147,17 +157,17 @@ public class StoredProcJmxManagedBeanTests { assertNotNull(message); assertNotNull(message.getPayload()); - assertNotNull(message.getPayload() instanceof Collection); - Map cacheStatistics2 = (Map) server.getAttribute(name, "JdbcCallOperationsCacheStatisticsAsMap"); + Map cacheStatistics2 = + (Map) server.getAttribute(name, "JdbcCallOperationsCacheStatisticsAsMap"); assertEquals(11, cacheStatistics2.size()); - assertEquals(Long.valueOf(1), cacheStatistics2.get("hitCount")); - assertEquals(Long.valueOf(0), cacheStatistics2.get("loadCount")); - assertEquals(Long.valueOf(0), cacheStatistics2.get("loadExceptionCount")); - assertEquals(Long.valueOf(0), cacheStatistics2.get("loadSuccessCount")); - assertEquals(Long.valueOf(0), cacheStatistics2.get("missCount")); + assertEquals(0L, cacheStatistics2.get("hitCount")); + assertEquals(1L, cacheStatistics2.get("loadCount")); + assertEquals(0L, cacheStatistics2.get("loadExceptionCount")); + assertEquals(1L, cacheStatistics2.get("loadSuccessCount")); + assertEquals(1L, cacheStatistics2.get("missCount")); } @@ -170,8 +180,9 @@ public class StoredProcJmxManagedBeanTests { //prevent message overload return null; } - return Integer.valueOf(count.incrementAndGet()); + return count.incrementAndGet(); } + } @@ -187,5 +198,7 @@ public class StoredProcJmxManagedBeanTests { Message> poll(long timeoutInMillis) throws InterruptedException { return messages.poll(timeoutInMillis, TimeUnit.MILLISECONDS); } + } + }