From 4143c0de268af47ffc3e6914f0f6c3ffb12de335 Mon Sep 17 00:00:00 2001
From: rbygrave
Date: Sat, 9 May 2015 01:09:32 +1200
Subject: [PATCH] No effective change - change newline char
---
.../server/lib/DaemonScheduleThreadPool.java | 158 +-
.../server/lib/DaemonThreadFactory.java | 96 +-
.../server/lib/DaemonThreadPool.java | 172 +-
.../server/lib/ShutdownHook.java | 44 +-
.../server/lib/ShutdownManager.java | 374 +--
.../server/lib/resource/DirectoryFinder.java | 216 +-
.../lib/resource/FileResourceContent.java | 142 +-
.../lib/resource/FileResourceSource.java | 102 +-
.../server/lib/resource/ResourceContent.java | 68 +-
.../server/lib/resource/ResourceSource.java | 74 +-
.../lib/resource/UrlResourceContent.java | 154 +-
.../lib/resource/UrlResourceSource.java | 128 +-
.../server/lib/sql/BusyConnectionBuffer.java | 404 ++--
.../server/lib/sql/DataSourceException.java | 48 +-
.../server/lib/sql/DataSourcePool.java | 2034 ++++++++---------
.../lib/sql/ExtendedPreparedStatement.java | 778 +++----
.../server/lib/sql/ExtendedStatement.java | 656 +++---
.../server/lib/sql/FreeConnectionBuffer.java | 212 +-
.../server/lib/sql/PooledConnection.java | 2024 ++++++++--------
.../server/lib/sql/PooledConnectionQueue.java | 1064 ++++-----
.../ebeaninternal/server/lib/sql/Prefix.java | 200 +-
.../server/lib/sql/PstmtCache.java | 366 +--
.../server/lib/sql/SimpleDataSourceAlert.java | 212 +-
.../server/lib/sql/TransactionIsolation.java | 138 +-
.../lib/util/CreateObjectException.java | 46 +-
.../ebeaninternal/server/lib/util/Dnode.java | 648 +++---
.../server/lib/util/DnodeParser.java | 400 ++--
.../server/lib/util/DnodeReader.java | 140 +-
.../server/lib/util/GeneralException.java | 44 +-
.../server/lib/util/InvalidDataException.java | 46 +-
.../server/lib/util/MailAddress.java | 90 +-
.../server/lib/util/MailEvent.java | 98 +-
.../server/lib/util/MailListener.java | 26 +-
.../server/lib/util/MailMessage.java | 314 +--
.../server/lib/util/MailSender.java | 412 ++--
.../server/lib/util/MapFromString.java | 126 +-
.../server/lib/util/MimeTypeHelper.java | 80 +-
.../server/lib/util/NotFoundException.java | 46 +-
.../server/lib/util/StringHelper.java | 1198 +++++-----
.../lib/util/StringParsingException.java | 30 +-
.../server/lib/util/ThrowablePrinter.java | 182 +-
41 files changed, 6895 insertions(+), 6895 deletions(-)
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/DaemonScheduleThreadPool.java b/src/main/java/com/avaje/ebeaninternal/server/lib/DaemonScheduleThreadPool.java
index 63747071a..d247f8ef4 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/DaemonScheduleThreadPool.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/DaemonScheduleThreadPool.java
@@ -1,79 +1,79 @@
-package com.avaje.ebeaninternal.server.lib;
-
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Daemon based ScheduleThreadPool.
- *
- * Uses Daemon threads and hooks into shutdown event.
- *
- *
- * @author rbygrave
- */
-public final class DaemonScheduleThreadPool extends ScheduledThreadPoolExecutor {
-
- private static final Logger logger = LoggerFactory.getLogger(DaemonScheduleThreadPool.class);
-
- private final String namePrefix;
-
- private int shutdownWaitSeconds;
-
- /**
- * Construct the DaemonScheduleThreadPool.
- */
- public DaemonScheduleThreadPool(int coreSize, int shutdownWaitSeconds, String namePrefix) {
-
- super(coreSize, new DaemonThreadFactory(namePrefix));
- this.namePrefix = namePrefix;
- this.shutdownWaitSeconds = shutdownWaitSeconds;
- }
-
- /**
- * Register a shutdown hook with the JVM Runtime.
- */
- public void registerShutdownHook() {
- Runtime.getRuntime().addShutdownHook(new ShutdownHook());
- }
-
- /**
- * Shutdown this thread pool nicely if possible.
- *
- * This will wait a maximum of 20 seconds before terminating any threads still
- * working.
- *
- */
- public void shutdown() {
- synchronized (this) {
- if (super.isShutdown()) {
- logger.debug("DaemonScheduleThreadPool {} already shut down", namePrefix);
- return;
- }
- try {
- logger.debug("DaemonScheduleThreadPool {} shutting down...", namePrefix);
- super.shutdown();
- if (!super.awaitTermination(shutdownWaitSeconds, TimeUnit.SECONDS)) {
- logger.info("DaemonScheduleThreadPool shut down timeout exceeded. Terminating running threads.");
- super.shutdownNow();
- }
-
- } catch (Exception e) {
- logger.error("Error during shutdown of " + namePrefix, e);
- e.printStackTrace();
- }
- }
- }
-
- /**
- * Fired by the JVM Runtime shutdown.
- */
- private class ShutdownHook extends Thread {
- @Override
- public void run() {
- shutdown();
- }
- };
-}
+package com.avaje.ebeaninternal.server.lib;
+
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Daemon based ScheduleThreadPool.
+ *
+ * Uses Daemon threads and hooks into shutdown event.
+ *
+ *
+ * @author rbygrave
+ */
+public final class DaemonScheduleThreadPool extends ScheduledThreadPoolExecutor {
+
+ private static final Logger logger = LoggerFactory.getLogger(DaemonScheduleThreadPool.class);
+
+ private final String namePrefix;
+
+ private int shutdownWaitSeconds;
+
+ /**
+ * Construct the DaemonScheduleThreadPool.
+ */
+ public DaemonScheduleThreadPool(int coreSize, int shutdownWaitSeconds, String namePrefix) {
+
+ super(coreSize, new DaemonThreadFactory(namePrefix));
+ this.namePrefix = namePrefix;
+ this.shutdownWaitSeconds = shutdownWaitSeconds;
+ }
+
+ /**
+ * Register a shutdown hook with the JVM Runtime.
+ */
+ public void registerShutdownHook() {
+ Runtime.getRuntime().addShutdownHook(new ShutdownHook());
+ }
+
+ /**
+ * Shutdown this thread pool nicely if possible.
+ *
+ * This will wait a maximum of 20 seconds before terminating any threads still
+ * working.
+ *
+ */
+ public void shutdown() {
+ synchronized (this) {
+ if (super.isShutdown()) {
+ logger.debug("DaemonScheduleThreadPool {} already shut down", namePrefix);
+ return;
+ }
+ try {
+ logger.debug("DaemonScheduleThreadPool {} shutting down...", namePrefix);
+ super.shutdown();
+ if (!super.awaitTermination(shutdownWaitSeconds, TimeUnit.SECONDS)) {
+ logger.info("DaemonScheduleThreadPool shut down timeout exceeded. Terminating running threads.");
+ super.shutdownNow();
+ }
+
+ } catch (Exception e) {
+ logger.error("Error during shutdown of " + namePrefix, e);
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Fired by the JVM Runtime shutdown.
+ */
+ private class ShutdownHook extends Thread {
+ @Override
+ public void run() {
+ shutdown();
+ }
+ };
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/DaemonThreadFactory.java b/src/main/java/com/avaje/ebeaninternal/server/lib/DaemonThreadFactory.java
index d96ce19bc..70ca67fe5 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/DaemonThreadFactory.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/DaemonThreadFactory.java
@@ -1,49 +1,49 @@
-package com.avaje.ebeaninternal.server.lib;
-
-
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * ThreadFactory for Daemon threads.
- *
- * Daemon threads do not stop a JVM stopping. If an application only has Daemon
- * threads left it will shutdown.
- *
- *
- * In using Daemon threads you need to either not care about being interrupted
- * on shutdown or register with the JVM shutdown hook to perform a nice shutdown
- * of the daemon threads etc.
- *
- *
- * @author rbygrave
- */
-public class DaemonThreadFactory implements ThreadFactory {
-
- private static final AtomicInteger poolNumber = new AtomicInteger(1);
-
- private final ThreadGroup group;
-
- private final AtomicInteger threadNumber = new AtomicInteger(1);
-
- private final String namePrefix;
-
- public DaemonThreadFactory(String namePrefix) {
- SecurityManager s = System.getSecurityManager();
- this.group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
- this.namePrefix = namePrefix != null ? namePrefix : "pool-" + poolNumber.getAndIncrement() + "-thread-";
- }
-
- public Thread newThread(Runnable r) {
-
- Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
-
- t.setDaemon(true);
-
- if (t.getPriority() != Thread.NORM_PRIORITY) {
- t.setPriority(Thread.NORM_PRIORITY);
- }
-
- return t;
- }
+package com.avaje.ebeaninternal.server.lib;
+
+
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * ThreadFactory for Daemon threads.
+ *
+ * Daemon threads do not stop a JVM stopping. If an application only has Daemon
+ * threads left it will shutdown.
+ *
+ *
+ * In using Daemon threads you need to either not care about being interrupted
+ * on shutdown or register with the JVM shutdown hook to perform a nice shutdown
+ * of the daemon threads etc.
+ *
+ *
+ * @author rbygrave
+ */
+public class DaemonThreadFactory implements ThreadFactory {
+
+ private static final AtomicInteger poolNumber = new AtomicInteger(1);
+
+ private final ThreadGroup group;
+
+ private final AtomicInteger threadNumber = new AtomicInteger(1);
+
+ private final String namePrefix;
+
+ public DaemonThreadFactory(String namePrefix) {
+ SecurityManager s = System.getSecurityManager();
+ this.group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
+ this.namePrefix = namePrefix != null ? namePrefix : "pool-" + poolNumber.getAndIncrement() + "-thread-";
+ }
+
+ public Thread newThread(Runnable r) {
+
+ Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
+
+ t.setDaemon(true);
+
+ if (t.getPriority() != Thread.NORM_PRIORITY) {
+ t.setPriority(Thread.NORM_PRIORITY);
+ }
+
+ return t;
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/DaemonThreadPool.java b/src/main/java/com/avaje/ebeaninternal/server/lib/DaemonThreadPool.java
index 75174344d..7ce19ae24 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/DaemonThreadPool.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/DaemonThreadPool.java
@@ -1,86 +1,86 @@
-package com.avaje.ebeaninternal.server.lib;
-
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The Thread Pool based on Daemon threads.
- *
- * @author rbygrave
- */
-public final class DaemonThreadPool extends ThreadPoolExecutor {
-
- private static final Logger logger = LoggerFactory.getLogger(DaemonThreadPool.class);
-
- private final String namePrefix;
-
- private final int shutdownWaitSeconds;
-
- /**
- * Construct the DaemonThreadPool.
- *
- * @param coreSize
- * the core size of the thread pool.
- * @param keepAliveSecs
- * the time in seconds idle threads are keep alive
- * @param shutdownWaitSeconds
- * the time in seconds allowed for the pool to shutdown nicely. After
- * this the pool is forced to shutdown.
- */
- public DaemonThreadPool(int coreSize, int maximumPoolSize, long keepAliveSecs, int shutdownWaitSeconds, String namePrefix) {
-
- super(coreSize, maximumPoolSize, keepAliveSecs, TimeUnit.SECONDS, new LinkedBlockingQueue(), new DaemonThreadFactory(namePrefix));
- allowCoreThreadTimeOut(true);
- this.shutdownWaitSeconds = shutdownWaitSeconds;
- this.namePrefix = namePrefix;
- }
-
- /**
- * Register a shutdown hook with the JVM Runtime.
- */
- public void registerShutdownHook() {
- Runtime.getRuntime().addShutdownHook(new ShutdownHook());
- }
-
- /**
- * Shutdown this thread pool nicely if possible.
- *
- * This will wait a maximum of 20 seconds before terminating any threads still
- * working.
- *
- */
- public void shutdown() {
- synchronized (this) {
- if (super.isShutdown()) {
- logger.debug("DaemonThreadPool[" + namePrefix + "] already shut down");
- return;
- }
- try {
- logger.debug("DaemonThreadPool[" + namePrefix + "] shutting down...");
- super.shutdown();
- if (!super.awaitTermination(shutdownWaitSeconds, TimeUnit.SECONDS)) {
- logger.info("DaemonThreadPool[" + namePrefix+ "] shut down timeout exceeded. Terminating running threads.");
- super.shutdownNow();
- }
-
- } catch (Exception e) {
- logger.error("Error during shutdown of DaemonThreadPool[" + namePrefix + "]", e);
- e.printStackTrace();
- }
- }
- }
-
- /**
- * Fired by the JVM Runtime shutdown.
- */
- private class ShutdownHook extends Thread {
- @Override
- public void run() {
- shutdown();
- }
- };
-}
+package com.avaje.ebeaninternal.server.lib;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The Thread Pool based on Daemon threads.
+ *
+ * @author rbygrave
+ */
+public final class DaemonThreadPool extends ThreadPoolExecutor {
+
+ private static final Logger logger = LoggerFactory.getLogger(DaemonThreadPool.class);
+
+ private final String namePrefix;
+
+ private final int shutdownWaitSeconds;
+
+ /**
+ * Construct the DaemonThreadPool.
+ *
+ * @param coreSize
+ * the core size of the thread pool.
+ * @param keepAliveSecs
+ * the time in seconds idle threads are keep alive
+ * @param shutdownWaitSeconds
+ * the time in seconds allowed for the pool to shutdown nicely. After
+ * this the pool is forced to shutdown.
+ */
+ public DaemonThreadPool(int coreSize, int maximumPoolSize, long keepAliveSecs, int shutdownWaitSeconds, String namePrefix) {
+
+ super(coreSize, maximumPoolSize, keepAliveSecs, TimeUnit.SECONDS, new LinkedBlockingQueue(), new DaemonThreadFactory(namePrefix));
+ allowCoreThreadTimeOut(true);
+ this.shutdownWaitSeconds = shutdownWaitSeconds;
+ this.namePrefix = namePrefix;
+ }
+
+ /**
+ * Register a shutdown hook with the JVM Runtime.
+ */
+ public void registerShutdownHook() {
+ Runtime.getRuntime().addShutdownHook(new ShutdownHook());
+ }
+
+ /**
+ * Shutdown this thread pool nicely if possible.
+ *
+ * This will wait a maximum of 20 seconds before terminating any threads still
+ * working.
+ *
+ */
+ public void shutdown() {
+ synchronized (this) {
+ if (super.isShutdown()) {
+ logger.debug("DaemonThreadPool[" + namePrefix + "] already shut down");
+ return;
+ }
+ try {
+ logger.debug("DaemonThreadPool[" + namePrefix + "] shutting down...");
+ super.shutdown();
+ if (!super.awaitTermination(shutdownWaitSeconds, TimeUnit.SECONDS)) {
+ logger.info("DaemonThreadPool[" + namePrefix+ "] shut down timeout exceeded. Terminating running threads.");
+ super.shutdownNow();
+ }
+
+ } catch (Exception e) {
+ logger.error("Error during shutdown of DaemonThreadPool[" + namePrefix + "]", e);
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Fired by the JVM Runtime shutdown.
+ */
+ private class ShutdownHook extends Thread {
+ @Override
+ public void run() {
+ shutdown();
+ }
+ };
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/ShutdownHook.java b/src/main/java/com/avaje/ebeaninternal/server/lib/ShutdownHook.java
index 7961434e3..772b25d34 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/ShutdownHook.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/ShutdownHook.java
@@ -1,22 +1,22 @@
-package com.avaje.ebeaninternal.server.lib;
-
-
-
-/**
- * This is the ShutdownHook that gets added to Runtime.
- * It will try to shutdown the system cleanly when the JVM exits.
- * It is best to add your own shutdown hooks to StartStop.
- */
-class ShutdownHook extends Thread {
-
- ShutdownHook() {
- }
-
- /**
- * Fired by the JVM Runtime on shutdown.
- */
- public void run() {
- ShutdownManager.shutdown();
- }
-
-};
+package com.avaje.ebeaninternal.server.lib;
+
+
+
+/**
+ * This is the ShutdownHook that gets added to Runtime.
+ * It will try to shutdown the system cleanly when the JVM exits.
+ * It is best to add your own shutdown hooks to StartStop.
+ */
+class ShutdownHook extends Thread {
+
+ ShutdownHook() {
+ }
+
+ /**
+ * Fired by the JVM Runtime on shutdown.
+ */
+ public void run() {
+ ShutdownManager.shutdown();
+ }
+
+};
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/ShutdownManager.java b/src/main/java/com/avaje/ebeaninternal/server/lib/ShutdownManager.java
index 718af5d6a..70bd654ae 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/ShutdownManager.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/ShutdownManager.java
@@ -1,187 +1,187 @@
-package com.avaje.ebeaninternal.server.lib;
-
-import com.avaje.ebean.common.SpiContainer;
-import com.avaje.ebeaninternal.api.ClassUtil;
-import com.avaje.ebeaninternal.api.SpiEbeanServer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.sql.Driver;
-import java.sql.DriverManager;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.List;
-
-/**
- * Manages the shutdown of the JVM Runtime.
- *
- * Makes sure all the resources are shutdown properly and in order.
- *
- */
-public final class ShutdownManager {
-
- private static final Logger logger = LoggerFactory.getLogger(ShutdownManager.class);
-
- static final List servers = new ArrayList();
-
- static final ShutdownHook shutdownHook = new ShutdownHook();
-
- static boolean stopping;
-
- static SpiContainer container;
-
- static {
- // Register the Shutdown hook
- registerShutdownHook();
- }
-
- /**
- * Disallow construction.
- */
- private ShutdownManager() {
- }
-
- public static void registerContainer(SpiContainer ebeanContainer){
- container = ebeanContainer;
- }
-
- /**
- * Make sure the ShutdownManager is activated.
- */
- public static void touch() {
- // Do nothing
- }
-
- /**
- * Return true if the system is in the process of stopping.
- */
- public static boolean isStopping() {
- synchronized (servers) {
- return stopping;
- }
- }
-
- /**
- * Deregister the Shutdown hook.
- *
- * For running in a Servlet Container a redeploy will cause a shutdown, and
- * for that case we need to make sure the shutdown hook is deregistered.
- *
- */
- protected static void deregisterShutdownHook() {
- synchronized (servers) {
- try {
- Runtime.getRuntime().removeShutdownHook(shutdownHook);
- } catch (IllegalStateException ex) {
- if (!ex.getMessage().equals("Shutdown in progress")) {
- throw ex;
- }
- }
- }
- }
-
- /**
- * Register the shutdown hook with the Runtime.
- */
- protected static void registerShutdownHook() {
- synchronized (servers) {
- try {
- Runtime.getRuntime().addShutdownHook(shutdownHook);
- } catch (IllegalStateException ex) {
- if (!ex.getMessage().equals("Shutdown in progress")) {
- throw ex;
- }
- }
- }
- }
-
- /**
- * Shutdown gracefully cleaning up any resources as required.
- *
- * This is typically invoked via JVM shutdown hook.
- *
- */
- public static void shutdown() {
- synchronized (servers) {
- if (stopping) {
- // Already run shutdown...
- return;
- }
-
- if (logger.isDebugEnabled()) {
- logger.debug("Shutting down");
- }
-
- stopping = true;
-
- deregisterShutdownHook();
-
- String shutdownRunner = System.getProperty("ebean.shutdown.runnable");
- if (shutdownRunner != null) {
- try {
- // A custom runnable executed at the start of shutdown
- Runnable r = (Runnable) ClassUtil.newInstance(shutdownRunner);
- r.run();
- } catch (Exception e) {
- logger.error("Error running custom shutdown runnable", e);
- }
- }
-
- if (container != null) {
- // shutdown cluster networking if active
- container.shutdown();
- }
-
- // shutdown any registered servers that have not
- // already been shutdown manually
- for (SpiEbeanServer server : servers) {
- try {
- server.shutdownManaged();
- } catch (Exception ex) {
- logger.error("Error executing shutdown runnable", ex);
- ex.printStackTrace();
- }
- }
-
- if ("true".equalsIgnoreCase(System.getProperty("ebean.datasource.deregisterAllDrivers", "false"))) {
- deregisterAllJdbcDrivers();
- }
- }
- }
-
- private static void deregisterAllJdbcDrivers() {
- // This manually deregisters all JDBC drivers
- Enumeration drivers = DriverManager.getDrivers();
- while (drivers.hasMoreElements()) {
- Driver driver = drivers.nextElement();
- try {
- logger.info("Deregistering jdbc driver: "+driver);
- DriverManager.deregisterDriver(driver);
- } catch (SQLException e) {
- logger.error("Error deregistering driver "+driver, e);
- }
- }
- }
-
- /**
- * Register an ebeanServer to be shutdown when the JVM is shutdown.
- */
- public static void registerEbeanServer(SpiEbeanServer server) {
- synchronized (servers) {
- servers.add(server);
- }
- }
-
- /**
- * Deregister an ebeanServer.
- *
- * This is done when the ebeanServer is shutdown manually.
- *
- */
- public static void unregisterEbeanServer(SpiEbeanServer server) {
- synchronized (servers) {
- servers.remove(server);
- }
- }
-}
+package com.avaje.ebeaninternal.server.lib;
+
+import com.avaje.ebean.common.SpiContainer;
+import com.avaje.ebeaninternal.api.ClassUtil;
+import com.avaje.ebeaninternal.api.SpiEbeanServer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+/**
+ * Manages the shutdown of the JVM Runtime.
+ *
+ * Makes sure all the resources are shutdown properly and in order.
+ *
+ */
+public final class ShutdownManager {
+
+ private static final Logger logger = LoggerFactory.getLogger(ShutdownManager.class);
+
+ static final List servers = new ArrayList();
+
+ static final ShutdownHook shutdownHook = new ShutdownHook();
+
+ static boolean stopping;
+
+ static SpiContainer container;
+
+ static {
+ // Register the Shutdown hook
+ registerShutdownHook();
+ }
+
+ /**
+ * Disallow construction.
+ */
+ private ShutdownManager() {
+ }
+
+ public static void registerContainer(SpiContainer ebeanContainer){
+ container = ebeanContainer;
+ }
+
+ /**
+ * Make sure the ShutdownManager is activated.
+ */
+ public static void touch() {
+ // Do nothing
+ }
+
+ /**
+ * Return true if the system is in the process of stopping.
+ */
+ public static boolean isStopping() {
+ synchronized (servers) {
+ return stopping;
+ }
+ }
+
+ /**
+ * Deregister the Shutdown hook.
+ *
+ * For running in a Servlet Container a redeploy will cause a shutdown, and
+ * for that case we need to make sure the shutdown hook is deregistered.
+ *
+ */
+ protected static void deregisterShutdownHook() {
+ synchronized (servers) {
+ try {
+ Runtime.getRuntime().removeShutdownHook(shutdownHook);
+ } catch (IllegalStateException ex) {
+ if (!ex.getMessage().equals("Shutdown in progress")) {
+ throw ex;
+ }
+ }
+ }
+ }
+
+ /**
+ * Register the shutdown hook with the Runtime.
+ */
+ protected static void registerShutdownHook() {
+ synchronized (servers) {
+ try {
+ Runtime.getRuntime().addShutdownHook(shutdownHook);
+ } catch (IllegalStateException ex) {
+ if (!ex.getMessage().equals("Shutdown in progress")) {
+ throw ex;
+ }
+ }
+ }
+ }
+
+ /**
+ * Shutdown gracefully cleaning up any resources as required.
+ *
+ * This is typically invoked via JVM shutdown hook.
+ *
+ */
+ public static void shutdown() {
+ synchronized (servers) {
+ if (stopping) {
+ // Already run shutdown...
+ return;
+ }
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Shutting down");
+ }
+
+ stopping = true;
+
+ deregisterShutdownHook();
+
+ String shutdownRunner = System.getProperty("ebean.shutdown.runnable");
+ if (shutdownRunner != null) {
+ try {
+ // A custom runnable executed at the start of shutdown
+ Runnable r = (Runnable) ClassUtil.newInstance(shutdownRunner);
+ r.run();
+ } catch (Exception e) {
+ logger.error("Error running custom shutdown runnable", e);
+ }
+ }
+
+ if (container != null) {
+ // shutdown cluster networking if active
+ container.shutdown();
+ }
+
+ // shutdown any registered servers that have not
+ // already been shutdown manually
+ for (SpiEbeanServer server : servers) {
+ try {
+ server.shutdownManaged();
+ } catch (Exception ex) {
+ logger.error("Error executing shutdown runnable", ex);
+ ex.printStackTrace();
+ }
+ }
+
+ if ("true".equalsIgnoreCase(System.getProperty("ebean.datasource.deregisterAllDrivers", "false"))) {
+ deregisterAllJdbcDrivers();
+ }
+ }
+ }
+
+ private static void deregisterAllJdbcDrivers() {
+ // This manually deregisters all JDBC drivers
+ Enumeration drivers = DriverManager.getDrivers();
+ while (drivers.hasMoreElements()) {
+ Driver driver = drivers.nextElement();
+ try {
+ logger.info("Deregistering jdbc driver: "+driver);
+ DriverManager.deregisterDriver(driver);
+ } catch (SQLException e) {
+ logger.error("Error deregistering driver "+driver, e);
+ }
+ }
+ }
+
+ /**
+ * Register an ebeanServer to be shutdown when the JVM is shutdown.
+ */
+ public static void registerEbeanServer(SpiEbeanServer server) {
+ synchronized (servers) {
+ servers.add(server);
+ }
+ }
+
+ /**
+ * Deregister an ebeanServer.
+ *
+ * This is done when the ebeanServer is shutdown manually.
+ *
+ */
+ public static void unregisterEbeanServer(SpiEbeanServer server) {
+ synchronized (servers) {
+ servers.remove(server);
+ }
+ }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/DirectoryFinder.java b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/DirectoryFinder.java
index 090d06809..0755ba6c8 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/DirectoryFinder.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/DirectoryFinder.java
@@ -1,108 +1,108 @@
-package com.avaje.ebeaninternal.server.lib.resource;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-
-/**
- * Helper object used to find directories typically from the current working
- * directory.
- */
-public class DirectoryFinder {
-
- private static final Logger logger = LoggerFactory.getLogger(DirectoryFinder.class);
-
- /**
- * Find a directory by search through subdirectories.
- *
- * For example, used to find the WEB-INF directory starting from the current
- * working directory.
- *
- *
- *
- *
- * // search to a depth of 3 from the current working directory
- * // looking for a directory WEB-INF that contains the subdirectory
- * // data
- *
- * File dir = DirectoryFinder.find(null, "WEB-INF/data", 3);
- * if (dir != null) {
- * //found the directory
- * }
- *
- */
- public static File find(File startDir, String match, int maxDepth) {
-
- String matchSub = null;
- int slashPos = match.indexOf('/');
- if (slashPos > -1) {
- // match has sub directories
- matchSub = match.substring(slashPos + 1);
- match = match.substring(0, slashPos);
- }
-
- // search for the directory
- File found = find(startDir, match, matchSub, 0, maxDepth);
-
- if (found != null && matchSub != null) {
- // match has sub directories
- return new File(found, matchSub);
- }
- return found;
- }
-
- private static File find(File dir, String match, String matchSub, int depth, int maxDepth) {
-
- if (dir == null) {
- String curDir = System.getProperty("user.dir");
- dir = new File(curDir);
- }
-
- if (dir.exists()) {
- File[] list = dir.listFiles();
- if (list != null){
- for (int i = 0; i < list.length; i++) {
- if (isMatch(list[i], match, matchSub)) {
- return list[i];
- }
- }
-
- // go through the directories again
- // Aka *NOT* a depth first search
- if (depth < maxDepth) {
- for (int i = 0; i < list.length; i++) {
- if (list[i].isDirectory()) {
- File found = find(list[i], match, matchSub, depth + 1, maxDepth);
- if (found != null) {
- return found;
- }
- }
- }
- }
- }
- }
- return null;
- }
-
- private static boolean isMatch(File f, String match, String matchSub) {
- if (f == null) {
- return false;
- }
- if (!f.isDirectory()) {
- return false;
- }
- if (!f.getName().equalsIgnoreCase(match)) {
- return false;
- }
- if (matchSub == null) {
- return true;
- }
- File sub = new File(f, matchSub);
- if (logger.isTraceEnabled()){
- logger.trace("search; " + f.getPath());
- }
- return sub.exists();
-
- }
-}
+package com.avaje.ebeaninternal.server.lib.resource;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+
+/**
+ * Helper object used to find directories typically from the current working
+ * directory.
+ */
+public class DirectoryFinder {
+
+ private static final Logger logger = LoggerFactory.getLogger(DirectoryFinder.class);
+
+ /**
+ * Find a directory by search through subdirectories.
+ *
+ * For example, used to find the WEB-INF directory starting from the current
+ * working directory.
+ *
+ *
+ *
+ *
+ * // search to a depth of 3 from the current working directory
+ * // looking for a directory WEB-INF that contains the subdirectory
+ * // data
+ *
+ * File dir = DirectoryFinder.find(null, "WEB-INF/data", 3);
+ * if (dir != null) {
+ * //found the directory
+ * }
+ *
+ */
+ public static File find(File startDir, String match, int maxDepth) {
+
+ String matchSub = null;
+ int slashPos = match.indexOf('/');
+ if (slashPos > -1) {
+ // match has sub directories
+ matchSub = match.substring(slashPos + 1);
+ match = match.substring(0, slashPos);
+ }
+
+ // search for the directory
+ File found = find(startDir, match, matchSub, 0, maxDepth);
+
+ if (found != null && matchSub != null) {
+ // match has sub directories
+ return new File(found, matchSub);
+ }
+ return found;
+ }
+
+ private static File find(File dir, String match, String matchSub, int depth, int maxDepth) {
+
+ if (dir == null) {
+ String curDir = System.getProperty("user.dir");
+ dir = new File(curDir);
+ }
+
+ if (dir.exists()) {
+ File[] list = dir.listFiles();
+ if (list != null){
+ for (int i = 0; i < list.length; i++) {
+ if (isMatch(list[i], match, matchSub)) {
+ return list[i];
+ }
+ }
+
+ // go through the directories again
+ // Aka *NOT* a depth first search
+ if (depth < maxDepth) {
+ for (int i = 0; i < list.length; i++) {
+ if (list[i].isDirectory()) {
+ File found = find(list[i], match, matchSub, depth + 1, maxDepth);
+ if (found != null) {
+ return found;
+ }
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ private static boolean isMatch(File f, String match, String matchSub) {
+ if (f == null) {
+ return false;
+ }
+ if (!f.isDirectory()) {
+ return false;
+ }
+ if (!f.getName().equalsIgnoreCase(match)) {
+ return false;
+ }
+ if (matchSub == null) {
+ return true;
+ }
+ File sub = new File(f, matchSub);
+ if (logger.isTraceEnabled()){
+ logger.trace("search; " + f.getPath());
+ }
+ return sub.exists();
+
+ }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/FileResourceContent.java b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/FileResourceContent.java
index 893603aff..7c0e9fff4 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/FileResourceContent.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/FileResourceContent.java
@@ -1,71 +1,71 @@
-package com.avaje.ebeaninternal.server.lib.resource;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Date;
-
-/**
- * Content from a file system file.
- */
-public class FileResourceContent implements ResourceContent {
-
- /**
- * The underlying file.
- */
- File file;
-
- String entryName;
-
- /**
- * Create with a File and the entryName.
- */
- public FileResourceContent(File file, String entryName) {
- this.file = file;
- this.entryName = entryName;
- }
-
- public String toString() {
- StringBuffer sb = new StringBuffer();
- sb.append("[").append(getName());
- sb.append("] size[").append(size());
- sb.append("] lastModified[").append(new Date(lastModified()));
- sb.append("]");
- return sb.toString();
- }
-
- /**
- * Returns the entry name which contains the path from the base directory.
- *
- * This does not return the full path of the file, but the path relative to
- * the FileIoSource directory.
- *
- */
- public String getName() {
- return entryName;
- }
-
- /**
- * Return the time the file was last modified.
- */
- public long lastModified() {
- return file.lastModified();
- }
-
- /**
- * Return the size of the file.
- */
- public long size() {
- return file.length();
- }
-
- /**
- * Return the input stream for this file.
- */
- public InputStream getInputStream() throws IOException {
-
- FileInputStream is = new FileInputStream(file);
- return is;
- }
-}
+package com.avaje.ebeaninternal.server.lib.resource;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Date;
+
+/**
+ * Content from a file system file.
+ */
+public class FileResourceContent implements ResourceContent {
+
+ /**
+ * The underlying file.
+ */
+ File file;
+
+ String entryName;
+
+ /**
+ * Create with a File and the entryName.
+ */
+ public FileResourceContent(File file, String entryName) {
+ this.file = file;
+ this.entryName = entryName;
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("[").append(getName());
+ sb.append("] size[").append(size());
+ sb.append("] lastModified[").append(new Date(lastModified()));
+ sb.append("]");
+ return sb.toString();
+ }
+
+ /**
+ * Returns the entry name which contains the path from the base directory.
+ *
+ * This does not return the full path of the file, but the path relative to
+ * the FileIoSource directory.
+ *
+ */
+ public String getName() {
+ return entryName;
+ }
+
+ /**
+ * Return the time the file was last modified.
+ */
+ public long lastModified() {
+ return file.lastModified();
+ }
+
+ /**
+ * Return the size of the file.
+ */
+ public long size() {
+ return file.length();
+ }
+
+ /**
+ * Return the input stream for this file.
+ */
+ public InputStream getInputStream() throws IOException {
+
+ FileInputStream is = new FileInputStream(file);
+ return is;
+ }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/FileResourceSource.java b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/FileResourceSource.java
index 1a9a9aeda..1f9a7461d 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/FileResourceSource.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/FileResourceSource.java
@@ -1,51 +1,51 @@
-package com.avaje.ebeaninternal.server.lib.resource;
-
-import java.io.File;
-
-/**
- * A file system directory represented as a FileSource.
- */
-public class FileResourceSource extends AbstractResourceSource implements ResourceSource {
-
- /**
- * The directory name.
- */
- String directory;
-
- String baseDir;
-
- /**
- * Create the source based on a directory name.
- */
- public FileResourceSource(String directory){
- this.directory = directory;
- this.baseDir = directory+File.separator;
- }
-
- /**
- * Create the source based on a directory file.
- */
- public FileResourceSource(File dir){
- this(dir.getPath());
- }
-
-
- public String getRealPath() {
- return directory;
- }
-
- /**
- * Search for the given file and return as IoContent.
- */
- public ResourceContent getContent(String entry) {
-
- String fullPath = baseDir+entry;
-
- File f = new File(fullPath);
- if (f.exists()){
- FileResourceContent content = new FileResourceContent(f, entry);
- return content;
- }
- return null;
- }
-}
+package com.avaje.ebeaninternal.server.lib.resource;
+
+import java.io.File;
+
+/**
+ * A file system directory represented as a FileSource.
+ */
+public class FileResourceSource extends AbstractResourceSource implements ResourceSource {
+
+ /**
+ * The directory name.
+ */
+ String directory;
+
+ String baseDir;
+
+ /**
+ * Create the source based on a directory name.
+ */
+ public FileResourceSource(String directory){
+ this.directory = directory;
+ this.baseDir = directory+File.separator;
+ }
+
+ /**
+ * Create the source based on a directory file.
+ */
+ public FileResourceSource(File dir){
+ this(dir.getPath());
+ }
+
+
+ public String getRealPath() {
+ return directory;
+ }
+
+ /**
+ * Search for the given file and return as IoContent.
+ */
+ public ResourceContent getContent(String entry) {
+
+ String fullPath = baseDir+entry;
+
+ File f = new File(fullPath);
+ if (f.exists()){
+ FileResourceContent content = new FileResourceContent(f, entry);
+ return content;
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/ResourceContent.java b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/ResourceContent.java
index 42b6811e6..ebc844371 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/ResourceContent.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/ResourceContent.java
@@ -1,34 +1,34 @@
-package com.avaje.ebeaninternal.server.lib.resource;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Represents content that can be read via the ResourceManager.
- *
- * Typically either content from a File or a URL.
- *
- */
-public interface ResourceContent {
-
- /**
- * The name of the content.
- */
- public String getName();
-
- /**
- * The size of the content in bytes.
- */
- public long size();
-
- /**
- * The last modified timestamp of the content.
- */
- public long lastModified();
-
- /**
- * The content itself.
- */
- public InputStream getInputStream() throws IOException;
-
-}
+package com.avaje.ebeaninternal.server.lib.resource;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Represents content that can be read via the ResourceManager.
+ *
+ * Typically either content from a File or a URL.
+ *
+ */
+public interface ResourceContent {
+
+ /**
+ * The name of the content.
+ */
+ public String getName();
+
+ /**
+ * The size of the content in bytes.
+ */
+ public long size();
+
+ /**
+ * The last modified timestamp of the content.
+ */
+ public long lastModified();
+
+ /**
+ * The content itself.
+ */
+ public InputStream getInputStream() throws IOException;
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/ResourceSource.java b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/ResourceSource.java
index c0cca33f4..346df0d6f 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/ResourceSource.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/ResourceSource.java
@@ -1,37 +1,37 @@
-package com.avaje.ebeaninternal.server.lib.resource;
-
-import java.io.IOException;
-
-/**
- * A Source for ResourceManager.
- *
- * Typically a File System Directory based source or a ServletContext URL
- * resource based source (for Servlet WAR files).
- *
- */
-public interface ResourceSource {
-
- /**
- * Return the File System path of the root of the ResourceSource.
- *
- * This will return null IF the ResourceSource is an unpacked WAR file.
- *
- */
- public String getRealPath();
-
- /**
- * Find the content with a given entry name. This will return null if no
- * matching content was found.
- */
- public ResourceContent getContent(String entry);
-
- /**
- * Return the content as a String.
- */
- public String readString(ResourceContent content, int bufSize) throws IOException;
-
- /**
- * Return the content as a byte[].
- */
- public byte[] readBytes(ResourceContent content, int bufSize) throws IOException;
-}
+package com.avaje.ebeaninternal.server.lib.resource;
+
+import java.io.IOException;
+
+/**
+ * A Source for ResourceManager.
+ *
+ * Typically a File System Directory based source or a ServletContext URL
+ * resource based source (for Servlet WAR files).
+ *
+ */
+public interface ResourceSource {
+
+ /**
+ * Return the File System path of the root of the ResourceSource.
+ *
+ * This will return null IF the ResourceSource is an unpacked WAR file.
+ *
+ */
+ public String getRealPath();
+
+ /**
+ * Find the content with a given entry name. This will return null if no
+ * matching content was found.
+ */
+ public ResourceContent getContent(String entry);
+
+ /**
+ * Return the content as a String.
+ */
+ public String readString(ResourceContent content, int bufSize) throws IOException;
+
+ /**
+ * Return the content as a byte[].
+ */
+ public byte[] readBytes(ResourceContent content, int bufSize) throws IOException;
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/UrlResourceContent.java b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/UrlResourceContent.java
index 0dac4c904..b23554aa5 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/UrlResourceContent.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/UrlResourceContent.java
@@ -1,77 +1,77 @@
-package com.avaje.ebeaninternal.server.lib.resource;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.net.URLConnection;
-import java.util.Date;
-
-/**
- * Content from a URL Resource.
- */
-public class UrlResourceContent implements ResourceContent {
-
- /**
- * The underlying resource.
- */
- //URL url;
-
- String entryName;
-
- URLConnection con;
-
- /**
- * Create with a File and the entryName.
- */
- public UrlResourceContent(URL url, String entryName) {
- //this.url = url;
- this.entryName = entryName;
- try {
- con = url.openConnection();
- } catch (IOException ex){
- throw new RuntimeException(ex);
- }
- }
-
- public String toString() {
- StringBuffer sb = new StringBuffer();
- sb.append("[").append(getName());
- sb.append("] size[").append(size());
- sb.append("] lastModified[").append(new Date(lastModified()));
- sb.append("]");
- return sb.toString();
- }
-
- /**
- * Returns the entry name which contains the path from the base directory.
- *
- * This does not return the full path of the file, but the path relative to
- * the FileIoSource directory.
- *
- */
- public String getName() {
- return entryName;
- }
-
- /**
- * Return the time the file was last modified.
- */
- public long lastModified() {
- return con.getLastModified();
- }
-
- /**
- * Return the size of the file.
- */
- public long size() {
- return con.getContentLength();
- }
-
- /**
- * Return the input stream for this file.
- */
- public InputStream getInputStream() throws IOException {
-
- return con.getInputStream();
- }
-}
+package com.avaje.ebeaninternal.server.lib.resource;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Date;
+
+/**
+ * Content from a URL Resource.
+ */
+public class UrlResourceContent implements ResourceContent {
+
+ /**
+ * The underlying resource.
+ */
+ //URL url;
+
+ String entryName;
+
+ URLConnection con;
+
+ /**
+ * Create with a File and the entryName.
+ */
+ public UrlResourceContent(URL url, String entryName) {
+ //this.url = url;
+ this.entryName = entryName;
+ try {
+ con = url.openConnection();
+ } catch (IOException ex){
+ throw new RuntimeException(ex);
+ }
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("[").append(getName());
+ sb.append("] size[").append(size());
+ sb.append("] lastModified[").append(new Date(lastModified()));
+ sb.append("]");
+ return sb.toString();
+ }
+
+ /**
+ * Returns the entry name which contains the path from the base directory.
+ *
+ * This does not return the full path of the file, but the path relative to
+ * the FileIoSource directory.
+ *
+ */
+ public String getName() {
+ return entryName;
+ }
+
+ /**
+ * Return the time the file was last modified.
+ */
+ public long lastModified() {
+ return con.getLastModified();
+ }
+
+ /**
+ * Return the size of the file.
+ */
+ public long size() {
+ return con.getContentLength();
+ }
+
+ /**
+ * Return the input stream for this file.
+ */
+ public InputStream getInputStream() throws IOException {
+
+ return con.getInputStream();
+ }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/UrlResourceSource.java b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/UrlResourceSource.java
index 5be64a80b..6a60938c6 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/resource/UrlResourceSource.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/resource/UrlResourceSource.java
@@ -1,64 +1,64 @@
-package com.avaje.ebeaninternal.server.lib.resource;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-
-import javax.servlet.ServletContext;
-
-import com.avaje.ebeaninternal.server.lib.util.GeneralException;
-
-/**
- * A file system directory represented as a FileSource.
- */
-public class UrlResourceSource extends AbstractResourceSource implements ResourceSource {
-
-
- ServletContext sc;
-
- String basePath;
-
- String realPath;
-
- /**
- * Create the source based on a directory name.
- */
- public UrlResourceSource(ServletContext sc, String basePath){
- this.sc = sc;
- if (basePath == null){
- this.basePath = "/";
- } else {
- this.basePath = "/"+basePath+"/";
- }
- this.realPath = sc.getRealPath(basePath);
- }
-
- /**
- * Returns the "real path" from the ServletContext root.
- *
- * This can be null for unpacked WAR deployment.
- *
- */
- public String getRealPath() {
- return realPath;
- }
-
- /**
- * Search for the given URL resource and return as ResourceContent.
- *
- * Returns null if the resource is not found.
- *
- */
- public ResourceContent getContent(String entry) {
-
- try {
- URL url = sc.getResource(basePath+entry);
- if (url != null){
- return new UrlResourceContent(url, entry);
- }
- return null;
-
- } catch (MalformedURLException ex){
- throw new GeneralException(ex);
- }
- }
-}
+package com.avaje.ebeaninternal.server.lib.resource;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.servlet.ServletContext;
+
+import com.avaje.ebeaninternal.server.lib.util.GeneralException;
+
+/**
+ * A file system directory represented as a FileSource.
+ */
+public class UrlResourceSource extends AbstractResourceSource implements ResourceSource {
+
+
+ ServletContext sc;
+
+ String basePath;
+
+ String realPath;
+
+ /**
+ * Create the source based on a directory name.
+ */
+ public UrlResourceSource(ServletContext sc, String basePath){
+ this.sc = sc;
+ if (basePath == null){
+ this.basePath = "/";
+ } else {
+ this.basePath = "/"+basePath+"/";
+ }
+ this.realPath = sc.getRealPath(basePath);
+ }
+
+ /**
+ * Returns the "real path" from the ServletContext root.
+ *
+ * This can be null for unpacked WAR deployment.
+ *
+ */
+ public String getRealPath() {
+ return realPath;
+ }
+
+ /**
+ * Search for the given URL resource and return as ResourceContent.
+ *
+ * Returns null if the resource is not found.
+ *
+ */
+ public ResourceContent getContent(String entry) {
+
+ try {
+ URL url = sc.getResource(basePath+entry);
+ if (url != null){
+ return new UrlResourceContent(url, entry);
+ }
+ return null;
+
+ } catch (MalformedURLException ex){
+ throw new GeneralException(ex);
+ }
+ }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/BusyConnectionBuffer.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/BusyConnectionBuffer.java
index 98b50346b..38be65f7d 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/BusyConnectionBuffer.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/BusyConnectionBuffer.java
@@ -1,202 +1,202 @@
-package com.avaje.ebeaninternal.server.lib.sql;
-
-import java.util.Arrays;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.avaje.ebeaninternal.server.lib.sql.PooledConnectionStatistics.LoadValues;
-
-/**
- * A buffer especially designed for Busy PooledConnections.
- *
- * All thread safety controlled externally (by PooledConnectionQueue).
- *
- *
- * It has a set of 'slots' and PooledConnections know which slot they went into
- * and this allows for fast addition and removal (by slotId without looping).
- * The capacity will increase on demand by the 'growBy' amount.
- *
- *
- * @author rbygrave
- *
- */
-class BusyConnectionBuffer {
-
- private static final Logger logger = LoggerFactory.getLogger(BusyConnectionBuffer.class);
-
- private PooledConnection[] slots;
-
- private int growBy;
-
- private int size;
-
- private int pos = -1;
-
- /**
- * Create the buffer with an initial capacity and fixed growBy.
- * We generally do not want the buffer to grow very often.
- *
- * @param capacity
- * the initial capacity
- * @param growBy
- * the fixed amount to grow the buffer by.
- */
- protected BusyConnectionBuffer(int capacity, int growBy) {
- this.slots = new PooledConnection[capacity];
- this.growBy = growBy;
- }
-
- /**
- * We can only grow (not shrink) the capacity.
- */
- protected void setCapacity(int newCapacity) {
- if (newCapacity > slots.length){
- PooledConnection[] current = this.slots;
- this.slots = new PooledConnection[newCapacity];
- System.arraycopy(current, 0, this.slots, 0, current.length);
- }
- }
-
- public String toString() {
- return Arrays.toString(slots);
- }
-
- protected int getCapacity() {
- return slots.length;
- }
-
- protected int size(){
- return size;
- }
-
- protected boolean isEmpty() {
- return size == 0;
- }
-
- protected int add(PooledConnection pc){
- if (size == slots.length){
- // grow the capacity
- setCapacity(slots.length + growBy);
- }
- int slot = nextEmptySlot();
- pc.setSlotId(slot);
- slots[slot] = pc;
- return ++size;
- }
-
- protected boolean remove(PooledConnection pc) {
-
- int slotId = pc.getSlotId();
- if (slots[slotId] != pc){
- PooledConnection heldBy = slots[slotId];
- logger.warn("Failed to remove from slot[{}] PooledConnection[{}] - HeldBy[{}]", pc.getSlotId(), pc, heldBy);
- return false;
- }
- slots[slotId] = null;
- --size;
- return true;
- }
-
- /**
- * Collect the load statistics from all the busy connections.
- * @param reset
- */
- protected void collectStatistics(LoadValues values, boolean reset) {
-
- for (int i = 0; i < slots.length; i++) {
- if (slots[i] != null){
- values.plus(slots[i].getStatistics().getValues(reset));
- }
- }
- }
-
- /**
- * Close connections that should be considered leaked.
- */
- protected void closeBusyConnections(long leakTimeMinutes) {
-
- long olderThanTime = System.currentTimeMillis() - (leakTimeMinutes*60000);
-
- logger.debug("Closing busy connections using leakTimeMinutes {}", leakTimeMinutes);
-
- for (int i = 0; i < slots.length; i++) {
- if (slots[i] != null){
- //tmp.add(slots[i]);
- PooledConnection pc = slots[i];
- if (pc.isLongRunning() || pc.getLastUsedTime() > olderThanTime) {
- // PooledConnection has been used recently or
- // expected to be longRunning so not closing...
- } else {
- slots[i] = null;
- --size;
- closeBusyConnection(pc);
- }
- }
- }
- }
-
- private void closeBusyConnection(PooledConnection pc) {
- try {
-
- logger.warn("DataSourcePool closing busy connection? "+pc.getFullDescription());
- System.out.println("CLOSING busy connection: "+pc.getFullDescription());
-
- pc.closeConnectionFully(false);
-
- } catch (Exception ex) {
- // this should never actually happen
- logger.error("Error when closing potentially leaked connection "+pc.getDescription(), ex);
- }
- }
-
- /**
- * Returns information describing connections that are currently being used.
- */
- protected String getBusyConnectionInformation(boolean toLogger) {
-
- if (toLogger) {
- logger.info("Dumping [{}] busy connections: (Use datasource.xxx.capturestacktrace=true ... to get stackTraces)", size());
- }
-
- StringBuilder sb = new StringBuilder();
-
- for (int i = 0; i < slots.length; i++) {
- if (slots[i] != null){
- PooledConnection pc = slots[i];
- if (toLogger) {
- logger.info("Busy Connection - {}", pc.getFullDescription());
- } else {
- sb.append(pc.getFullDescription()).append("\r\n");
- }
- }
- }
-
- return sb.toString();
- }
-
-
- /**
- * Return the position of the next empty slot.
- */
- private int nextEmptySlot() {
-
- // search forward
- while(++pos < slots.length) {
- if (slots[pos] == null){
- return pos;
- }
- }
- // search from beginning
- pos = -1;
- while(++pos < slots.length) {
- if (slots[pos] == null){
- return pos;
- }
- }
-
- // not expecting this
- throw new RuntimeException("No Empty Slot Found?");
- }
-
-}
+package com.avaje.ebeaninternal.server.lib.sql;
+
+import java.util.Arrays;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.avaje.ebeaninternal.server.lib.sql.PooledConnectionStatistics.LoadValues;
+
+/**
+ * A buffer especially designed for Busy PooledConnections.
+ *
+ * All thread safety controlled externally (by PooledConnectionQueue).
+ *
+ *
+ * It has a set of 'slots' and PooledConnections know which slot they went into
+ * and this allows for fast addition and removal (by slotId without looping).
+ * The capacity will increase on demand by the 'growBy' amount.
+ *
+ *
+ * @author rbygrave
+ *
+ */
+class BusyConnectionBuffer {
+
+ private static final Logger logger = LoggerFactory.getLogger(BusyConnectionBuffer.class);
+
+ private PooledConnection[] slots;
+
+ private int growBy;
+
+ private int size;
+
+ private int pos = -1;
+
+ /**
+ * Create the buffer with an initial capacity and fixed growBy.
+ * We generally do not want the buffer to grow very often.
+ *
+ * @param capacity
+ * the initial capacity
+ * @param growBy
+ * the fixed amount to grow the buffer by.
+ */
+ protected BusyConnectionBuffer(int capacity, int growBy) {
+ this.slots = new PooledConnection[capacity];
+ this.growBy = growBy;
+ }
+
+ /**
+ * We can only grow (not shrink) the capacity.
+ */
+ protected void setCapacity(int newCapacity) {
+ if (newCapacity > slots.length){
+ PooledConnection[] current = this.slots;
+ this.slots = new PooledConnection[newCapacity];
+ System.arraycopy(current, 0, this.slots, 0, current.length);
+ }
+ }
+
+ public String toString() {
+ return Arrays.toString(slots);
+ }
+
+ protected int getCapacity() {
+ return slots.length;
+ }
+
+ protected int size(){
+ return size;
+ }
+
+ protected boolean isEmpty() {
+ return size == 0;
+ }
+
+ protected int add(PooledConnection pc){
+ if (size == slots.length){
+ // grow the capacity
+ setCapacity(slots.length + growBy);
+ }
+ int slot = nextEmptySlot();
+ pc.setSlotId(slot);
+ slots[slot] = pc;
+ return ++size;
+ }
+
+ protected boolean remove(PooledConnection pc) {
+
+ int slotId = pc.getSlotId();
+ if (slots[slotId] != pc){
+ PooledConnection heldBy = slots[slotId];
+ logger.warn("Failed to remove from slot[{}] PooledConnection[{}] - HeldBy[{}]", pc.getSlotId(), pc, heldBy);
+ return false;
+ }
+ slots[slotId] = null;
+ --size;
+ return true;
+ }
+
+ /**
+ * Collect the load statistics from all the busy connections.
+ * @param reset
+ */
+ protected void collectStatistics(LoadValues values, boolean reset) {
+
+ for (int i = 0; i < slots.length; i++) {
+ if (slots[i] != null){
+ values.plus(slots[i].getStatistics().getValues(reset));
+ }
+ }
+ }
+
+ /**
+ * Close connections that should be considered leaked.
+ */
+ protected void closeBusyConnections(long leakTimeMinutes) {
+
+ long olderThanTime = System.currentTimeMillis() - (leakTimeMinutes*60000);
+
+ logger.debug("Closing busy connections using leakTimeMinutes {}", leakTimeMinutes);
+
+ for (int i = 0; i < slots.length; i++) {
+ if (slots[i] != null){
+ //tmp.add(slots[i]);
+ PooledConnection pc = slots[i];
+ if (pc.isLongRunning() || pc.getLastUsedTime() > olderThanTime) {
+ // PooledConnection has been used recently or
+ // expected to be longRunning so not closing...
+ } else {
+ slots[i] = null;
+ --size;
+ closeBusyConnection(pc);
+ }
+ }
+ }
+ }
+
+ private void closeBusyConnection(PooledConnection pc) {
+ try {
+
+ logger.warn("DataSourcePool closing busy connection? "+pc.getFullDescription());
+ System.out.println("CLOSING busy connection: "+pc.getFullDescription());
+
+ pc.closeConnectionFully(false);
+
+ } catch (Exception ex) {
+ // this should never actually happen
+ logger.error("Error when closing potentially leaked connection "+pc.getDescription(), ex);
+ }
+ }
+
+ /**
+ * Returns information describing connections that are currently being used.
+ */
+ protected String getBusyConnectionInformation(boolean toLogger) {
+
+ if (toLogger) {
+ logger.info("Dumping [{}] busy connections: (Use datasource.xxx.capturestacktrace=true ... to get stackTraces)", size());
+ }
+
+ StringBuilder sb = new StringBuilder();
+
+ for (int i = 0; i < slots.length; i++) {
+ if (slots[i] != null){
+ PooledConnection pc = slots[i];
+ if (toLogger) {
+ logger.info("Busy Connection - {}", pc.getFullDescription());
+ } else {
+ sb.append(pc.getFullDescription()).append("\r\n");
+ }
+ }
+ }
+
+ return sb.toString();
+ }
+
+
+ /**
+ * Return the position of the next empty slot.
+ */
+ private int nextEmptySlot() {
+
+ // search forward
+ while(++pos < slots.length) {
+ if (slots[pos] == null){
+ return pos;
+ }
+ }
+ // search from beginning
+ pos = -1;
+ while(++pos < slots.length) {
+ if (slots[pos] == null){
+ return pos;
+ }
+ }
+
+ // not expecting this
+ throw new RuntimeException("No Empty Slot Found?");
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourceException.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourceException.java
index 9dc198aaf..0a585033d 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourceException.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourceException.java
@@ -1,24 +1,24 @@
-package com.avaje.ebeaninternal.server.lib.sql;
-
-
-
-/**
- * A general DataSource exception.
- */
-public class DataSourceException extends RuntimeException
-{
- static final long serialVersionUID = 7061559938704539844L;
-
- public DataSourceException(Exception cause) {
- super(cause);
- }
-
- public DataSourceException(String s, Exception cause) {
- super(s, cause);
- }
-
- public DataSourceException(String s) {
- super(s);
- }
-
-}
+package com.avaje.ebeaninternal.server.lib.sql;
+
+
+
+/**
+ * A general DataSource exception.
+ */
+public class DataSourceException extends RuntimeException
+{
+ static final long serialVersionUID = 7061559938704539844L;
+
+ public DataSourceException(Exception cause) {
+ super(cause);
+ }
+
+ public DataSourceException(String s, Exception cause) {
+ super(s, cause);
+ }
+
+ public DataSourceException(String s) {
+ super(s);
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourcePool.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourcePool.java
index 0c1c5c2e4..9601cab98 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourcePool.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourcePool.java
@@ -1,1017 +1,1017 @@
-package com.avaje.ebeaninternal.server.lib.sql;
-
-import java.io.PrintWriter;
-import java.sql.Connection;
-import java.sql.DriverManager;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.SQLFeatureNotSupportedException;
-import java.sql.Statement;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Properties;
-import java.util.Set;
-
-import javax.persistence.PersistenceException;
-import javax.sql.DataSource;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.avaje.ebean.config.DataSourceConfig;
-import com.avaje.ebeaninternal.api.ClassUtil;
-
-/**
- * A robust DataSource.
- *
- *
- * - Manages the number of connections closing connections that have been idle
- * for some time.
- *
- Notifies when the datasource goes down and comes back up.
- *
- Checks for expected downtime which is useful for schedule db backups.
- *
- Provides PreparedStatement caching
- *
- Knows the busy connections
- *
- Traces connections that have been leaked
- *
- *
- */
-public class DataSourcePool implements DataSource {
-
- private static final Logger logger = LoggerFactory.getLogger(DataSourcePool.class);
-
- /**
- * The name given to this dataSource.
- */
- private final String name;
-
- /**
- * Used to notify of changes to the DataSource status.
- */
- private final DataSourceAlert notify;
-
- /**
- * Optional listener that can be notified when connections are got from and
- * put back into the pool.
- */
- private final DataSourcePoolListener poolListener;
-
- /**
- * Properties used to create a Connection.
- */
- private final Properties connectionProps;
-
- /**
- * The jdbc connection url.
- */
- private final String databaseUrl;
-
- /**
- * The jdbc driver.
- */
- private final String databaseDriver;
-
- /**
- * The sql used to test a connection.
- */
- private final String heartbeatsql;
-
- private final int heartbeatFreqSecs;
-
- private final int heartbeatTimeoutSeconds;
-
-
- private final long trimPoolFreqMillis;
-
- /**
- * The transaction isolation level as per java.sql.Connection.
- */
- private final int transactionIsolation;
-
- /**
- * The default autoCommit setting for Connections in this pool.
- */
- private final boolean autoCommit;
-
- /**
- * Max idle time in millis.
- */
- private final int maxInactiveMillis;
-
- /**
- * Max age a connection is allowed in millis.
- * A value of 0 means no limit (no trimming based on max age).
- */
- private final long maxAgeMillis;
-
- /**
- * Flag set to true to capture stackTraces (can be expensive).
- */
- private boolean captureStackTrace;
-
- /**
- * The max size of the stack trace to report.
- */
- private int maxStackTraceSize;
-
- /**
- * flag to indicate we have sent an alert message.
- */
- private boolean dataSourceDownAlertSent;
-
- /**
- * The time the pool was last trimmed.
- */
- private long lastTrimTime;
-
- /**
- * Assume that the DataSource is up. heartBeat checking will discover when
- * it goes down, and comes back up again.
- */
- private boolean dataSourceUp = true;
-
- /**
- * The current alert.
- */
- private boolean inWarningMode;
-
- /**
- * The minimum number of connections this pool will maintain.
- */
- private int minConnections;
-
- /**
- * The maximum number of connections this pool will grow to.
- */
- private int maxConnections;
-
- /**
- * The number of connections to exceed before a warning Alert is fired.
- */
- private int warningSize;
-
- /**
- * The time a thread will wait for a connection to become available.
- */
- private final int waitTimeoutMillis;
-
- /**
- * The size of the preparedStatement cache;
- */
- private int pstmtCacheSize;
-
- private final PooledConnectionQueue queue;
-
- /**
- * Used to find and close() leaked connections. Leaked connections are
- * thought to be busy but have not been used for some time. Each time a
- * connection is used it sets it's lastUsedTime.
- */
- private long leakTimeMinutes;
-
- private final Runnable heartbeatRunnable = new HeartBeatRunnable();
-
- public DataSourcePool(DataSourceAlert notify, String name, DataSourceConfig params) {
-
- this.notify = notify;
- this.name = name;
- this.poolListener = createPoolListener(params.getPoolListener());
-
- this.autoCommit = params.isAutoCommit();
- this.transactionIsolation = params.getIsolationLevel();
-
- this.maxInactiveMillis = 1000 * params.getMaxInactiveTimeSecs();
- this.maxAgeMillis = 60000 * params.getMaxAgeMinutes();
- this.leakTimeMinutes = params.getLeakTimeMinutes();
- this.captureStackTrace = params.isCaptureStackTrace();
- this.maxStackTraceSize = params.getMaxStackTraceSize();
- this.databaseDriver = params.getDriver();
- this.databaseUrl = params.getUrl();
- this.pstmtCacheSize = params.getPstmtCacheSize();
-
- this.minConnections = params.getMinConnections();
- this.maxConnections = params.getMaxConnections();
- this.waitTimeoutMillis = params.getWaitTimeoutMillis();
- this.heartbeatsql = params.getHeartbeatSql();
- this.heartbeatFreqSecs = params.getHeartbeatFreqSecs();
- this.heartbeatTimeoutSeconds = params.getHeartbeatTimeoutSeconds();
- this.trimPoolFreqMillis = 1000 * params.getTrimPoolFreqSecs();
-
- queue = new PooledConnectionQueue(this);
-
- String un = params.getUsername();
- String pw = params.getPassword();
- if (un == null) {
- throw new RuntimeException("DataSource user is null?");
- }
- if (pw == null) {
- throw new RuntimeException("DataSource password is null?");
- }
- this.connectionProps = new Properties();
- this.connectionProps.setProperty("user", un);
- this.connectionProps.setProperty("password", pw);
-
- Map customProperties = params.getCustomProperties();
- if (customProperties != null){
- Set> entrySet = customProperties.entrySet();
- for (Entry entry : entrySet) {
- this.connectionProps.setProperty(entry.getKey(), entry.getValue());
- }
- }
-
- try {
- initialise();
- } catch (SQLException ex) {
- throw new DataSourceException(ex);
- }
- }
-
- class HeartBeatRunnable implements Runnable {
- @Override
- public void run() {
- checkDataSource();
- }
- }
-
-
- @Override
- public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException {
- throw new SQLFeatureNotSupportedException("We do not support java.util.logging");
- }
-
- /**
- * Create the DataSourcePoolListener if there is one.
- */
- private DataSourcePoolListener createPoolListener(String cn) {
- if (cn == null) {
- return null;
- }
- try {
- return (DataSourcePoolListener)ClassUtil.newInstance(cn, this.getClass());
- } catch (Exception e) {
- throw new DataSourceException(e);
- }
- }
-
- private void initialise() throws SQLException {
-
- // Ensure database driver is loaded
- try {
- ClassUtil.forName(this.databaseDriver, this.getClass());
- } catch (Throwable e) {
- throw new PersistenceException("Problem loading Database Driver [" + this.databaseDriver + "]: "
- + e.getMessage(), e);
- }
-
- String transIsolation = TransactionIsolation.getLevelDescription(transactionIsolation);
- StringBuilder sb = new StringBuilder();
- sb.append("DataSourcePool [").append(name);
- sb.append("] autoCommit[").append(autoCommit);
- sb.append("] transIsolation[").append(transIsolation);
- sb.append("] min[").append(minConnections);
- sb.append("] max[").append(maxConnections).append("]");
-
- logger.info(sb.toString());
-
- queue.ensureMinimumConnections();
- }
-
- /**
- * Returns false.
- */
- public boolean isWrapperFor(Class> arg0) throws SQLException {
- return false;
- }
-
- /**
- * Not Implemented.
- */
- public T unwrap(Class arg0) throws SQLException {
- throw new SQLException("Not Implemented");
- }
-
- /**
- * Return the dataSource name.
- */
- public String getName() {
- return name;
- }
-
- /**
- * Return the max size of stack traces used when trying to find connection pool leaks.
- *
- * This is only used when {@link #isCaptureStackTrace()} is true.
- *
- */
- public int getMaxStackTraceSize() {
- return maxStackTraceSize;
- }
-
- /**
- * Returns false when the dataSource is down.
- */
- public boolean isDataSourceUp() {
- return dataSourceUp;
- }
-
- /**
- * Called when the pool hits the warning level.
- */
- protected void notifyWarning(String msg) {
-
- if (!inWarningMode) {
- // send an Error to the event log...
- inWarningMode = true;
- logger.warn(msg);
- if (notify != null) {
- String subject = "DataSourcePool [" + name + "] warning";
- notify.dataSourceWarning(subject, msg);
- }
- }
- }
-
- private void notifyDataSourceIsDown(SQLException ex) {
-
- if (!dataSourceDownAlertSent) {
- logger.error("FATAL: DataSourcePool [" + name + "] is down or has network error!!!", ex);
- if (notify != null) {
- notify.dataSourceDown(name);
- }
- dataSourceDownAlertSent = true;
- }
- if (dataSourceUp) {
- reset();
- }
- dataSourceUp = false;
- }
-
- private void notifyDataSourceIsUp() {
- if (dataSourceDownAlertSent) {
- logger.error("RESOLVED FATAL: DataSourcePool [" + name + "] is back up!");
- if (notify != null) {
- notify.dataSourceUp(name);
- }
- dataSourceDownAlertSent = false;
-
- } else if (!dataSourceUp) {
- logger.info("DataSourcePool [" + name + "] is back up!");
- }
-
- if (!dataSourceUp) {
- dataSourceUp = true;
- reset();
- }
- }
-
-
- /**
- * Return the heartbeat frequency in seconds.
- *
- * This is the frequency that the heartbeat runnable should be run.
- *
- */
- public int getHeartbeatFreqSecs() {
- return heartbeatFreqSecs;
- }
-
- /**
- * Returns the Runnable used to check the dataSource using a heartbeat query.
- */
- public Runnable getHeartbeatRunnable() {
- return heartbeatRunnable;
- }
-
- /**
- * Trim connections (in the free list) based on idle time and maximum age.
- */
- private void trimIdleConnections() {
- if (System.currentTimeMillis() > (lastTrimTime + trimPoolFreqMillis)) {
- try {
- queue.trim(maxInactiveMillis, maxAgeMillis);
- lastTrimTime = System.currentTimeMillis();
- } catch (Exception e) {
- logger.error("Error trying to trim idle connections", e);
- }
- }
- }
-
- /**
- * Check the dataSource is up. Trim connections.
- *
- * This is called by the HeartbeatRunnable which should be scheduled to
- * run periodically (every heartbeatFreqSecs seconds actually).
- *
- */
- public void checkDataSource() {
-
- // first trim idle connections
- trimIdleConnections();
-
- Connection conn = null;
- try {
- // Get a connection from the pool and test it
- conn = getConnection();
- if (testConnection(conn)) {
- notifyDataSourceIsUp();
-
- } else {
- notifyDataSourceIsDown(null);
- }
-
- } catch (SQLException ex) {
- notifyDataSourceIsDown(ex);
-
- } finally {
- try {
- if (conn != null) {
- conn.close();
- }
- } catch (SQLException ex) {
- logger.warn("Can't close connection in checkDataSource!");
- }
- }
- }
-
- /**
- * Create a Connection that will not be part of the connection pool.
- *
- *
- * When this connection is closed it will not go back into the pool.
- *
- *
- *
- * If withDefaults is true then the Connection will have the autoCommit and
- * transaction isolation set to the defaults for the pool.
- *
- */
- public Connection createUnpooledConnection() throws SQLException {
-
- try {
- Connection conn = DriverManager.getConnection(databaseUrl, connectionProps);
- conn.setAutoCommit(autoCommit);
- conn.setTransactionIsolation(transactionIsolation);
- return conn;
-
- } catch (SQLException ex) {
- notifyDataSourceIsDown(null);
- throw ex;
- }
- }
-
- /**
- * Set a new maximum size. The pool should respect this new maximum
- * immediately and not require a restart. You may want to increase the
- * maxConnections if the pool gets large and hits the warning level.
- */
- public void setMaxSize(int max) {
- queue.setMaxSize(max);
- this.maxConnections = max;
- }
-
- /**
- * Return the max size this pool can grow to.
- */
- public int getMaxSize() {
- return maxConnections;
- }
-
- /**
- * Set the min size this pool should maintain.
- */
- public void setMinSize(int min) {
- queue.setMinSize(min);
- this.minConnections = min;
- }
-
- /**
- * Return the min size this pool should maintain.
- */
- public int getMinSize() {
- return minConnections;
- }
-
- /**
- * Set a new maximum size. The pool should respect this new maximum
- * immediately and not require a restart. You may want to increase the
- * maxConnections if the pool gets large and hits the warning and or alert
- * levels.
- */
- public void setWarningSize(int warningSize) {
- queue.setWarningSize(warningSize);
- this.warningSize = warningSize;
- }
-
- /**
- * Return the warning size. When the pool hits this size it can send a
- * notify message to an administrator.
- */
- public int getWarningSize() {
- return warningSize;
- }
-
- /**
- * Return the time in millis that threads will wait when the pool has hit
- * the max size. These threads wait for connections to be returned by the
- * busy connections.
- */
- public int getWaitTimeoutMillis() {
- return waitTimeoutMillis;
- }
-
- /**
- * Return the time after which inactive connections are trimmed.
- */
- public int getMaxInactiveMillis() {
- return maxInactiveMillis;
- }
-
- /**
- * Return the maximum age a connection is allowed to be before it is trimmed
- * out of the pool. This value can be 0 which means there is no maximum age.
- */
- public long getMaxAgeMillis() {
- return maxAgeMillis;
- }
-
- private boolean testConnection(Connection conn) throws SQLException {
-
- if (heartbeatsql == null) {
- return conn.isValid(heartbeatTimeoutSeconds);
- }
- Statement stmt = null;
- ResultSet rset = null;
- try {
- // It should only error IF the DataSource is down or a network issue
- stmt = conn.createStatement();
- if (heartbeatTimeoutSeconds > 0) {
- stmt.setQueryTimeout(heartbeatTimeoutSeconds);
- }
- rset = stmt.executeQuery(heartbeatsql);
- conn.commit();
-
- return true;
-
- } finally {
- try {
- if (rset != null) {
- rset.close();
- }
- } catch (SQLException e) {
- logger.error(null, e);
- }
- try {
- if (stmt != null) {
- stmt.close();
- }
- } catch (SQLException e) {
- logger.error(null, e);
- }
- }
- }
-
- /**
- * Make sure the connection is still ok to use. If not then remove it from
- * the pool.
- */
- protected boolean validateConnection(PooledConnection conn) {
- try {
- return testConnection(conn);
-
- } catch (Exception e) {
- logger.warn("heartbeatsql test failed on connection[" + conn.getName() + "]");
- return false;
- }
- }
-
- /**
- * Called by the PooledConnection themselves, returning themselves to the
- * pool when they have been finished with.
- *
- * Note that connections may not be added back to the pool if returnToPool
- * is false or if they where created before the recycleTime. In both of
- * these cases the connection is fully closed and not pooled.
- *
- *
- * @param pooledConnection
- * the returning connection
- *
- */
- protected void returnConnection(PooledConnection pooledConnection) {
-
- // return a normal 'good' connection
- returnTheConnection(pooledConnection, false);
- }
-
- /**
- * This is a bad connection and must be removed from the pool's busy list and fully closed.
- */
- protected void returnConnectionForceClose(PooledConnection pooledConnection) {
-
- returnTheConnection(pooledConnection, true);
- }
-
- /**
- * Return connection. If forceClose is true then this is a bad connection that
- * must be removed and closed fully.
- */
- private void returnTheConnection(PooledConnection pooledConnection, boolean forceClose) {
-
- if (poolListener != null && !forceClose) {
- poolListener.onBeforeReturnConnection(pooledConnection);
- }
- queue.returnPooledConnection(pooledConnection, forceClose);
-
- if (forceClose) {
- // Got a bad connection so check the pool
- checkDataSource();
- }
- }
-
- /**
- * Collect statistics of a connection that is fully closing
- */
- protected void reportClosingConnection(PooledConnection pooledConnection) {
-
- queue.reportClosingConnection(pooledConnection);
- }
-
- /**
- * Returns information describing connections that are currently being used.
- */
- public String getBusyConnectionInformation() {
-
- return queue.getBusyConnectionInformation();
- }
-
- /**
- * Dumps the busy connection information to the logs.
- *
- * This includes the stackTrace elements if they are being captured. This is
- * useful when needing to look a potential connection pool leaks.
- *
- */
- public void dumpBusyConnectionInformation() {
-
- queue.dumpBusyConnectionInformation();
- }
-
- /**
- * Close any busy connections that have not been used for some time.
- *
- * These connections are considered to have leaked from the connection pool.
- *
- *
- * Connection leaks occur when code doesn't ensure that connections are
- * closed() after they have been finished with. There should be an
- * appropriate try catch finally block to ensure connections are always
- * closed and put back into the pool.
- *
- */
- public void closeBusyConnections(long leakTimeMinutes) {
-
- queue.closeBusyConnections(leakTimeMinutes);
- }
-
- /**
- * Grow the pool by creating a new connection. The connection can either be
- * added to the available list, or returned.
- *
- * This method is protected by synchronization in calling methods.
- *
- */
- protected PooledConnection createConnectionForQueue(int connId) throws SQLException {
-
- try {
- Connection c = createUnpooledConnection();
-
- PooledConnection pc = new PooledConnection(this, connId, c);
- pc.resetForUse();
-
- if (!dataSourceUp) {
- notifyDataSourceIsUp();
- }
- return pc;
-
- } catch (SQLException ex) {
- notifyDataSourceIsDown(ex);
- throw ex;
- }
- }
-
- /**
- * Close all the connections in the pool.
- *
- *
- * - Checks that the database is up.
- *
- Resets the Alert level.
- *
- Closes busy connections that have not been used for some time (aka
- * leaks).
- *
- This closes all the currently available connections.
- *
- Busy connections are closed when they are returned to the pool.
- *
- *
- */
- public void reset() {
- queue.reset(leakTimeMinutes);
- inWarningMode = false;
- }
-
- /**
- * Return a pooled connection.
- */
- public Connection getConnection() throws SQLException {
- return getPooledConnection();
- }
-
- /**
- * Get a connection from the pool.
- *
- * This will grow the pool if all the current connections are busy. This
- * will go into a wait if the pool has hit its maximum size.
- *
- */
- public PooledConnection getPooledConnection() throws SQLException {
-
- PooledConnection c = queue.getPooledConnection();
-
- if (captureStackTrace) {
- c.setStackTrace(Thread.currentThread().getStackTrace());
- }
-
- if (poolListener != null) {
- poolListener.onAfterBorrowConnection(c);
- }
- return c;
- }
-
- /**
- * Send a message to the DataSourceAlertListener to test it. This is so that
- * you can make sure the alerter is configured correctly etc.
- */
- public void testAlert() {
-
- String subject = "Test DataSourcePool [" + name + "]";
- String msg = "Just testing if alert message is sent successfully.";
-
- if (notify != null) {
- notify.dataSourceWarning(subject, msg);
- }
- }
-
- /**
- * This will close all the free connections, and then go into a wait loop,
- * waiting for the busy connections to be freed.
- *
- *
- * The DataSources's should be shutdown AFTER thread pools. Leaked
- * Connections are not waited on, as that would hang the server.
- *
- */
- public void shutdown(boolean deregisterDriver) {
- queue.shutdown();
- if (deregisterDriver){
- deregisterDriver();
- }
- }
-
- /**
- * Return the default autoCommit setting Connections in this pool will use.
- *
- * @return true if the pool defaults autoCommit to true
- */
- public boolean getAutoCommit() {
- return autoCommit;
- }
-
- /**
- * Return the default transaction isolation level connections in this pool
- * should have.
- *
- * @return the default transaction isolation level
- */
- public int getTransactionIsolation() {
- return transactionIsolation;
- }
-
- /**
- * Return true if the connection pool is currently capturing the StackTrace
- * when connections are 'got' from the pool.
- *
- * This is set to true to help diagnose connection pool leaks.
- *
- */
- public boolean isCaptureStackTrace() {
- return captureStackTrace;
- }
-
- /**
- * Set this to true means that the StackElements are captured every time a
- * connection is retrieved from the pool. This can be used to identify
- * connection pool leaks.
- */
- public void setCaptureStackTrace(boolean captureStackTrace) {
- this.captureStackTrace = captureStackTrace;
- }
-
- /**
- * Not implemented and shouldn't be used.
- */
- public Connection getConnection(String username, String password) throws SQLException {
- throw new SQLException("Method not supported");
- }
-
- /**
- * Not implemented and shouldn't be used.
- */
- public int getLoginTimeout() throws SQLException {
- throw new SQLException("Method not supported");
- }
-
- /**
- * Not implemented and shouldn't be used.
- */
- public void setLoginTimeout(int seconds) throws SQLException {
- throw new SQLException("Method not supported");
- }
-
- /**
- * Returns null.
- */
- public PrintWriter getLogWriter() {
- return null;
- }
-
- /**
- * Not implemented.
- */
- public void setLogWriter(PrintWriter writer) throws SQLException {
- throw new SQLException("Method not supported");
- }
-
- /**
- * For detecting and closing leaked connections. Connections that have been
- * busy for more than leakTimeMinutes are considered leaks and will be
- * closed on a reset().
- *
- * If you want to use a connection for that longer then you should consider
- * creating an unpooled connection or setting longRunning to true on that
- * connection.
- *
- */
- public void setLeakTimeMinutes(long leakTimeMinutes) {
- this.leakTimeMinutes = leakTimeMinutes;
- }
-
- /**
- * Return the number of minutes after which a busy connection could be
- * considered leaked from the connection pool.
- */
- public long getLeakTimeMinutes() {
- return leakTimeMinutes;
- }
-
- /**
- * Return the preparedStatement cache size.
- */
- public int getPstmtCacheSize() {
- return pstmtCacheSize;
- }
-
- /**
- * Set the preparedStatement cache size.
- */
- public void setPstmtCacheSize(int pstmtCacheSize) {
- this.pstmtCacheSize = pstmtCacheSize;
- }
-
- /**
- * Return the current status of the connection pool.
- *
- * If you pass reset = true then the counters such as
- * hitCount, waitCount and highWaterMark are reset.
- *
- */
- public Status getStatus(boolean reset) {
- return queue.getStatus(reset);
- }
-
- /**
- * Return the aggregated load statistics collected on all the connections in the pool.
- */
- public DataSourcePoolStatistics getStatistics(boolean reset) {
-
- return queue.getStatistics(reset);
- }
-
- /**
- * Deregister the JDBC driver.
- */
- public void deregisterDriver() {
- try {
- logger.debug("Deregistered the JDBC driver "+this.databaseDriver);
- DriverManager.deregisterDriver(DriverManager.getDriver(this.databaseUrl));
- } catch (SQLException e) {
- logger.warn("Error trying to deregister the JDBC driver "+this.databaseDriver, e);
- }
- }
-
- public static class Status {
-
- private final String name;
- private final int minSize;
- private final int maxSize;
- private final int free;
- private final int busy;
- private final int waiting;
- private final int highWaterMark;
- private final int waitCount;
- private final int hitCount;
-
- protected Status(String name, int minSize, int maxSize, int free, int busy, int waiting, int highWaterMark,
- int waitCount, int hitCount) {
- this.name = name;
- this.minSize = minSize;
- this.maxSize = maxSize;
- this.free = free;
- this.busy = busy;
- this.waiting = waiting;
- this.highWaterMark = highWaterMark;
- this.waitCount = waitCount;
- this.hitCount = hitCount;
- }
-
- public String toString() {
- return "min[" + minSize + "] max[" + maxSize + "] free[" + free + "] busy[" + busy + "] waiting[" + waiting
- + "] highWaterMark[" + highWaterMark + "] waitCount[" + waitCount + "] hitCount[" + hitCount+"]";
- }
-
- /**
- * Return the DataSource name.
- */
- public String getName() {
- return name;
- }
-
- /**
- * Return the min pool size.
- */
- public int getMinSize() {
- return minSize;
- }
-
- /**
- * Return the max pool size.
- */
- public int getMaxSize() {
- return maxSize;
- }
-
- /**
- * Return the current number of free connections in the pool.
- */
- public int getFree() {
- return free;
- }
-
- /**
- * Return the current number of busy connections in the pool.
- */
- public int getBusy() {
- return busy;
- }
-
- /**
- * Return the current number of threads waiting for a connection.
- */
- public int getWaiting() {
- return waiting;
- }
-
- /**
- * Return the high water mark of busy connections.
- */
- public int getHighWaterMark() {
- return highWaterMark;
- }
-
- /**
- * Return the total number of times a thread had to wait.
- */
- public int getWaitCount() {
- return waitCount;
- }
-
- /**
- * Return the total number of times there was an attempt to get a
- * connection.
- *
- * If the attempt to get a connection failed with a timeout or other
- * exception those attempts are still included in this hit count.
- *
- */
- public int getHitCount() {
- return hitCount;
- }
-
- }
-
-}
+package com.avaje.ebeaninternal.server.lib.sql;
+
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.Statement;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.persistence.PersistenceException;
+import javax.sql.DataSource;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.avaje.ebean.config.DataSourceConfig;
+import com.avaje.ebeaninternal.api.ClassUtil;
+
+/**
+ * A robust DataSource.
+ *
+ *
+ * - Manages the number of connections closing connections that have been idle
+ * for some time.
+ *
- Notifies when the datasource goes down and comes back up.
+ *
- Checks for expected downtime which is useful for schedule db backups.
+ *
- Provides PreparedStatement caching
+ *
- Knows the busy connections
+ *
- Traces connections that have been leaked
+ *
+ *
+ */
+public class DataSourcePool implements DataSource {
+
+ private static final Logger logger = LoggerFactory.getLogger(DataSourcePool.class);
+
+ /**
+ * The name given to this dataSource.
+ */
+ private final String name;
+
+ /**
+ * Used to notify of changes to the DataSource status.
+ */
+ private final DataSourceAlert notify;
+
+ /**
+ * Optional listener that can be notified when connections are got from and
+ * put back into the pool.
+ */
+ private final DataSourcePoolListener poolListener;
+
+ /**
+ * Properties used to create a Connection.
+ */
+ private final Properties connectionProps;
+
+ /**
+ * The jdbc connection url.
+ */
+ private final String databaseUrl;
+
+ /**
+ * The jdbc driver.
+ */
+ private final String databaseDriver;
+
+ /**
+ * The sql used to test a connection.
+ */
+ private final String heartbeatsql;
+
+ private final int heartbeatFreqSecs;
+
+ private final int heartbeatTimeoutSeconds;
+
+
+ private final long trimPoolFreqMillis;
+
+ /**
+ * The transaction isolation level as per java.sql.Connection.
+ */
+ private final int transactionIsolation;
+
+ /**
+ * The default autoCommit setting for Connections in this pool.
+ */
+ private final boolean autoCommit;
+
+ /**
+ * Max idle time in millis.
+ */
+ private final int maxInactiveMillis;
+
+ /**
+ * Max age a connection is allowed in millis.
+ * A value of 0 means no limit (no trimming based on max age).
+ */
+ private final long maxAgeMillis;
+
+ /**
+ * Flag set to true to capture stackTraces (can be expensive).
+ */
+ private boolean captureStackTrace;
+
+ /**
+ * The max size of the stack trace to report.
+ */
+ private int maxStackTraceSize;
+
+ /**
+ * flag to indicate we have sent an alert message.
+ */
+ private boolean dataSourceDownAlertSent;
+
+ /**
+ * The time the pool was last trimmed.
+ */
+ private long lastTrimTime;
+
+ /**
+ * Assume that the DataSource is up. heartBeat checking will discover when
+ * it goes down, and comes back up again.
+ */
+ private boolean dataSourceUp = true;
+
+ /**
+ * The current alert.
+ */
+ private boolean inWarningMode;
+
+ /**
+ * The minimum number of connections this pool will maintain.
+ */
+ private int minConnections;
+
+ /**
+ * The maximum number of connections this pool will grow to.
+ */
+ private int maxConnections;
+
+ /**
+ * The number of connections to exceed before a warning Alert is fired.
+ */
+ private int warningSize;
+
+ /**
+ * The time a thread will wait for a connection to become available.
+ */
+ private final int waitTimeoutMillis;
+
+ /**
+ * The size of the preparedStatement cache;
+ */
+ private int pstmtCacheSize;
+
+ private final PooledConnectionQueue queue;
+
+ /**
+ * Used to find and close() leaked connections. Leaked connections are
+ * thought to be busy but have not been used for some time. Each time a
+ * connection is used it sets it's lastUsedTime.
+ */
+ private long leakTimeMinutes;
+
+ private final Runnable heartbeatRunnable = new HeartBeatRunnable();
+
+ public DataSourcePool(DataSourceAlert notify, String name, DataSourceConfig params) {
+
+ this.notify = notify;
+ this.name = name;
+ this.poolListener = createPoolListener(params.getPoolListener());
+
+ this.autoCommit = params.isAutoCommit();
+ this.transactionIsolation = params.getIsolationLevel();
+
+ this.maxInactiveMillis = 1000 * params.getMaxInactiveTimeSecs();
+ this.maxAgeMillis = 60000 * params.getMaxAgeMinutes();
+ this.leakTimeMinutes = params.getLeakTimeMinutes();
+ this.captureStackTrace = params.isCaptureStackTrace();
+ this.maxStackTraceSize = params.getMaxStackTraceSize();
+ this.databaseDriver = params.getDriver();
+ this.databaseUrl = params.getUrl();
+ this.pstmtCacheSize = params.getPstmtCacheSize();
+
+ this.minConnections = params.getMinConnections();
+ this.maxConnections = params.getMaxConnections();
+ this.waitTimeoutMillis = params.getWaitTimeoutMillis();
+ this.heartbeatsql = params.getHeartbeatSql();
+ this.heartbeatFreqSecs = params.getHeartbeatFreqSecs();
+ this.heartbeatTimeoutSeconds = params.getHeartbeatTimeoutSeconds();
+ this.trimPoolFreqMillis = 1000 * params.getTrimPoolFreqSecs();
+
+ queue = new PooledConnectionQueue(this);
+
+ String un = params.getUsername();
+ String pw = params.getPassword();
+ if (un == null) {
+ throw new RuntimeException("DataSource user is null?");
+ }
+ if (pw == null) {
+ throw new RuntimeException("DataSource password is null?");
+ }
+ this.connectionProps = new Properties();
+ this.connectionProps.setProperty("user", un);
+ this.connectionProps.setProperty("password", pw);
+
+ Map customProperties = params.getCustomProperties();
+ if (customProperties != null){
+ Set> entrySet = customProperties.entrySet();
+ for (Entry entry : entrySet) {
+ this.connectionProps.setProperty(entry.getKey(), entry.getValue());
+ }
+ }
+
+ try {
+ initialise();
+ } catch (SQLException ex) {
+ throw new DataSourceException(ex);
+ }
+ }
+
+ class HeartBeatRunnable implements Runnable {
+ @Override
+ public void run() {
+ checkDataSource();
+ }
+ }
+
+
+ @Override
+ public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException {
+ throw new SQLFeatureNotSupportedException("We do not support java.util.logging");
+ }
+
+ /**
+ * Create the DataSourcePoolListener if there is one.
+ */
+ private DataSourcePoolListener createPoolListener(String cn) {
+ if (cn == null) {
+ return null;
+ }
+ try {
+ return (DataSourcePoolListener)ClassUtil.newInstance(cn, this.getClass());
+ } catch (Exception e) {
+ throw new DataSourceException(e);
+ }
+ }
+
+ private void initialise() throws SQLException {
+
+ // Ensure database driver is loaded
+ try {
+ ClassUtil.forName(this.databaseDriver, this.getClass());
+ } catch (Throwable e) {
+ throw new PersistenceException("Problem loading Database Driver [" + this.databaseDriver + "]: "
+ + e.getMessage(), e);
+ }
+
+ String transIsolation = TransactionIsolation.getLevelDescription(transactionIsolation);
+ StringBuilder sb = new StringBuilder();
+ sb.append("DataSourcePool [").append(name);
+ sb.append("] autoCommit[").append(autoCommit);
+ sb.append("] transIsolation[").append(transIsolation);
+ sb.append("] min[").append(minConnections);
+ sb.append("] max[").append(maxConnections).append("]");
+
+ logger.info(sb.toString());
+
+ queue.ensureMinimumConnections();
+ }
+
+ /**
+ * Returns false.
+ */
+ public boolean isWrapperFor(Class> arg0) throws SQLException {
+ return false;
+ }
+
+ /**
+ * Not Implemented.
+ */
+ public T unwrap(Class arg0) throws SQLException {
+ throw new SQLException("Not Implemented");
+ }
+
+ /**
+ * Return the dataSource name.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Return the max size of stack traces used when trying to find connection pool leaks.
+ *
+ * This is only used when {@link #isCaptureStackTrace()} is true.
+ *
+ */
+ public int getMaxStackTraceSize() {
+ return maxStackTraceSize;
+ }
+
+ /**
+ * Returns false when the dataSource is down.
+ */
+ public boolean isDataSourceUp() {
+ return dataSourceUp;
+ }
+
+ /**
+ * Called when the pool hits the warning level.
+ */
+ protected void notifyWarning(String msg) {
+
+ if (!inWarningMode) {
+ // send an Error to the event log...
+ inWarningMode = true;
+ logger.warn(msg);
+ if (notify != null) {
+ String subject = "DataSourcePool [" + name + "] warning";
+ notify.dataSourceWarning(subject, msg);
+ }
+ }
+ }
+
+ private void notifyDataSourceIsDown(SQLException ex) {
+
+ if (!dataSourceDownAlertSent) {
+ logger.error("FATAL: DataSourcePool [" + name + "] is down or has network error!!!", ex);
+ if (notify != null) {
+ notify.dataSourceDown(name);
+ }
+ dataSourceDownAlertSent = true;
+ }
+ if (dataSourceUp) {
+ reset();
+ }
+ dataSourceUp = false;
+ }
+
+ private void notifyDataSourceIsUp() {
+ if (dataSourceDownAlertSent) {
+ logger.error("RESOLVED FATAL: DataSourcePool [" + name + "] is back up!");
+ if (notify != null) {
+ notify.dataSourceUp(name);
+ }
+ dataSourceDownAlertSent = false;
+
+ } else if (!dataSourceUp) {
+ logger.info("DataSourcePool [" + name + "] is back up!");
+ }
+
+ if (!dataSourceUp) {
+ dataSourceUp = true;
+ reset();
+ }
+ }
+
+
+ /**
+ * Return the heartbeat frequency in seconds.
+ *
+ * This is the frequency that the heartbeat runnable should be run.
+ *
+ */
+ public int getHeartbeatFreqSecs() {
+ return heartbeatFreqSecs;
+ }
+
+ /**
+ * Returns the Runnable used to check the dataSource using a heartbeat query.
+ */
+ public Runnable getHeartbeatRunnable() {
+ return heartbeatRunnable;
+ }
+
+ /**
+ * Trim connections (in the free list) based on idle time and maximum age.
+ */
+ private void trimIdleConnections() {
+ if (System.currentTimeMillis() > (lastTrimTime + trimPoolFreqMillis)) {
+ try {
+ queue.trim(maxInactiveMillis, maxAgeMillis);
+ lastTrimTime = System.currentTimeMillis();
+ } catch (Exception e) {
+ logger.error("Error trying to trim idle connections", e);
+ }
+ }
+ }
+
+ /**
+ * Check the dataSource is up. Trim connections.
+ *
+ * This is called by the HeartbeatRunnable which should be scheduled to
+ * run periodically (every heartbeatFreqSecs seconds actually).
+ *
+ */
+ public void checkDataSource() {
+
+ // first trim idle connections
+ trimIdleConnections();
+
+ Connection conn = null;
+ try {
+ // Get a connection from the pool and test it
+ conn = getConnection();
+ if (testConnection(conn)) {
+ notifyDataSourceIsUp();
+
+ } else {
+ notifyDataSourceIsDown(null);
+ }
+
+ } catch (SQLException ex) {
+ notifyDataSourceIsDown(ex);
+
+ } finally {
+ try {
+ if (conn != null) {
+ conn.close();
+ }
+ } catch (SQLException ex) {
+ logger.warn("Can't close connection in checkDataSource!");
+ }
+ }
+ }
+
+ /**
+ * Create a Connection that will not be part of the connection pool.
+ *
+ *
+ * When this connection is closed it will not go back into the pool.
+ *
+ *
+ *
+ * If withDefaults is true then the Connection will have the autoCommit and
+ * transaction isolation set to the defaults for the pool.
+ *
+ */
+ public Connection createUnpooledConnection() throws SQLException {
+
+ try {
+ Connection conn = DriverManager.getConnection(databaseUrl, connectionProps);
+ conn.setAutoCommit(autoCommit);
+ conn.setTransactionIsolation(transactionIsolation);
+ return conn;
+
+ } catch (SQLException ex) {
+ notifyDataSourceIsDown(null);
+ throw ex;
+ }
+ }
+
+ /**
+ * Set a new maximum size. The pool should respect this new maximum
+ * immediately and not require a restart. You may want to increase the
+ * maxConnections if the pool gets large and hits the warning level.
+ */
+ public void setMaxSize(int max) {
+ queue.setMaxSize(max);
+ this.maxConnections = max;
+ }
+
+ /**
+ * Return the max size this pool can grow to.
+ */
+ public int getMaxSize() {
+ return maxConnections;
+ }
+
+ /**
+ * Set the min size this pool should maintain.
+ */
+ public void setMinSize(int min) {
+ queue.setMinSize(min);
+ this.minConnections = min;
+ }
+
+ /**
+ * Return the min size this pool should maintain.
+ */
+ public int getMinSize() {
+ return minConnections;
+ }
+
+ /**
+ * Set a new maximum size. The pool should respect this new maximum
+ * immediately and not require a restart. You may want to increase the
+ * maxConnections if the pool gets large and hits the warning and or alert
+ * levels.
+ */
+ public void setWarningSize(int warningSize) {
+ queue.setWarningSize(warningSize);
+ this.warningSize = warningSize;
+ }
+
+ /**
+ * Return the warning size. When the pool hits this size it can send a
+ * notify message to an administrator.
+ */
+ public int getWarningSize() {
+ return warningSize;
+ }
+
+ /**
+ * Return the time in millis that threads will wait when the pool has hit
+ * the max size. These threads wait for connections to be returned by the
+ * busy connections.
+ */
+ public int getWaitTimeoutMillis() {
+ return waitTimeoutMillis;
+ }
+
+ /**
+ * Return the time after which inactive connections are trimmed.
+ */
+ public int getMaxInactiveMillis() {
+ return maxInactiveMillis;
+ }
+
+ /**
+ * Return the maximum age a connection is allowed to be before it is trimmed
+ * out of the pool. This value can be 0 which means there is no maximum age.
+ */
+ public long getMaxAgeMillis() {
+ return maxAgeMillis;
+ }
+
+ private boolean testConnection(Connection conn) throws SQLException {
+
+ if (heartbeatsql == null) {
+ return conn.isValid(heartbeatTimeoutSeconds);
+ }
+ Statement stmt = null;
+ ResultSet rset = null;
+ try {
+ // It should only error IF the DataSource is down or a network issue
+ stmt = conn.createStatement();
+ if (heartbeatTimeoutSeconds > 0) {
+ stmt.setQueryTimeout(heartbeatTimeoutSeconds);
+ }
+ rset = stmt.executeQuery(heartbeatsql);
+ conn.commit();
+
+ return true;
+
+ } finally {
+ try {
+ if (rset != null) {
+ rset.close();
+ }
+ } catch (SQLException e) {
+ logger.error(null, e);
+ }
+ try {
+ if (stmt != null) {
+ stmt.close();
+ }
+ } catch (SQLException e) {
+ logger.error(null, e);
+ }
+ }
+ }
+
+ /**
+ * Make sure the connection is still ok to use. If not then remove it from
+ * the pool.
+ */
+ protected boolean validateConnection(PooledConnection conn) {
+ try {
+ return testConnection(conn);
+
+ } catch (Exception e) {
+ logger.warn("heartbeatsql test failed on connection[" + conn.getName() + "]");
+ return false;
+ }
+ }
+
+ /**
+ * Called by the PooledConnection themselves, returning themselves to the
+ * pool when they have been finished with.
+ *
+ * Note that connections may not be added back to the pool if returnToPool
+ * is false or if they where created before the recycleTime. In both of
+ * these cases the connection is fully closed and not pooled.
+ *
+ *
+ * @param pooledConnection
+ * the returning connection
+ *
+ */
+ protected void returnConnection(PooledConnection pooledConnection) {
+
+ // return a normal 'good' connection
+ returnTheConnection(pooledConnection, false);
+ }
+
+ /**
+ * This is a bad connection and must be removed from the pool's busy list and fully closed.
+ */
+ protected void returnConnectionForceClose(PooledConnection pooledConnection) {
+
+ returnTheConnection(pooledConnection, true);
+ }
+
+ /**
+ * Return connection. If forceClose is true then this is a bad connection that
+ * must be removed and closed fully.
+ */
+ private void returnTheConnection(PooledConnection pooledConnection, boolean forceClose) {
+
+ if (poolListener != null && !forceClose) {
+ poolListener.onBeforeReturnConnection(pooledConnection);
+ }
+ queue.returnPooledConnection(pooledConnection, forceClose);
+
+ if (forceClose) {
+ // Got a bad connection so check the pool
+ checkDataSource();
+ }
+ }
+
+ /**
+ * Collect statistics of a connection that is fully closing
+ */
+ protected void reportClosingConnection(PooledConnection pooledConnection) {
+
+ queue.reportClosingConnection(pooledConnection);
+ }
+
+ /**
+ * Returns information describing connections that are currently being used.
+ */
+ public String getBusyConnectionInformation() {
+
+ return queue.getBusyConnectionInformation();
+ }
+
+ /**
+ * Dumps the busy connection information to the logs.
+ *
+ * This includes the stackTrace elements if they are being captured. This is
+ * useful when needing to look a potential connection pool leaks.
+ *
+ */
+ public void dumpBusyConnectionInformation() {
+
+ queue.dumpBusyConnectionInformation();
+ }
+
+ /**
+ * Close any busy connections that have not been used for some time.
+ *
+ * These connections are considered to have leaked from the connection pool.
+ *
+ *
+ * Connection leaks occur when code doesn't ensure that connections are
+ * closed() after they have been finished with. There should be an
+ * appropriate try catch finally block to ensure connections are always
+ * closed and put back into the pool.
+ *
+ */
+ public void closeBusyConnections(long leakTimeMinutes) {
+
+ queue.closeBusyConnections(leakTimeMinutes);
+ }
+
+ /**
+ * Grow the pool by creating a new connection. The connection can either be
+ * added to the available list, or returned.
+ *
+ * This method is protected by synchronization in calling methods.
+ *
+ */
+ protected PooledConnection createConnectionForQueue(int connId) throws SQLException {
+
+ try {
+ Connection c = createUnpooledConnection();
+
+ PooledConnection pc = new PooledConnection(this, connId, c);
+ pc.resetForUse();
+
+ if (!dataSourceUp) {
+ notifyDataSourceIsUp();
+ }
+ return pc;
+
+ } catch (SQLException ex) {
+ notifyDataSourceIsDown(ex);
+ throw ex;
+ }
+ }
+
+ /**
+ * Close all the connections in the pool.
+ *
+ *
+ * - Checks that the database is up.
+ *
- Resets the Alert level.
+ *
- Closes busy connections that have not been used for some time (aka
+ * leaks).
+ *
- This closes all the currently available connections.
+ *
- Busy connections are closed when they are returned to the pool.
+ *
+ *
+ */
+ public void reset() {
+ queue.reset(leakTimeMinutes);
+ inWarningMode = false;
+ }
+
+ /**
+ * Return a pooled connection.
+ */
+ public Connection getConnection() throws SQLException {
+ return getPooledConnection();
+ }
+
+ /**
+ * Get a connection from the pool.
+ *
+ * This will grow the pool if all the current connections are busy. This
+ * will go into a wait if the pool has hit its maximum size.
+ *
+ */
+ public PooledConnection getPooledConnection() throws SQLException {
+
+ PooledConnection c = queue.getPooledConnection();
+
+ if (captureStackTrace) {
+ c.setStackTrace(Thread.currentThread().getStackTrace());
+ }
+
+ if (poolListener != null) {
+ poolListener.onAfterBorrowConnection(c);
+ }
+ return c;
+ }
+
+ /**
+ * Send a message to the DataSourceAlertListener to test it. This is so that
+ * you can make sure the alerter is configured correctly etc.
+ */
+ public void testAlert() {
+
+ String subject = "Test DataSourcePool [" + name + "]";
+ String msg = "Just testing if alert message is sent successfully.";
+
+ if (notify != null) {
+ notify.dataSourceWarning(subject, msg);
+ }
+ }
+
+ /**
+ * This will close all the free connections, and then go into a wait loop,
+ * waiting for the busy connections to be freed.
+ *
+ *
+ * The DataSources's should be shutdown AFTER thread pools. Leaked
+ * Connections are not waited on, as that would hang the server.
+ *
+ */
+ public void shutdown(boolean deregisterDriver) {
+ queue.shutdown();
+ if (deregisterDriver){
+ deregisterDriver();
+ }
+ }
+
+ /**
+ * Return the default autoCommit setting Connections in this pool will use.
+ *
+ * @return true if the pool defaults autoCommit to true
+ */
+ public boolean getAutoCommit() {
+ return autoCommit;
+ }
+
+ /**
+ * Return the default transaction isolation level connections in this pool
+ * should have.
+ *
+ * @return the default transaction isolation level
+ */
+ public int getTransactionIsolation() {
+ return transactionIsolation;
+ }
+
+ /**
+ * Return true if the connection pool is currently capturing the StackTrace
+ * when connections are 'got' from the pool.
+ *
+ * This is set to true to help diagnose connection pool leaks.
+ *
+ */
+ public boolean isCaptureStackTrace() {
+ return captureStackTrace;
+ }
+
+ /**
+ * Set this to true means that the StackElements are captured every time a
+ * connection is retrieved from the pool. This can be used to identify
+ * connection pool leaks.
+ */
+ public void setCaptureStackTrace(boolean captureStackTrace) {
+ this.captureStackTrace = captureStackTrace;
+ }
+
+ /**
+ * Not implemented and shouldn't be used.
+ */
+ public Connection getConnection(String username, String password) throws SQLException {
+ throw new SQLException("Method not supported");
+ }
+
+ /**
+ * Not implemented and shouldn't be used.
+ */
+ public int getLoginTimeout() throws SQLException {
+ throw new SQLException("Method not supported");
+ }
+
+ /**
+ * Not implemented and shouldn't be used.
+ */
+ public void setLoginTimeout(int seconds) throws SQLException {
+ throw new SQLException("Method not supported");
+ }
+
+ /**
+ * Returns null.
+ */
+ public PrintWriter getLogWriter() {
+ return null;
+ }
+
+ /**
+ * Not implemented.
+ */
+ public void setLogWriter(PrintWriter writer) throws SQLException {
+ throw new SQLException("Method not supported");
+ }
+
+ /**
+ * For detecting and closing leaked connections. Connections that have been
+ * busy for more than leakTimeMinutes are considered leaks and will be
+ * closed on a reset().
+ *
+ * If you want to use a connection for that longer then you should consider
+ * creating an unpooled connection or setting longRunning to true on that
+ * connection.
+ *
+ */
+ public void setLeakTimeMinutes(long leakTimeMinutes) {
+ this.leakTimeMinutes = leakTimeMinutes;
+ }
+
+ /**
+ * Return the number of minutes after which a busy connection could be
+ * considered leaked from the connection pool.
+ */
+ public long getLeakTimeMinutes() {
+ return leakTimeMinutes;
+ }
+
+ /**
+ * Return the preparedStatement cache size.
+ */
+ public int getPstmtCacheSize() {
+ return pstmtCacheSize;
+ }
+
+ /**
+ * Set the preparedStatement cache size.
+ */
+ public void setPstmtCacheSize(int pstmtCacheSize) {
+ this.pstmtCacheSize = pstmtCacheSize;
+ }
+
+ /**
+ * Return the current status of the connection pool.
+ *
+ * If you pass reset = true then the counters such as
+ * hitCount, waitCount and highWaterMark are reset.
+ *
+ */
+ public Status getStatus(boolean reset) {
+ return queue.getStatus(reset);
+ }
+
+ /**
+ * Return the aggregated load statistics collected on all the connections in the pool.
+ */
+ public DataSourcePoolStatistics getStatistics(boolean reset) {
+
+ return queue.getStatistics(reset);
+ }
+
+ /**
+ * Deregister the JDBC driver.
+ */
+ public void deregisterDriver() {
+ try {
+ logger.debug("Deregistered the JDBC driver "+this.databaseDriver);
+ DriverManager.deregisterDriver(DriverManager.getDriver(this.databaseUrl));
+ } catch (SQLException e) {
+ logger.warn("Error trying to deregister the JDBC driver "+this.databaseDriver, e);
+ }
+ }
+
+ public static class Status {
+
+ private final String name;
+ private final int minSize;
+ private final int maxSize;
+ private final int free;
+ private final int busy;
+ private final int waiting;
+ private final int highWaterMark;
+ private final int waitCount;
+ private final int hitCount;
+
+ protected Status(String name, int minSize, int maxSize, int free, int busy, int waiting, int highWaterMark,
+ int waitCount, int hitCount) {
+ this.name = name;
+ this.minSize = minSize;
+ this.maxSize = maxSize;
+ this.free = free;
+ this.busy = busy;
+ this.waiting = waiting;
+ this.highWaterMark = highWaterMark;
+ this.waitCount = waitCount;
+ this.hitCount = hitCount;
+ }
+
+ public String toString() {
+ return "min[" + minSize + "] max[" + maxSize + "] free[" + free + "] busy[" + busy + "] waiting[" + waiting
+ + "] highWaterMark[" + highWaterMark + "] waitCount[" + waitCount + "] hitCount[" + hitCount+"]";
+ }
+
+ /**
+ * Return the DataSource name.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Return the min pool size.
+ */
+ public int getMinSize() {
+ return minSize;
+ }
+
+ /**
+ * Return the max pool size.
+ */
+ public int getMaxSize() {
+ return maxSize;
+ }
+
+ /**
+ * Return the current number of free connections in the pool.
+ */
+ public int getFree() {
+ return free;
+ }
+
+ /**
+ * Return the current number of busy connections in the pool.
+ */
+ public int getBusy() {
+ return busy;
+ }
+
+ /**
+ * Return the current number of threads waiting for a connection.
+ */
+ public int getWaiting() {
+ return waiting;
+ }
+
+ /**
+ * Return the high water mark of busy connections.
+ */
+ public int getHighWaterMark() {
+ return highWaterMark;
+ }
+
+ /**
+ * Return the total number of times a thread had to wait.
+ */
+ public int getWaitCount() {
+ return waitCount;
+ }
+
+ /**
+ * Return the total number of times there was an attempt to get a
+ * connection.
+ *
+ * If the attempt to get a connection failed with a timeout or other
+ * exception those attempts are still included in this hit count.
+ *
+ */
+ public int getHitCount() {
+ return hitCount;
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/ExtendedPreparedStatement.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/ExtendedPreparedStatement.java
index 439b0731f..9e97996fc 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/ExtendedPreparedStatement.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/ExtendedPreparedStatement.java
@@ -1,389 +1,389 @@
-package com.avaje.ebeaninternal.server.lib.sql;
-
-import java.io.InputStream;
-import java.io.Reader;
-import java.math.BigDecimal;
-import java.net.URL;
-import java.sql.Array;
-import java.sql.Blob;
-import java.sql.Clob;
-import java.sql.Date;
-import java.sql.ParameterMetaData;
-import java.sql.PreparedStatement;
-import java.sql.Ref;
-import java.sql.ResultSet;
-import java.sql.ResultSetMetaData;
-import java.sql.SQLException;
-import java.sql.Time;
-import java.sql.Timestamp;
-import java.util.Calendar;
-
-/**
- * Extended PreparedStatement that supports caching.
- *
- * Designed so that it can be cached by the PooledConnection. It additionally
- * notes any Exceptions that occur and this is used to ensure bad connections
- * are removed from the connection pool.
- *
- */
-public class ExtendedPreparedStatement extends ExtendedStatement implements PreparedStatement {
-
- /**
- * The SQL used to create the underlying PreparedStatement.
- */
- private final String sql;
-
- /**
- * The key used to cache this in the connection.
- */
- private final String cacheKey;
-
- /**
- * Create a wrapped PreparedStatement that can be cached.
- */
- public ExtendedPreparedStatement(PooledConnection pooledConnection, PreparedStatement pstmt, String sql, String cacheKey) {
- super(pooledConnection, pstmt);
- this.sql = sql;
- this.cacheKey = cacheKey;
- }
-
- public PreparedStatement getDelegate() {
- return pstmt;
- }
-
- /**
- * Return the key used to cache this on the Connection.
- */
- public String getCacheKey() {
- return cacheKey;
- }
-
- /**
- * Return the SQL used to create this PreparedStatement.
- */
- public String getSql() {
- return sql;
- }
-
- /**
- * Fully close the underlying PreparedStatement. After this we can no longer
- * reuse the PreparedStatement.
- */
- public void closeDestroy() throws SQLException {
- pstmt.close();
- }
-
- /**
- * Returns the PreparedStatement back into the cache. This doesn't fully
- * close the underlying PreparedStatement.
- */
- public void close() throws SQLException {
- // return the connection back into the cache.
- pooledConnection.returnPreparedStatement(this);
- }
-
- /**
- * Add the last binding for batch execution.
- */
- public void addBatch() throws SQLException {
- try {
- pstmt.addBatch();
- } catch (SQLException e) {
- // we got an error... need to check this
- // connection before returning it
- pooledConnection.addError(e);
- throw e;
- }
- }
-
- /**
- * Clear parameters.
- */
- public void clearParameters() throws SQLException {
- try {
- pstmt.clearParameters();
- } catch (SQLException e) {
- // we got an error... need to check
- // this connection before returning it
- pooledConnection.addError(e);
- throw e;
- }
- }
-
- /**
- * execute the statement.
- */
- public boolean execute() throws SQLException {
- try {
- return pstmt.execute();
- } catch (SQLException e) {
- // we got an error... need to check
- // this connection before returning it
- pooledConnection.addError(e);
- throw e;
- }
- }
-
- /**
- * Execute teh query.
- */
- public ResultSet executeQuery() throws SQLException {
- try {
- return pstmt.executeQuery();
- } catch (SQLException e) {
- // we got an error... need to check
- // this connection before returning it
- pooledConnection.addError(e);
- throw e;
- }
- }
-
- /**
- * Execute the dml statement.
- */
- public int executeUpdate() throws SQLException {
- try {
- return pstmt.executeUpdate();
- } catch (SQLException e) {
- // we got an error... need to check
- // this connection before returning it
- pooledConnection.addError(e);
- throw e;
- }
- }
-
- /**
- * Return the MetaData for the query.
- */
- public ResultSetMetaData getMetaData() throws SQLException {
- try {
- return pstmt.getMetaData();
- } catch (SQLException e) {
- // we got an error... need to check
- // this connection before returning it
- pooledConnection.addError(e);
- throw e;
- }
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public ParameterMetaData getParameterMetaData() throws SQLException {
- return pstmt.getParameterMetaData();
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setArray(int i, Array x) throws SQLException {
- pstmt.setArray(i, x);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {
- pstmt.setAsciiStream(parameterIndex, x, length);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
- pstmt.setBigDecimal(parameterIndex, x);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {
- pstmt.setBinaryStream(parameterIndex, x, length);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setBlob(int i, Blob x) throws SQLException {
- pstmt.setBlob(i, x);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setBoolean(int parameterIndex, boolean x) throws SQLException {
- pstmt.setBoolean(parameterIndex, x);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setByte(int parameterIndex, byte x) throws SQLException {
- pstmt.setByte(parameterIndex, x);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setBytes(int parameterIndex, byte[] x) throws SQLException {
- pstmt.setBytes(parameterIndex, x);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setCharacterStream(int parameterIndex, Reader reader, int length)
- throws SQLException {
- pstmt.setCharacterStream(parameterIndex, reader, length);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setClob(int i, Clob x) throws SQLException {
- pstmt.setClob(i, x);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setDate(int parameterIndex, Date x) throws SQLException {
- pstmt.setDate(parameterIndex, x);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
- pstmt.setDate(parameterIndex, x, cal);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setDouble(int parameterIndex, double x) throws SQLException {
- pstmt.setDouble(parameterIndex, x);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setFloat(int parameterIndex, float x) throws SQLException {
- pstmt.setFloat(parameterIndex, x);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setInt(int parameterIndex, int x) throws SQLException {
- pstmt.setInt(parameterIndex, x);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setLong(int parameterIndex, long x) throws SQLException {
- pstmt.setLong(parameterIndex, x);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setNull(int parameterIndex, int sqlType) throws SQLException {
- pstmt.setNull(parameterIndex, sqlType);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setNull(int paramIndex, int sqlType, String typeName) throws SQLException {
- pstmt.setNull(paramIndex, sqlType, typeName);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setObject(int parameterIndex, Object x) throws SQLException {
- pstmt.setObject(parameterIndex, x);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
- pstmt.setObject(parameterIndex, x, targetSqlType);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setObject(int parameterIndex, Object x, int targetSqlType, int scale)
- throws SQLException {
- pstmt.setObject(parameterIndex, x, targetSqlType, scale);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setRef(int i, Ref x) throws SQLException {
- pstmt.setRef(i, x);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setShort(int parameterIndex, short x) throws SQLException {
- pstmt.setShort(parameterIndex, x);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setString(int parameterIndex, String x) throws SQLException {
- pstmt.setString(parameterIndex, x);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setTime(int parameterIndex, Time x) throws SQLException {
- pstmt.setTime(parameterIndex, x);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
- pstmt.setTime(parameterIndex, x, cal);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
- pstmt.setTimestamp(parameterIndex, x);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
- pstmt.setTimestamp(parameterIndex, x, cal);
- }
-
- /**
- * Standard PreparedStatement method execution.
- * @deprecated
- */
- public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
- pstmt.setUnicodeStream(parameterIndex, x, length);
- }
-
- /**
- * Standard PreparedStatement method execution.
- */
- public void setURL(int parameterIndex, URL x) throws SQLException {
- pstmt.setURL(parameterIndex, x);
- }
-
-}
+package com.avaje.ebeaninternal.server.lib.sql;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.Date;
+import java.sql.ParameterMetaData;
+import java.sql.PreparedStatement;
+import java.sql.Ref;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Calendar;
+
+/**
+ * Extended PreparedStatement that supports caching.
+ *
+ * Designed so that it can be cached by the PooledConnection. It additionally
+ * notes any Exceptions that occur and this is used to ensure bad connections
+ * are removed from the connection pool.
+ *
+ */
+public class ExtendedPreparedStatement extends ExtendedStatement implements PreparedStatement {
+
+ /**
+ * The SQL used to create the underlying PreparedStatement.
+ */
+ private final String sql;
+
+ /**
+ * The key used to cache this in the connection.
+ */
+ private final String cacheKey;
+
+ /**
+ * Create a wrapped PreparedStatement that can be cached.
+ */
+ public ExtendedPreparedStatement(PooledConnection pooledConnection, PreparedStatement pstmt, String sql, String cacheKey) {
+ super(pooledConnection, pstmt);
+ this.sql = sql;
+ this.cacheKey = cacheKey;
+ }
+
+ public PreparedStatement getDelegate() {
+ return pstmt;
+ }
+
+ /**
+ * Return the key used to cache this on the Connection.
+ */
+ public String getCacheKey() {
+ return cacheKey;
+ }
+
+ /**
+ * Return the SQL used to create this PreparedStatement.
+ */
+ public String getSql() {
+ return sql;
+ }
+
+ /**
+ * Fully close the underlying PreparedStatement. After this we can no longer
+ * reuse the PreparedStatement.
+ */
+ public void closeDestroy() throws SQLException {
+ pstmt.close();
+ }
+
+ /**
+ * Returns the PreparedStatement back into the cache. This doesn't fully
+ * close the underlying PreparedStatement.
+ */
+ public void close() throws SQLException {
+ // return the connection back into the cache.
+ pooledConnection.returnPreparedStatement(this);
+ }
+
+ /**
+ * Add the last binding for batch execution.
+ */
+ public void addBatch() throws SQLException {
+ try {
+ pstmt.addBatch();
+ } catch (SQLException e) {
+ // we got an error... need to check this
+ // connection before returning it
+ pooledConnection.addError(e);
+ throw e;
+ }
+ }
+
+ /**
+ * Clear parameters.
+ */
+ public void clearParameters() throws SQLException {
+ try {
+ pstmt.clearParameters();
+ } catch (SQLException e) {
+ // we got an error... need to check
+ // this connection before returning it
+ pooledConnection.addError(e);
+ throw e;
+ }
+ }
+
+ /**
+ * execute the statement.
+ */
+ public boolean execute() throws SQLException {
+ try {
+ return pstmt.execute();
+ } catch (SQLException e) {
+ // we got an error... need to check
+ // this connection before returning it
+ pooledConnection.addError(e);
+ throw e;
+ }
+ }
+
+ /**
+ * Execute teh query.
+ */
+ public ResultSet executeQuery() throws SQLException {
+ try {
+ return pstmt.executeQuery();
+ } catch (SQLException e) {
+ // we got an error... need to check
+ // this connection before returning it
+ pooledConnection.addError(e);
+ throw e;
+ }
+ }
+
+ /**
+ * Execute the dml statement.
+ */
+ public int executeUpdate() throws SQLException {
+ try {
+ return pstmt.executeUpdate();
+ } catch (SQLException e) {
+ // we got an error... need to check
+ // this connection before returning it
+ pooledConnection.addError(e);
+ throw e;
+ }
+ }
+
+ /**
+ * Return the MetaData for the query.
+ */
+ public ResultSetMetaData getMetaData() throws SQLException {
+ try {
+ return pstmt.getMetaData();
+ } catch (SQLException e) {
+ // we got an error... need to check
+ // this connection before returning it
+ pooledConnection.addError(e);
+ throw e;
+ }
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public ParameterMetaData getParameterMetaData() throws SQLException {
+ return pstmt.getParameterMetaData();
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setArray(int i, Array x) throws SQLException {
+ pstmt.setArray(i, x);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {
+ pstmt.setAsciiStream(parameterIndex, x, length);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
+ pstmt.setBigDecimal(parameterIndex, x);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {
+ pstmt.setBinaryStream(parameterIndex, x, length);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setBlob(int i, Blob x) throws SQLException {
+ pstmt.setBlob(i, x);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setBoolean(int parameterIndex, boolean x) throws SQLException {
+ pstmt.setBoolean(parameterIndex, x);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setByte(int parameterIndex, byte x) throws SQLException {
+ pstmt.setByte(parameterIndex, x);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setBytes(int parameterIndex, byte[] x) throws SQLException {
+ pstmt.setBytes(parameterIndex, x);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setCharacterStream(int parameterIndex, Reader reader, int length)
+ throws SQLException {
+ pstmt.setCharacterStream(parameterIndex, reader, length);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setClob(int i, Clob x) throws SQLException {
+ pstmt.setClob(i, x);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setDate(int parameterIndex, Date x) throws SQLException {
+ pstmt.setDate(parameterIndex, x);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
+ pstmt.setDate(parameterIndex, x, cal);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setDouble(int parameterIndex, double x) throws SQLException {
+ pstmt.setDouble(parameterIndex, x);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setFloat(int parameterIndex, float x) throws SQLException {
+ pstmt.setFloat(parameterIndex, x);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setInt(int parameterIndex, int x) throws SQLException {
+ pstmt.setInt(parameterIndex, x);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setLong(int parameterIndex, long x) throws SQLException {
+ pstmt.setLong(parameterIndex, x);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setNull(int parameterIndex, int sqlType) throws SQLException {
+ pstmt.setNull(parameterIndex, sqlType);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setNull(int paramIndex, int sqlType, String typeName) throws SQLException {
+ pstmt.setNull(paramIndex, sqlType, typeName);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setObject(int parameterIndex, Object x) throws SQLException {
+ pstmt.setObject(parameterIndex, x);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
+ pstmt.setObject(parameterIndex, x, targetSqlType);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setObject(int parameterIndex, Object x, int targetSqlType, int scale)
+ throws SQLException {
+ pstmt.setObject(parameterIndex, x, targetSqlType, scale);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setRef(int i, Ref x) throws SQLException {
+ pstmt.setRef(i, x);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setShort(int parameterIndex, short x) throws SQLException {
+ pstmt.setShort(parameterIndex, x);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setString(int parameterIndex, String x) throws SQLException {
+ pstmt.setString(parameterIndex, x);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setTime(int parameterIndex, Time x) throws SQLException {
+ pstmt.setTime(parameterIndex, x);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
+ pstmt.setTime(parameterIndex, x, cal);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
+ pstmt.setTimestamp(parameterIndex, x);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
+ pstmt.setTimestamp(parameterIndex, x, cal);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ * @deprecated
+ */
+ public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
+ pstmt.setUnicodeStream(parameterIndex, x, length);
+ }
+
+ /**
+ * Standard PreparedStatement method execution.
+ */
+ public void setURL(int parameterIndex, URL x) throws SQLException {
+ pstmt.setURL(parameterIndex, x);
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/ExtendedStatement.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/ExtendedStatement.java
index 780cf7625..46fce0992 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/ExtendedStatement.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/ExtendedStatement.java
@@ -1,328 +1,328 @@
-package com.avaje.ebeaninternal.server.lib.sql;
-
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.SQLWarning;
-
-import com.avaje.ebeaninternal.jdbc.PreparedStatementDelegator;
-
-/**
- * Implements the Statement methods for ExtendedPreparedStatement.
- *
- * PreparedStatements should always be used and the intention is that there
- * should be no use of Statement at all. The implementation here is generally
- * for the case where someone uses the Statement api on an ExtendedPreparedStatement.
- *
- */
-public abstract class ExtendedStatement extends PreparedStatementDelegator
-{
-
- /**
- * The pooled connection this Statement belongs to.
- */
- protected final PooledConnection pooledConnection;
-
- /**
- * The underlying Statement that this object wraps.
- */
- protected final PreparedStatement pstmt;
-
- /**
- * Create the ExtendedStatement for a given pooledConnection.
- */
- public ExtendedStatement(PooledConnection pooledConnection, PreparedStatement pstmt) {
- super(pstmt);
-
- this.pooledConnection = pooledConnection;
- this.pstmt = pstmt;
- }
-
- /**
- * Put the statement back into the statement cache.
- */
- public abstract void close() throws SQLException;
-
- /**
- * Return the underlying connection.
- */
- public Connection getConnection() throws SQLException {
- try {
- return pstmt.getConnection();
- } catch (SQLException e) {
- pooledConnection.addError(e);
- throw e;
- }
- }
-
- /**
- * Add the sql for batch execution.
- */
- public void addBatch(String sql) throws SQLException {
- try {
- pooledConnection.setLastStatement(sql);
- pstmt.addBatch(sql);
- } catch (SQLException e) {
- pooledConnection.addError(e);
- throw e;
- }
- }
-
- /**
- * Execute the sql.
- */
- public boolean execute(String sql) throws SQLException {
- try {
- pooledConnection.setLastStatement(sql);
- return pstmt.execute(sql);
- } catch (SQLException e) {
- pooledConnection.addError(e);
- throw e;
- }
- }
-
- /**
- * Execute the query.
- */
- public ResultSet executeQuery(String sql) throws SQLException {
- try {
- pooledConnection.setLastStatement(sql);
- return pstmt.executeQuery(sql);
- } catch (SQLException e) {
- pooledConnection.addError(e);
- throw e;
- }
- }
-
- /**
- * Execute the dml sql.
- */
- public int executeUpdate(String sql) throws SQLException {
- try {
- pooledConnection.setLastStatement(sql);
- return pstmt.executeUpdate(sql);
- } catch (SQLException e) {
- pooledConnection.addError(e);
- throw e;
- }
- }
-
- /**
- * Standard Statement method call.
- */
- public int[] executeBatch() throws SQLException {
- return pstmt.executeBatch();
- }
-
- /**
- * Standard Statement method call.
- */
- public void cancel() throws SQLException {
- pstmt.cancel();
- }
-
- /**
- * Standard Statement method call.
- */
- public void clearBatch() throws SQLException {
- pstmt.clearBatch();
- }
-
- /**
- * Standard Statement method call.
- */
- public void clearWarnings() throws SQLException {
- pstmt.clearWarnings();
- }
-
- /**
- * Standard Statement method call.
- */
- public int getFetchDirection() throws SQLException {
- return pstmt.getFetchDirection();
- }
-
- /**
- * Standard Statement method call.
- */
- public int getFetchSize() throws SQLException {
- return pstmt.getFetchSize();
- }
-
- /**
- * Standard Statement method call.
- */
- public int getMaxFieldSize() throws SQLException {
- return pstmt.getMaxFieldSize();
- }
-
- /**
- * Standard Statement method call.
- */
- public int getMaxRows() throws SQLException {
- return pstmt.getMaxRows();
- }
-
- /**
- * Standard Statement method call.
- */
- public boolean getMoreResults() throws SQLException {
- return pstmt.getMoreResults();
- }
-
- /**
- * Standard Statement method call.
- */
- public int getQueryTimeout() throws SQLException {
- return pstmt.getQueryTimeout();
- }
-
- /**
- * Standard Statement method call.
- */
- public ResultSet getResultSet() throws SQLException {
- return pstmt.getResultSet();
- }
-
- /**
- * Standard Statement method call.
- */
- public int getResultSetConcurrency() throws SQLException {
- return pstmt.getResultSetConcurrency();
- }
-
- /**
- * Standard Statement method call.
- */
- public int getResultSetType() throws SQLException {
- return pstmt.getResultSetType();
- }
-
- /**
- * Standard Statement method call.
- */
- public int getUpdateCount() throws SQLException {
- return pstmt.getUpdateCount();
- }
-
- /**
- * Standard Statement method call.
- */
- public SQLWarning getWarnings() throws SQLException {
- return pstmt.getWarnings();
- }
-
- /**
- * Standard Statement method call.
- */
- public void setCursorName(String name) throws SQLException {
- pstmt.setCursorName(name);
- }
-
- /**
- * Standard Statement method call.
- */
- public void setEscapeProcessing(boolean enable) throws SQLException {
- pstmt.setEscapeProcessing(enable);
- }
-
- /**
- * Standard Statement method call.
- */
- public void setFetchDirection(int direction) throws SQLException {
- pstmt.setFetchDirection(direction);
- }
-
- /**
- * Standard Statement method call.
- */
- public void setFetchSize(int rows) throws SQLException {
- pstmt.setFetchSize(rows);
- }
-
- /**
- * Standard Statement method call.
- */
- public void setMaxFieldSize(int max) throws SQLException {
- pstmt.setMaxFieldSize(max);
- }
-
- /**
- * Standard Statement method call.
- */
- public void setMaxRows(int max) throws SQLException {
- pstmt.setMaxRows(max);
- }
-
- /**
- * Standard Statement method call.
- */
- public void setQueryTimeout(int seconds) throws SQLException {
- pstmt.setQueryTimeout(seconds);
- }
-
- /**
- * Standard Statement method call.
- */
- public boolean getMoreResults(int i) throws SQLException {
- return pstmt.getMoreResults(i);
- }
-
- /**
- * Standard Statement method call.
- */
- public ResultSet getGeneratedKeys() throws SQLException {
- return pstmt.getGeneratedKeys();
- }
-
- /**
- * Standard Statement method call.
- */
- public int executeUpdate(String s, int i) throws SQLException {
- return pstmt.executeUpdate(s, i);
- }
-
- /**
- * Standard Statement method call.
- */
- public int executeUpdate(String s, int[] i) throws SQLException {
- return pstmt.executeUpdate(s, i);
- }
-
- /**
- * Standard Statement method call.
- */
- public int executeUpdate(String s, String[] i) throws SQLException {
- return pstmt.executeUpdate(s, i);
- }
-
- /**
- * Standard Statement method call.
- */
- public boolean execute(String s, int i) throws SQLException {
- return pstmt.execute(s, i);
- }
-
- /**
- * Standard Statement method call.
- */
- public boolean execute(String s, int[] i) throws SQLException {
- return pstmt.execute(s, i);
- }
-
- /**
- * Standard Statement method call.
- */
- public boolean execute(String s, String[] i) throws SQLException {
- return pstmt.execute(s, i);
- }
-
- /**
- * Standard Statement method call.
- */
- public int getResultSetHoldability() throws SQLException {
- return pstmt.getResultSetHoldability();
- }
-
-}
+package com.avaje.ebeaninternal.server.lib.sql;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+
+import com.avaje.ebeaninternal.jdbc.PreparedStatementDelegator;
+
+/**
+ * Implements the Statement methods for ExtendedPreparedStatement.
+ *
+ * PreparedStatements should always be used and the intention is that there
+ * should be no use of Statement at all. The implementation here is generally
+ * for the case where someone uses the Statement api on an ExtendedPreparedStatement.
+ *
+ */
+public abstract class ExtendedStatement extends PreparedStatementDelegator
+{
+
+ /**
+ * The pooled connection this Statement belongs to.
+ */
+ protected final PooledConnection pooledConnection;
+
+ /**
+ * The underlying Statement that this object wraps.
+ */
+ protected final PreparedStatement pstmt;
+
+ /**
+ * Create the ExtendedStatement for a given pooledConnection.
+ */
+ public ExtendedStatement(PooledConnection pooledConnection, PreparedStatement pstmt) {
+ super(pstmt);
+
+ this.pooledConnection = pooledConnection;
+ this.pstmt = pstmt;
+ }
+
+ /**
+ * Put the statement back into the statement cache.
+ */
+ public abstract void close() throws SQLException;
+
+ /**
+ * Return the underlying connection.
+ */
+ public Connection getConnection() throws SQLException {
+ try {
+ return pstmt.getConnection();
+ } catch (SQLException e) {
+ pooledConnection.addError(e);
+ throw e;
+ }
+ }
+
+ /**
+ * Add the sql for batch execution.
+ */
+ public void addBatch(String sql) throws SQLException {
+ try {
+ pooledConnection.setLastStatement(sql);
+ pstmt.addBatch(sql);
+ } catch (SQLException e) {
+ pooledConnection.addError(e);
+ throw e;
+ }
+ }
+
+ /**
+ * Execute the sql.
+ */
+ public boolean execute(String sql) throws SQLException {
+ try {
+ pooledConnection.setLastStatement(sql);
+ return pstmt.execute(sql);
+ } catch (SQLException e) {
+ pooledConnection.addError(e);
+ throw e;
+ }
+ }
+
+ /**
+ * Execute the query.
+ */
+ public ResultSet executeQuery(String sql) throws SQLException {
+ try {
+ pooledConnection.setLastStatement(sql);
+ return pstmt.executeQuery(sql);
+ } catch (SQLException e) {
+ pooledConnection.addError(e);
+ throw e;
+ }
+ }
+
+ /**
+ * Execute the dml sql.
+ */
+ public int executeUpdate(String sql) throws SQLException {
+ try {
+ pooledConnection.setLastStatement(sql);
+ return pstmt.executeUpdate(sql);
+ } catch (SQLException e) {
+ pooledConnection.addError(e);
+ throw e;
+ }
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public int[] executeBatch() throws SQLException {
+ return pstmt.executeBatch();
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public void cancel() throws SQLException {
+ pstmt.cancel();
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public void clearBatch() throws SQLException {
+ pstmt.clearBatch();
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public void clearWarnings() throws SQLException {
+ pstmt.clearWarnings();
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public int getFetchDirection() throws SQLException {
+ return pstmt.getFetchDirection();
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public int getFetchSize() throws SQLException {
+ return pstmt.getFetchSize();
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public int getMaxFieldSize() throws SQLException {
+ return pstmt.getMaxFieldSize();
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public int getMaxRows() throws SQLException {
+ return pstmt.getMaxRows();
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public boolean getMoreResults() throws SQLException {
+ return pstmt.getMoreResults();
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public int getQueryTimeout() throws SQLException {
+ return pstmt.getQueryTimeout();
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public ResultSet getResultSet() throws SQLException {
+ return pstmt.getResultSet();
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public int getResultSetConcurrency() throws SQLException {
+ return pstmt.getResultSetConcurrency();
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public int getResultSetType() throws SQLException {
+ return pstmt.getResultSetType();
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public int getUpdateCount() throws SQLException {
+ return pstmt.getUpdateCount();
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public SQLWarning getWarnings() throws SQLException {
+ return pstmt.getWarnings();
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public void setCursorName(String name) throws SQLException {
+ pstmt.setCursorName(name);
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public void setEscapeProcessing(boolean enable) throws SQLException {
+ pstmt.setEscapeProcessing(enable);
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public void setFetchDirection(int direction) throws SQLException {
+ pstmt.setFetchDirection(direction);
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public void setFetchSize(int rows) throws SQLException {
+ pstmt.setFetchSize(rows);
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public void setMaxFieldSize(int max) throws SQLException {
+ pstmt.setMaxFieldSize(max);
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public void setMaxRows(int max) throws SQLException {
+ pstmt.setMaxRows(max);
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public void setQueryTimeout(int seconds) throws SQLException {
+ pstmt.setQueryTimeout(seconds);
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public boolean getMoreResults(int i) throws SQLException {
+ return pstmt.getMoreResults(i);
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public ResultSet getGeneratedKeys() throws SQLException {
+ return pstmt.getGeneratedKeys();
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public int executeUpdate(String s, int i) throws SQLException {
+ return pstmt.executeUpdate(s, i);
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public int executeUpdate(String s, int[] i) throws SQLException {
+ return pstmt.executeUpdate(s, i);
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public int executeUpdate(String s, String[] i) throws SQLException {
+ return pstmt.executeUpdate(s, i);
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public boolean execute(String s, int i) throws SQLException {
+ return pstmt.execute(s, i);
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public boolean execute(String s, int[] i) throws SQLException {
+ return pstmt.execute(s, i);
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public boolean execute(String s, String[] i) throws SQLException {
+ return pstmt.execute(s, i);
+ }
+
+ /**
+ * Standard Statement method call.
+ */
+ public int getResultSetHoldability() throws SQLException {
+ return pstmt.getResultSetHoldability();
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/FreeConnectionBuffer.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/FreeConnectionBuffer.java
index 8fede2fc2..778410909 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/FreeConnectionBuffer.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/FreeConnectionBuffer.java
@@ -1,106 +1,106 @@
-package com.avaje.ebeaninternal.server.lib.sql;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.avaje.ebeaninternal.server.lib.sql.PooledConnectionStatistics.LoadValues;
-
-/**
- * A buffer designed especially to hold free pooled connections.
- *
- * All thread safety controlled externally (by PooledConnectionQueue).
- *
- */
-class FreeConnectionBuffer {
-
- private static final Logger logger = LoggerFactory.getLogger(FreeConnectionBuffer.class);
-
- /**
- * Buffer oriented for add and remove.
- */
- private final LinkedList freeBuffer = new LinkedList();
-
- protected FreeConnectionBuffer() {
- }
-
- protected int size() {
- return freeBuffer.size();
- }
-
- protected boolean isEmpty() {
- return freeBuffer.isEmpty();
- }
-
- /**
- * Add connection to the free list.
- */
- protected void add(PooledConnection pc) {
- freeBuffer.addLast(pc);
- }
-
- /**
- * Remove a connection from the free list.
- */
- protected PooledConnection remove() {
- return freeBuffer.removeFirst();
- }
-
- /**
- * Close all connections in this buffer.
- */
- protected void closeAll(boolean logErrors) {
-
- // create a temporary list
- List tempList = new ArrayList(freeBuffer.size());
-
- // add all the connections into it
- for (PooledConnection c : freeBuffer) {
- tempList.add(c);
- }
-
- // clear the buffer (in case it takes some time to close these connections).
- freeBuffer.clear();
-
- logger.debug("... closing all {} connections from the free list with logErrors: {}", tempList.size(), logErrors);
- for (int i = 0; i < tempList.size(); i++) {
- PooledConnection pooledConnection = tempList.get(i);
- logger.debug("... closing {} of {} connections from the free list", i, tempList.size());
- pooledConnection.closeConnectionFully(logErrors);
- }
- }
-
- /**
- * Trim any inactive connections that have not been used since usedSince.
- */
- protected int trim(long usedSince, long createdSince) {
-
- int trimCount = 0;
-
- Iterator iterator = freeBuffer.iterator();
- while (iterator.hasNext()) {
- PooledConnection pooledConnection = iterator.next();
- if (pooledConnection.shouldTrim(usedSince, createdSince)) {
- iterator.remove();
- pooledConnection.closeConnectionFully(true);
- trimCount++;
- }
- }
-
- return trimCount;
- }
-
- /**
- * Collect the load statistics from all the free connections.
- */
- protected void collectStatistics(LoadValues values, boolean reset) {
-
- for (PooledConnection c : freeBuffer) {
- values.plus(c.getStatistics().getValues(reset));
- }
- }
-}
+package com.avaje.ebeaninternal.server.lib.sql;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.avaje.ebeaninternal.server.lib.sql.PooledConnectionStatistics.LoadValues;
+
+/**
+ * A buffer designed especially to hold free pooled connections.
+ *
+ * All thread safety controlled externally (by PooledConnectionQueue).
+ *
+ */
+class FreeConnectionBuffer {
+
+ private static final Logger logger = LoggerFactory.getLogger(FreeConnectionBuffer.class);
+
+ /**
+ * Buffer oriented for add and remove.
+ */
+ private final LinkedList freeBuffer = new LinkedList();
+
+ protected FreeConnectionBuffer() {
+ }
+
+ protected int size() {
+ return freeBuffer.size();
+ }
+
+ protected boolean isEmpty() {
+ return freeBuffer.isEmpty();
+ }
+
+ /**
+ * Add connection to the free list.
+ */
+ protected void add(PooledConnection pc) {
+ freeBuffer.addLast(pc);
+ }
+
+ /**
+ * Remove a connection from the free list.
+ */
+ protected PooledConnection remove() {
+ return freeBuffer.removeFirst();
+ }
+
+ /**
+ * Close all connections in this buffer.
+ */
+ protected void closeAll(boolean logErrors) {
+
+ // create a temporary list
+ List tempList = new ArrayList(freeBuffer.size());
+
+ // add all the connections into it
+ for (PooledConnection c : freeBuffer) {
+ tempList.add(c);
+ }
+
+ // clear the buffer (in case it takes some time to close these connections).
+ freeBuffer.clear();
+
+ logger.debug("... closing all {} connections from the free list with logErrors: {}", tempList.size(), logErrors);
+ for (int i = 0; i < tempList.size(); i++) {
+ PooledConnection pooledConnection = tempList.get(i);
+ logger.debug("... closing {} of {} connections from the free list", i, tempList.size());
+ pooledConnection.closeConnectionFully(logErrors);
+ }
+ }
+
+ /**
+ * Trim any inactive connections that have not been used since usedSince.
+ */
+ protected int trim(long usedSince, long createdSince) {
+
+ int trimCount = 0;
+
+ Iterator iterator = freeBuffer.iterator();
+ while (iterator.hasNext()) {
+ PooledConnection pooledConnection = iterator.next();
+ if (pooledConnection.shouldTrim(usedSince, createdSince)) {
+ iterator.remove();
+ pooledConnection.closeConnectionFully(true);
+ trimCount++;
+ }
+ }
+
+ return trimCount;
+ }
+
+ /**
+ * Collect the load statistics from all the free connections.
+ */
+ protected void collectStatistics(LoadValues values, boolean reset) {
+
+ for (PooledConnection c : freeBuffer) {
+ values.plus(c.getStatistics().getValues(reset));
+ }
+ }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PooledConnection.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PooledConnection.java
index 3053c56ba..93b09a68d 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PooledConnection.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PooledConnection.java
@@ -1,1012 +1,1012 @@
-package com.avaje.ebeaninternal.server.lib.sql;
-
-import java.sql.CallableStatement;
-import java.sql.Connection;
-import java.sql.DatabaseMetaData;
-import java.sql.PreparedStatement;
-import java.sql.SQLException;
-import java.sql.SQLWarning;
-import java.sql.Savepoint;
-import java.sql.Statement;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Map;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.avaje.ebeaninternal.jdbc.ConnectionDelegator;
-
-/**
- * Is a connection that belongs to a DataSourcePool.
- *
- *
- * It is designed to be part of DataSourcePool. Closing the connection puts it
- * back into the pool.
- *
- *
- *
- * It defaults autoCommit and Transaction Isolation to the defaults of the
- * DataSourcePool.
- *
- *
- *
- * It has caching of Statements and PreparedStatements. Remembers the last
- * statement that was executed. Keeps statistics on how long it is in use.
- *
- */
-public class PooledConnection extends ConnectionDelegator {
-
- private static final Logger logger = LoggerFactory.getLogger(PooledConnection.class);
-
- private static final String IDLE_CONNECTION_ACCESSED_ERROR = "Pooled Connection has been accessed whilst idle in the pool, via method: ";
-
- /**
- * Marker for when connection is closed due to exceeding the max allowed age.
- */
- private static final String REASON_MAXAGE = "maxAge";
-
- /**
- * Marker for when connection is closed due to exceeding the max inactive time.
- */
- private static final String REASON_IDLE = "idleTime";
-
- /**
- * Marker for when the connection is closed due to a reset.
- */
- private static final String REASON_RESET = "reset";
-
- /**
- * Set when connection is idle in the pool. In general when in the pool the
- * connection should not be modified.
- */
- private static final int STATUS_IDLE = 88;
-
- /**
- * Set when connection given to client.
- */
- private static final int STATUS_ACTIVE = 89;
-
- /**
- * Set when commit() or rollback() called.
- */
- private static final int STATUS_ENDED = 87;
-
- /**
- * Name used to identify the PooledConnection for logging.
- */
- private final String name;
-
- /**
- * The pool this connection belongs to.
- */
- private final DataSourcePool pool;
-
- /**
- * The underlying connection.
- */
- private final Connection connection;
-
- /**
- * The time this connection was created.
- */
- private final long creationTime;
-
- /**
- * Cache of the PreparedStatements
- */
- private final PstmtCache pstmtCache;
-
- private final Object pstmtMonitor = new Object();
-
- /**
- * Helper for statistics collection.
- */
- private final PooledConnectionStatistics stats = new PooledConnectionStatistics();
-
- /**
- * The status of the connection. IDLE, ACTIVE or ENDED.
- */
- private int status = STATUS_IDLE;
-
- /**
- * The reason for a connection closing.
- */
- private String closeReason;
-
- /**
- * Set this to true if the connection will be busy for a long time.
- *
- * This means it should skip the suspected connection pool leak checking.
- *
- */
- private boolean longRunning;
-
- /**
- * Flag to indicate that this connection had errors and should be checked to
- * make sure it is okay.
- */
- private boolean hadErrors;
-
- /**
- * The last start time. When the connection was given to a thread.
- */
- private long startUseTime;
-
- /**
- * The last end time of this connection. This is to calculate the usage
- * time.
- */
- private long lastUseTime;
-
- private long exeStartNanos;
-
- /**
- * The last statement executed by this connection.
- */
- private String lastStatement;
-
- /**
- * The non avaje method that created the connection.
- */
- private String createdByMethod;
-
- /**
- * Used to find connection pool leaks.
- */
- private StackTraceElement[] stackTrace;
-
- private int maxStackTrace;
-
- /**
- * Slot position in the BusyConnectionBuffer.
- */
- private int slotId;
-
- private boolean resetIsolationReadOnlyRequired;
-
-
- /**
- * Construct the connection that can refer back to the pool it belongs to.
- *
- * close() will return the connection back to the pool , while
- * closeDestroy() will close() the underlining connection properly.
- *
- */
- public PooledConnection(DataSourcePool pool, int uniqueId, Connection connection) throws SQLException {
- super(connection);
-
- this.pool = pool;
- this.connection = connection;
- this.name = pool.getName() + "." + uniqueId;
- this.pstmtCache = new PstmtCache(name, pool.getPstmtCacheSize());
- this.maxStackTrace = pool.getMaxStackTraceSize();
- this.creationTime = System.currentTimeMillis();
- this.lastUseTime = creationTime;
- }
-
- /**
- * For testing the pool without real connections.
- */
- protected PooledConnection(String name) {
- super(null);
- this.name = name;
- this.pool = null;
- this.connection = null;
- this.pstmtCache = null;
- this.maxStackTrace = 0;
- this.creationTime = System.currentTimeMillis();
- this.lastUseTime = creationTime;
- }
-
- /**
- * Return the slot position in the busy buffer.
- */
- public int getSlotId() {
- return slotId;
- }
-
- /**
- * Set the slot position in the busy buffer.
- */
- public void setSlotId(int slotId) {
- this.slotId = slotId;
- }
-
- /**
- * Return the DataSourcePool that this connection belongs to.
- */
- public DataSourcePool getDataSourcePool() {
- return pool;
- }
-
- /**
- * Return the time the connection was created.
- */
- public long getCreationTime() {
- return creationTime;
- }
-
- /**
- * Return a string to identify the connection.
- */
- public String getName() {
- return name;
- }
-
- public String getNameSlot() {
- return name+":"+slotId;
- }
-
- public String toString() {
- return getDescription();
- }
-
- public long getBusySeconds() {
- return (System.currentTimeMillis() - startUseTime)/1000;
- }
-
- public String getDescription() {
- return "name["+name+"] slot["+slotId+"] startTime["+getStartUseTime()+"] busySeconds["+getBusySeconds()+"] createdBy["+getCreatedByMethod()+"] stmt["+getLastStatement()+"]";
- }
-
- public String getFullDescription() {
- return "name["+name+"] slot["+slotId+"] startTime["+getStartUseTime()+"] busySeconds["+getBusySeconds()+"] stackTrace["+getStackTraceAsString()+"] stmt["+getLastStatement()+"]";
- }
-
- public String getPstmtStatistics() {
- return "name["+name+"] startTime["+getStartUseTime()+"] "+pstmtCache.getDescription();
- }
-
- public PooledConnectionStatistics getStatistics() {
- return stats;
- }
-
- /**
- * Return true if the connection should be treated as long running (skip connection pool leak check).
- */
- public boolean isLongRunning() {
- return longRunning;
- }
-
- /**
- * Set this to true if the connection is a long running connection and should skip the
- * 'suspected connection pool leak' checking.
- */
- public void setLongRunning(boolean longRunning) {
- this.longRunning = longRunning;
- }
-
- /**
- * Close the connection fully NOT putting in back into the pool.
- *
- * The logErrors parameter exists so that expected errors are not logged
- * such as when the database is known to be down.
- *
- *
- * @param logErrors
- * if false then don't log errors when closing
- */
- public void closeConnectionFully(boolean logErrors) {
-
- if (pool != null) {
- // allow collection of load statistics
- pool.reportClosingConnection(this);
- }
-
- if (logger.isDebugEnabled()) {
- logger.debug("Closing Connection[{}] slot[{}] reason[{}] stats: {} , pstmtStats: {} ", name, slotId, closeReason, stats.getValues(false), pstmtCache.getDescription());
- }
-
- try {
- if (connection.isClosed()) {
- // Typically the JDBC Driver has its own JVM shutdown hook and already
- // closed the connections in our DataSource pool so making this DEBUG level
- logger.debug("Closing Connection[{}] that is already closed?", name);
- return;
- }
- } catch (SQLException ex) {
- if (logErrors) {
- logger.error("Error checking if connection [" + getNameSlot() + "] is closed", ex);
- }
- }
-
- try {
- for (ExtendedPreparedStatement ps : pstmtCache.values()) {
- ps.closeDestroy();
- }
-
- } catch (SQLException ex) {
- if (logErrors) {
- logger.warn("Error when closing connection Statements", ex);
- }
- }
-
- try {
- connection.close();
- } catch (SQLException ex) {
- if (logErrors || logger.isDebugEnabled()) {
- logger.error("Error when fully closing connection [" + getNameSlot() + "]", ex);
- }
- }
- }
-
- /**
- * A Least Recently used cache of PreparedStatements.
- */
- public PstmtCache getPstmtCache() {
- return pstmtCache;
- }
-
- /**
- * Creates a wrapper ExtendedStatement so that I can get the executed sql. I
- * want to do this so that I can get the slowest query statments etc, and
- * log that information.
- */
- public Statement createStatement() throws SQLException {
- if (status == STATUS_IDLE) {
- throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "createStatement()");
- }
- try {
- return connection.createStatement();
- } catch (SQLException ex) {
- addError(ex);
- throw ex;
- }
- }
-
- public Statement createStatement(int resultSetType, int resultSetConcurreny) throws SQLException {
- if (status == STATUS_IDLE) {
- throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "createStatement()");
- }
- try {
- return connection.createStatement(resultSetType, resultSetConcurreny);
-
- } catch (SQLException ex) {
- addError(ex);
- throw ex;
- }
- }
-
- /**
- * Return a PreparedStatement back into the cache.
- */
- protected void returnPreparedStatement(ExtendedPreparedStatement pstmt) {
-
- synchronized (pstmtMonitor) {
- if (!pstmtCache.returnStatement(pstmt)) {
- try {
- // Already an entry in the cache with the exact same SQL...
- pstmt.closeDestroy();
-
- } catch (SQLException e) {
- logger.error("Error closing Pstmt", e);
- }
- }
- }
- }
-
- /**
- * This will try to use a cache of PreparedStatements.
- */
- public PreparedStatement prepareStatement(String sql, int returnKeysFlag) throws SQLException {
- String cacheKey = sql + returnKeysFlag;
- return prepareStatement(sql, true, returnKeysFlag, cacheKey);
- }
-
- /**
- * This will try to use a cache of PreparedStatements.
- */
- public PreparedStatement prepareStatement(String sql) throws SQLException {
- return prepareStatement(sql, false, 0, sql);
- }
-
- /**
- * This will try to use a cache of PreparedStatements.
- */
- private PreparedStatement prepareStatement(String sql, boolean useFlag, int flag, String cacheKey) throws SQLException {
-
- if (status == STATUS_IDLE) {
- throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareStatement()");
- }
- try {
- synchronized (pstmtMonitor) {
- lastStatement = sql;
-
- // try to get a matching cached PStmt from the cache.
- ExtendedPreparedStatement pstmt = pstmtCache.remove(cacheKey);
-
- if (pstmt != null) {
- return pstmt;
- }
-
- // create a new PreparedStatement
- PreparedStatement actualPstmt;
- if (useFlag) {
- actualPstmt = connection.prepareStatement(sql, flag);
- } else {
- actualPstmt = connection.prepareStatement(sql);
- }
- return new ExtendedPreparedStatement(this, actualPstmt, sql, cacheKey);
- }
-
- } catch (SQLException ex) {
- addError(ex);
- throw ex;
- }
- }
-
- public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurreny) throws SQLException {
-
- if (status == STATUS_IDLE) {
- throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareStatement()");
- }
- try {
- // no caching when creating PreparedStatements this way
- lastStatement = sql;
- return connection.prepareStatement(sql, resultSetType, resultSetConcurreny);
- } catch (SQLException ex) {
- addError(ex);
- throw ex;
- }
- }
-
- /**
- * Reset the connection for returning to the client. Resets the status,
- * startUseTime and hadErrors.
- */
- protected void resetForUse() {
- this.status = STATUS_ACTIVE;
- this.startUseTime = System.currentTimeMillis();
- this.exeStartNanos = System.nanoTime();
- this.createdByMethod = null;
- this.lastStatement = null;
- this.hadErrors = false;
- this.longRunning = false;
- }
-
- /**
- * When an error occurs during use add it the connection.
- *
- * Any PooledConnection that has an error is checked to make sure it works
- * before it is placed back into the connection pool.
- *
- */
- public void addError(Throwable e) {
- hadErrors = true;
- }
-
- /**
- * Returns true if the connect threw any errors during use.
- *
- * Connections with errors are testing to make sure they are still good
- * before putting them back into the pool.
- *
- */
- public boolean hadErrors() {
- return hadErrors;
- }
-
- /**
- * close the connection putting it back into the connection pool.
- *
- * Note that to ensure that the next transaction starts at the correct time
- * a commit() or rollback() should be called. If neither has occured at this
- * time then a rollback() is used (to end the transaction).
- *
- *
- * To close the connection fully use closeConnectionFully().
- *
- */
- public void close() throws SQLException {
- if (status == STATUS_IDLE) {
- throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "close()");
- }
-
- long durationNanos = System.nanoTime() - exeStartNanos;
- stats.add(durationNanos, hadErrors);
-
- if (hadErrors) {
- if (!pool.validateConnection(this)) {
- // the connection is BAD, remove it, close it and test the pool
- pool.returnConnectionForceClose(this);
- return;
- }
- }
-
- try {
- // reset the autoCommit back if client code changed it
- if (connection.getAutoCommit() != pool.getAutoCommit()) {
- connection.setAutoCommit(pool.getAutoCommit());
- }
- // Generally resetting Isolation level seems expensive.
- // Hence using resetIsolationReadOnlyRequired flag
- // performance reasons.
- if (resetIsolationReadOnlyRequired) {
- resetIsolationReadOnly();
- resetIsolationReadOnlyRequired = false;
- }
-
- // the connection is assumed GOOD so put it back in the pool
- lastUseTime = System.currentTimeMillis();
- // connection.clearWarnings();
- status = STATUS_IDLE;
- pool.returnConnection(this);
-
- } catch (Exception ex) {
- // the connection is BAD, remove it, close it and test the pool
- logger.warn("Error when trying to return connection to pool, closing fully.", ex);
- pool.returnConnectionForceClose(this);
- }
- }
-
- private void resetIsolationReadOnly() throws SQLException {
- // reset the transaction isolation if the client code changed it
- if (connection.getTransactionIsolation() != pool.getTransactionIsolation()) {
- connection.setTransactionIsolation(pool.getTransactionIsolation());
- }
- // reset readonly to false
- if (connection.isReadOnly()) {
- connection.setReadOnly(false);
- }
- }
-
- protected void finalize() throws Throwable {
- try {
- if (connection != null && !connection.isClosed()) {
- // connect leak?
- logger.warn("Closing Connection on finalize() - {}", getFullDescription());
- closeConnectionFully(false);
- }
- } catch (Exception e) {
- logger.error("Error when finalize is closing a connection? (unexpected)", e);
- }
- super.finalize();
- }
-
- /**
- * Return true if the connection is too old.
- */
- public boolean exceedsMaxAge(long maxAgeMillis) {
- if (maxAgeMillis > 0 && (creationTime < (System.currentTimeMillis() - maxAgeMillis))){
- this.closeReason = REASON_MAXAGE;
- return true;
- }
- return false;
- }
-
- public boolean shouldTrimOnReturn(long lastResetTime, long maxAgeMillis) {
- if (creationTime <= lastResetTime) {
- this.closeReason = REASON_RESET;
- return true;
- }
- if (exceedsMaxAge(maxAgeMillis)) {
- return true;
- }
- return false;
- }
-
- /**
- * Return true if the connection has been idle for too long or is too old.
- */
- public boolean shouldTrim(long usedSince, long createdSince) {
- if (lastUseTime < usedSince) {
- // been idle for too long so trim it
- this.closeReason = REASON_IDLE;
- return true;
- }
- if (createdSince > 0 && createdSince > creationTime) {
- // exceeds max age so trim it
- this.closeReason = REASON_MAXAGE;
- return true;
- }
- return false;
- }
-
- /**
- * Return the time the connection was passed to the client code.
- *
- * Used to detect busy connections that could be leaks.
- *
- */
- public long getStartUseTime() {
- return startUseTime;
- }
-
- /**
- * Returns the time the connection was last used.
- *
- * Used to close connections that have been idle for some time. Typically 5
- * minutes.
- *
- */
- public long getLastUsedTime() {
- return lastUseTime;
- }
-
- /**
- * Returns the last sql statement executed.
- */
- public String getLastStatement() {
- return lastStatement;
- }
-
- /**
- * Called by ExtendedStatement to trace the sql being executed.
- *
- * Note with addBatch() this will not really work.
- *
- */
- protected void setLastStatement(String lastStatement) {
- this.lastStatement = lastStatement;
- if (logger.isTraceEnabled()) {
- logger.trace(".setLastStatement[" + lastStatement + "]");
- }
- }
-
-
- /**
- * Also note the read only status needs to be reset when put back into the
- * pool.
- */
- public void setReadOnly(boolean readOnly) throws SQLException {
- // A bit loose not checking for STATUS_IDLE
- // if (status == STATUS_IDLE) {
- // throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR +
- // "setReadOnly()");
- // }
- resetIsolationReadOnlyRequired = true;
- connection.setReadOnly(readOnly);
- }
-
- /**
- * Also note the Isolation level needs to be reset when put back into the
- * pool.
- */
- public void setTransactionIsolation(int level) throws SQLException {
- if (status == STATUS_IDLE) {
- throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setTransactionIsolation()");
- }
- try {
- resetIsolationReadOnlyRequired = true;
- connection.setTransactionIsolation(level);
- } catch (SQLException ex) {
- addError(ex);
- throw ex;
- }
- }
-
- //
- //
- // Simple wrapper methods which pass a method call onto the acutal
- // connection object. These methods are safe-guarded to prevent use of
- // the methods whilst the connection is in the connection pool.
- //
- //
- public void clearWarnings() throws SQLException {
- if (status == STATUS_IDLE) {
- throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "clearWarnings()");
- }
- connection.clearWarnings();
- }
-
- public void commit() throws SQLException {
- if (status == STATUS_IDLE) {
- throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "commit()");
- }
- try {
- status = STATUS_ENDED;
- connection.commit();
- } catch (SQLException ex) {
- addError(ex);
- throw ex;
- }
- }
-
- public boolean getAutoCommit() throws SQLException {
- if (status == STATUS_IDLE) {
- throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getAutoCommit()");
- }
- return connection.getAutoCommit();
- }
-
- public String getCatalog() throws SQLException {
- if (status == STATUS_IDLE) {
- throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getCatalog()");
- }
- return connection.getCatalog();
- }
-
- public DatabaseMetaData getMetaData() throws SQLException {
- if (status == STATUS_IDLE) {
- throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getMetaData()");
- }
- return connection.getMetaData();
- }
-
- public int getTransactionIsolation() throws SQLException {
- if (status == STATUS_IDLE) {
- throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getTransactionIsolation()");
- }
- return connection.getTransactionIsolation();
- }
-
- public Map> getTypeMap() throws SQLException {
- if (status == STATUS_IDLE) {
- throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getTypeMap()");
- }
- return connection.getTypeMap();
- }
-
- public SQLWarning getWarnings() throws SQLException {
- if (status == STATUS_IDLE) {
- throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getWarnings()");
- }
- return connection.getWarnings();
- }
-
- public boolean isClosed() throws SQLException {
- if (status == STATUS_IDLE) {
- throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "isClosed()");
- }
- return connection.isClosed();
- }
-
- public boolean isReadOnly() throws SQLException {
- if (status == STATUS_IDLE) {
- throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "isReadOnly()");
- }
- return connection.isReadOnly();
- }
-
- public String nativeSQL(String sql) throws SQLException {
- if (status == STATUS_IDLE) {
- throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "nativeSQL()");
- }
- lastStatement = sql;
- return connection.nativeSQL(sql);
- }
-
- public CallableStatement prepareCall(String sql) throws SQLException {
- if (status == STATUS_IDLE) {
- throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareCall()");
- }
- lastStatement = sql;
- return connection.prepareCall(sql);
- }
-
- public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurreny) throws SQLException {
- if (status == STATUS_IDLE) {
- throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareCall()");
- }
- lastStatement = sql;
- return connection.prepareCall(sql, resultSetType, resultSetConcurreny);
- }
-
- public void rollback() throws SQLException {
- if (status == STATUS_IDLE) {
- throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "rollback()");
- }
- try {
- status = STATUS_ENDED;
- connection.rollback();
- } catch (SQLException ex) {
- addError(ex);
- throw ex;
- }
- }
-
- public void setAutoCommit(boolean autoCommit) throws SQLException {
- if (status == STATUS_IDLE) {
- throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setAutoCommit()");
- }
- try {
- connection.setAutoCommit(autoCommit);
- } catch (SQLException ex) {
- addError(ex);
- throw ex;
- }
- }
-
- public void setCatalog(String catalog) throws SQLException {
- if (status == STATUS_IDLE) {
- throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setCatalog()");
- }
- connection.setCatalog(catalog);
- }
-
- public void setTypeMap(Map> map) throws SQLException {
- if (status == STATUS_IDLE) {
- throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setTypeMap()");
- }
- connection.setTypeMap(map);
- }
-
- public Savepoint setSavepoint() throws SQLException {
- try {
- return connection.setSavepoint();
- } catch (SQLException ex) {
- addError(ex);
- throw ex;
- }
- }
-
- public Savepoint setSavepoint(String savepointName) throws SQLException {
- try {
- return connection.setSavepoint(savepointName);
- } catch (SQLException ex) {
- addError(ex);
- throw ex;
- }
- }
-
- public void rollback(Savepoint sp) throws SQLException {
- try {
- connection.rollback(sp);
- } catch (SQLException ex) {
- addError(ex);
- throw ex;
- }
- }
-
- public void releaseSavepoint(Savepoint sp) throws SQLException {
- try {
- connection.releaseSavepoint(sp);
- } catch (SQLException ex) {
- addError(ex);
- throw ex;
- }
- }
-
- public void setHoldability(int i) throws SQLException {
- try {
- connection.setHoldability(i);
- } catch (SQLException ex) {
- addError(ex);
- throw ex;
- }
- }
-
- public int getHoldability() throws SQLException {
- try {
- return connection.getHoldability();
- } catch (SQLException ex) {
- addError(ex);
- throw ex;
- }
- }
-
- public Statement createStatement(int i, int x, int y) throws SQLException {
- try {
- return connection.createStatement(i, x, y);
- } catch (SQLException ex) {
- addError(ex);
- throw ex;
- }
- }
-
- public PreparedStatement prepareStatement(String s, int i, int x, int y) throws SQLException {
- try {
- return connection.prepareStatement(s, i, x, y);
- } catch (SQLException ex) {
- addError(ex);
- throw ex;
- }
- }
-
- public PreparedStatement prepareStatement(String s, int[] i) throws SQLException {
- try {
- return connection.prepareStatement(s, i);
- } catch (SQLException ex) {
- addError(ex);
- throw ex;
- }
- }
-
- public PreparedStatement prepareStatement(String s, String[] s2) throws SQLException {
- try {
- return connection.prepareStatement(s, s2);
- } catch (SQLException ex) {
- addError(ex);
- throw ex;
- }
- }
-
- public CallableStatement prepareCall(String s, int i, int x, int y) throws SQLException {
- try {
- return connection.prepareCall(s, i, x, y);
- } catch (SQLException ex) {
- addError(ex);
- throw ex;
- }
- }
-
- /**
- * Returns the method that created the connection.
- *
- * Used to help finding connection pool leaks.
- *
- */
- public String getCreatedByMethod() {
- if (createdByMethod != null) {
- return createdByMethod;
- }
- if (stackTrace == null) {
- return null;
- }
-
- for (int j = 0; j < stackTrace.length; j++) {
- String methodLine = stackTrace[j].toString();
- if (skipElement(methodLine)) {
- // ignore these methods...
- } else {
- createdByMethod = methodLine;
- return createdByMethod;
- }
- }
-
- return null;
- }
-
- private boolean skipElement(String methodLine) {
- if (methodLine.startsWith("java.lang.")) {
- return true;
- } else if (methodLine.startsWith("java.util.")) {
- return true;
- } else if (methodLine.startsWith("com.avaje.ebeaninternal.server.query.CallableQuery.")) {
- // creating connection on future...
- return true;
- } else if (methodLine.startsWith("com.avaje.ebeaninternal.server.query.Callable")) {
- // it is a future task being executed...
- return false;
- } else if (methodLine.startsWith("com.avaje.ebeaninternal")) {
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Set the stack trace to help find connection pool leaks.
- */
- protected void setStackTrace(StackTraceElement[] stackTrace) {
- this.stackTrace = stackTrace;
- }
-
- /**
- * Return the stackTrace as a String for logging purposes.
- */
- public String getStackTraceAsString() {
- StackTraceElement[] stackTrace = getStackTrace();
- if (stackTrace == null){
- return "";
- }
- return Arrays.toString(stackTrace);
- }
-
- /**
- * Return the full stack trace that got the connection from the pool. You
- * could use this if getCreatedByMethod() doesn't work for you.
- */
- public StackTraceElement[] getStackTrace() {
-
- if (stackTrace == null) {
- return null;
- }
-
- // filter off the top of the stack that we are not interested in
- ArrayList filteredList = new ArrayList();
- boolean include = false;
- for (int i = 0; i < stackTrace.length; i++) {
- if (!include && !skipElement(stackTrace[i].toString())) {
- include = true;
- }
- if (include && filteredList.size() < maxStackTrace) {
- filteredList.add(stackTrace[i]);
- }
- }
- return filteredList.toArray(new StackTraceElement[filteredList.size()]);
-
- }
-
-}
+package com.avaje.ebeaninternal.server.lib.sql;
+
+import java.sql.CallableStatement;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.Savepoint;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.avaje.ebeaninternal.jdbc.ConnectionDelegator;
+
+/**
+ * Is a connection that belongs to a DataSourcePool.
+ *
+ *
+ * It is designed to be part of DataSourcePool. Closing the connection puts it
+ * back into the pool.
+ *
+ *
+ *
+ * It defaults autoCommit and Transaction Isolation to the defaults of the
+ * DataSourcePool.
+ *
+ *
+ *
+ * It has caching of Statements and PreparedStatements. Remembers the last
+ * statement that was executed. Keeps statistics on how long it is in use.
+ *
+ */
+public class PooledConnection extends ConnectionDelegator {
+
+ private static final Logger logger = LoggerFactory.getLogger(PooledConnection.class);
+
+ private static final String IDLE_CONNECTION_ACCESSED_ERROR = "Pooled Connection has been accessed whilst idle in the pool, via method: ";
+
+ /**
+ * Marker for when connection is closed due to exceeding the max allowed age.
+ */
+ private static final String REASON_MAXAGE = "maxAge";
+
+ /**
+ * Marker for when connection is closed due to exceeding the max inactive time.
+ */
+ private static final String REASON_IDLE = "idleTime";
+
+ /**
+ * Marker for when the connection is closed due to a reset.
+ */
+ private static final String REASON_RESET = "reset";
+
+ /**
+ * Set when connection is idle in the pool. In general when in the pool the
+ * connection should not be modified.
+ */
+ private static final int STATUS_IDLE = 88;
+
+ /**
+ * Set when connection given to client.
+ */
+ private static final int STATUS_ACTIVE = 89;
+
+ /**
+ * Set when commit() or rollback() called.
+ */
+ private static final int STATUS_ENDED = 87;
+
+ /**
+ * Name used to identify the PooledConnection for logging.
+ */
+ private final String name;
+
+ /**
+ * The pool this connection belongs to.
+ */
+ private final DataSourcePool pool;
+
+ /**
+ * The underlying connection.
+ */
+ private final Connection connection;
+
+ /**
+ * The time this connection was created.
+ */
+ private final long creationTime;
+
+ /**
+ * Cache of the PreparedStatements
+ */
+ private final PstmtCache pstmtCache;
+
+ private final Object pstmtMonitor = new Object();
+
+ /**
+ * Helper for statistics collection.
+ */
+ private final PooledConnectionStatistics stats = new PooledConnectionStatistics();
+
+ /**
+ * The status of the connection. IDLE, ACTIVE or ENDED.
+ */
+ private int status = STATUS_IDLE;
+
+ /**
+ * The reason for a connection closing.
+ */
+ private String closeReason;
+
+ /**
+ * Set this to true if the connection will be busy for a long time.
+ *
+ * This means it should skip the suspected connection pool leak checking.
+ *
+ */
+ private boolean longRunning;
+
+ /**
+ * Flag to indicate that this connection had errors and should be checked to
+ * make sure it is okay.
+ */
+ private boolean hadErrors;
+
+ /**
+ * The last start time. When the connection was given to a thread.
+ */
+ private long startUseTime;
+
+ /**
+ * The last end time of this connection. This is to calculate the usage
+ * time.
+ */
+ private long lastUseTime;
+
+ private long exeStartNanos;
+
+ /**
+ * The last statement executed by this connection.
+ */
+ private String lastStatement;
+
+ /**
+ * The non avaje method that created the connection.
+ */
+ private String createdByMethod;
+
+ /**
+ * Used to find connection pool leaks.
+ */
+ private StackTraceElement[] stackTrace;
+
+ private int maxStackTrace;
+
+ /**
+ * Slot position in the BusyConnectionBuffer.
+ */
+ private int slotId;
+
+ private boolean resetIsolationReadOnlyRequired;
+
+
+ /**
+ * Construct the connection that can refer back to the pool it belongs to.
+ *
+ * close() will return the connection back to the pool , while
+ * closeDestroy() will close() the underlining connection properly.
+ *
+ */
+ public PooledConnection(DataSourcePool pool, int uniqueId, Connection connection) throws SQLException {
+ super(connection);
+
+ this.pool = pool;
+ this.connection = connection;
+ this.name = pool.getName() + "." + uniqueId;
+ this.pstmtCache = new PstmtCache(name, pool.getPstmtCacheSize());
+ this.maxStackTrace = pool.getMaxStackTraceSize();
+ this.creationTime = System.currentTimeMillis();
+ this.lastUseTime = creationTime;
+ }
+
+ /**
+ * For testing the pool without real connections.
+ */
+ protected PooledConnection(String name) {
+ super(null);
+ this.name = name;
+ this.pool = null;
+ this.connection = null;
+ this.pstmtCache = null;
+ this.maxStackTrace = 0;
+ this.creationTime = System.currentTimeMillis();
+ this.lastUseTime = creationTime;
+ }
+
+ /**
+ * Return the slot position in the busy buffer.
+ */
+ public int getSlotId() {
+ return slotId;
+ }
+
+ /**
+ * Set the slot position in the busy buffer.
+ */
+ public void setSlotId(int slotId) {
+ this.slotId = slotId;
+ }
+
+ /**
+ * Return the DataSourcePool that this connection belongs to.
+ */
+ public DataSourcePool getDataSourcePool() {
+ return pool;
+ }
+
+ /**
+ * Return the time the connection was created.
+ */
+ public long getCreationTime() {
+ return creationTime;
+ }
+
+ /**
+ * Return a string to identify the connection.
+ */
+ public String getName() {
+ return name;
+ }
+
+ public String getNameSlot() {
+ return name+":"+slotId;
+ }
+
+ public String toString() {
+ return getDescription();
+ }
+
+ public long getBusySeconds() {
+ return (System.currentTimeMillis() - startUseTime)/1000;
+ }
+
+ public String getDescription() {
+ return "name["+name+"] slot["+slotId+"] startTime["+getStartUseTime()+"] busySeconds["+getBusySeconds()+"] createdBy["+getCreatedByMethod()+"] stmt["+getLastStatement()+"]";
+ }
+
+ public String getFullDescription() {
+ return "name["+name+"] slot["+slotId+"] startTime["+getStartUseTime()+"] busySeconds["+getBusySeconds()+"] stackTrace["+getStackTraceAsString()+"] stmt["+getLastStatement()+"]";
+ }
+
+ public String getPstmtStatistics() {
+ return "name["+name+"] startTime["+getStartUseTime()+"] "+pstmtCache.getDescription();
+ }
+
+ public PooledConnectionStatistics getStatistics() {
+ return stats;
+ }
+
+ /**
+ * Return true if the connection should be treated as long running (skip connection pool leak check).
+ */
+ public boolean isLongRunning() {
+ return longRunning;
+ }
+
+ /**
+ * Set this to true if the connection is a long running connection and should skip the
+ * 'suspected connection pool leak' checking.
+ */
+ public void setLongRunning(boolean longRunning) {
+ this.longRunning = longRunning;
+ }
+
+ /**
+ * Close the connection fully NOT putting in back into the pool.
+ *
+ * The logErrors parameter exists so that expected errors are not logged
+ * such as when the database is known to be down.
+ *
+ *
+ * @param logErrors
+ * if false then don't log errors when closing
+ */
+ public void closeConnectionFully(boolean logErrors) {
+
+ if (pool != null) {
+ // allow collection of load statistics
+ pool.reportClosingConnection(this);
+ }
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Closing Connection[{}] slot[{}] reason[{}] stats: {} , pstmtStats: {} ", name, slotId, closeReason, stats.getValues(false), pstmtCache.getDescription());
+ }
+
+ try {
+ if (connection.isClosed()) {
+ // Typically the JDBC Driver has its own JVM shutdown hook and already
+ // closed the connections in our DataSource pool so making this DEBUG level
+ logger.debug("Closing Connection[{}] that is already closed?", name);
+ return;
+ }
+ } catch (SQLException ex) {
+ if (logErrors) {
+ logger.error("Error checking if connection [" + getNameSlot() + "] is closed", ex);
+ }
+ }
+
+ try {
+ for (ExtendedPreparedStatement ps : pstmtCache.values()) {
+ ps.closeDestroy();
+ }
+
+ } catch (SQLException ex) {
+ if (logErrors) {
+ logger.warn("Error when closing connection Statements", ex);
+ }
+ }
+
+ try {
+ connection.close();
+ } catch (SQLException ex) {
+ if (logErrors || logger.isDebugEnabled()) {
+ logger.error("Error when fully closing connection [" + getNameSlot() + "]", ex);
+ }
+ }
+ }
+
+ /**
+ * A Least Recently used cache of PreparedStatements.
+ */
+ public PstmtCache getPstmtCache() {
+ return pstmtCache;
+ }
+
+ /**
+ * Creates a wrapper ExtendedStatement so that I can get the executed sql. I
+ * want to do this so that I can get the slowest query statments etc, and
+ * log that information.
+ */
+ public Statement createStatement() throws SQLException {
+ if (status == STATUS_IDLE) {
+ throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "createStatement()");
+ }
+ try {
+ return connection.createStatement();
+ } catch (SQLException ex) {
+ addError(ex);
+ throw ex;
+ }
+ }
+
+ public Statement createStatement(int resultSetType, int resultSetConcurreny) throws SQLException {
+ if (status == STATUS_IDLE) {
+ throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "createStatement()");
+ }
+ try {
+ return connection.createStatement(resultSetType, resultSetConcurreny);
+
+ } catch (SQLException ex) {
+ addError(ex);
+ throw ex;
+ }
+ }
+
+ /**
+ * Return a PreparedStatement back into the cache.
+ */
+ protected void returnPreparedStatement(ExtendedPreparedStatement pstmt) {
+
+ synchronized (pstmtMonitor) {
+ if (!pstmtCache.returnStatement(pstmt)) {
+ try {
+ // Already an entry in the cache with the exact same SQL...
+ pstmt.closeDestroy();
+
+ } catch (SQLException e) {
+ logger.error("Error closing Pstmt", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * This will try to use a cache of PreparedStatements.
+ */
+ public PreparedStatement prepareStatement(String sql, int returnKeysFlag) throws SQLException {
+ String cacheKey = sql + returnKeysFlag;
+ return prepareStatement(sql, true, returnKeysFlag, cacheKey);
+ }
+
+ /**
+ * This will try to use a cache of PreparedStatements.
+ */
+ public PreparedStatement prepareStatement(String sql) throws SQLException {
+ return prepareStatement(sql, false, 0, sql);
+ }
+
+ /**
+ * This will try to use a cache of PreparedStatements.
+ */
+ private PreparedStatement prepareStatement(String sql, boolean useFlag, int flag, String cacheKey) throws SQLException {
+
+ if (status == STATUS_IDLE) {
+ throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareStatement()");
+ }
+ try {
+ synchronized (pstmtMonitor) {
+ lastStatement = sql;
+
+ // try to get a matching cached PStmt from the cache.
+ ExtendedPreparedStatement pstmt = pstmtCache.remove(cacheKey);
+
+ if (pstmt != null) {
+ return pstmt;
+ }
+
+ // create a new PreparedStatement
+ PreparedStatement actualPstmt;
+ if (useFlag) {
+ actualPstmt = connection.prepareStatement(sql, flag);
+ } else {
+ actualPstmt = connection.prepareStatement(sql);
+ }
+ return new ExtendedPreparedStatement(this, actualPstmt, sql, cacheKey);
+ }
+
+ } catch (SQLException ex) {
+ addError(ex);
+ throw ex;
+ }
+ }
+
+ public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurreny) throws SQLException {
+
+ if (status == STATUS_IDLE) {
+ throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareStatement()");
+ }
+ try {
+ // no caching when creating PreparedStatements this way
+ lastStatement = sql;
+ return connection.prepareStatement(sql, resultSetType, resultSetConcurreny);
+ } catch (SQLException ex) {
+ addError(ex);
+ throw ex;
+ }
+ }
+
+ /**
+ * Reset the connection for returning to the client. Resets the status,
+ * startUseTime and hadErrors.
+ */
+ protected void resetForUse() {
+ this.status = STATUS_ACTIVE;
+ this.startUseTime = System.currentTimeMillis();
+ this.exeStartNanos = System.nanoTime();
+ this.createdByMethod = null;
+ this.lastStatement = null;
+ this.hadErrors = false;
+ this.longRunning = false;
+ }
+
+ /**
+ * When an error occurs during use add it the connection.
+ *
+ * Any PooledConnection that has an error is checked to make sure it works
+ * before it is placed back into the connection pool.
+ *
+ */
+ public void addError(Throwable e) {
+ hadErrors = true;
+ }
+
+ /**
+ * Returns true if the connect threw any errors during use.
+ *
+ * Connections with errors are testing to make sure they are still good
+ * before putting them back into the pool.
+ *
+ */
+ public boolean hadErrors() {
+ return hadErrors;
+ }
+
+ /**
+ * close the connection putting it back into the connection pool.
+ *
+ * Note that to ensure that the next transaction starts at the correct time
+ * a commit() or rollback() should be called. If neither has occured at this
+ * time then a rollback() is used (to end the transaction).
+ *
+ *
+ * To close the connection fully use closeConnectionFully().
+ *
+ */
+ public void close() throws SQLException {
+ if (status == STATUS_IDLE) {
+ throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "close()");
+ }
+
+ long durationNanos = System.nanoTime() - exeStartNanos;
+ stats.add(durationNanos, hadErrors);
+
+ if (hadErrors) {
+ if (!pool.validateConnection(this)) {
+ // the connection is BAD, remove it, close it and test the pool
+ pool.returnConnectionForceClose(this);
+ return;
+ }
+ }
+
+ try {
+ // reset the autoCommit back if client code changed it
+ if (connection.getAutoCommit() != pool.getAutoCommit()) {
+ connection.setAutoCommit(pool.getAutoCommit());
+ }
+ // Generally resetting Isolation level seems expensive.
+ // Hence using resetIsolationReadOnlyRequired flag
+ // performance reasons.
+ if (resetIsolationReadOnlyRequired) {
+ resetIsolationReadOnly();
+ resetIsolationReadOnlyRequired = false;
+ }
+
+ // the connection is assumed GOOD so put it back in the pool
+ lastUseTime = System.currentTimeMillis();
+ // connection.clearWarnings();
+ status = STATUS_IDLE;
+ pool.returnConnection(this);
+
+ } catch (Exception ex) {
+ // the connection is BAD, remove it, close it and test the pool
+ logger.warn("Error when trying to return connection to pool, closing fully.", ex);
+ pool.returnConnectionForceClose(this);
+ }
+ }
+
+ private void resetIsolationReadOnly() throws SQLException {
+ // reset the transaction isolation if the client code changed it
+ if (connection.getTransactionIsolation() != pool.getTransactionIsolation()) {
+ connection.setTransactionIsolation(pool.getTransactionIsolation());
+ }
+ // reset readonly to false
+ if (connection.isReadOnly()) {
+ connection.setReadOnly(false);
+ }
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ if (connection != null && !connection.isClosed()) {
+ // connect leak?
+ logger.warn("Closing Connection on finalize() - {}", getFullDescription());
+ closeConnectionFully(false);
+ }
+ } catch (Exception e) {
+ logger.error("Error when finalize is closing a connection? (unexpected)", e);
+ }
+ super.finalize();
+ }
+
+ /**
+ * Return true if the connection is too old.
+ */
+ public boolean exceedsMaxAge(long maxAgeMillis) {
+ if (maxAgeMillis > 0 && (creationTime < (System.currentTimeMillis() - maxAgeMillis))){
+ this.closeReason = REASON_MAXAGE;
+ return true;
+ }
+ return false;
+ }
+
+ public boolean shouldTrimOnReturn(long lastResetTime, long maxAgeMillis) {
+ if (creationTime <= lastResetTime) {
+ this.closeReason = REASON_RESET;
+ return true;
+ }
+ if (exceedsMaxAge(maxAgeMillis)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Return true if the connection has been idle for too long or is too old.
+ */
+ public boolean shouldTrim(long usedSince, long createdSince) {
+ if (lastUseTime < usedSince) {
+ // been idle for too long so trim it
+ this.closeReason = REASON_IDLE;
+ return true;
+ }
+ if (createdSince > 0 && createdSince > creationTime) {
+ // exceeds max age so trim it
+ this.closeReason = REASON_MAXAGE;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Return the time the connection was passed to the client code.
+ *
+ * Used to detect busy connections that could be leaks.
+ *
+ */
+ public long getStartUseTime() {
+ return startUseTime;
+ }
+
+ /**
+ * Returns the time the connection was last used.
+ *
+ * Used to close connections that have been idle for some time. Typically 5
+ * minutes.
+ *
+ */
+ public long getLastUsedTime() {
+ return lastUseTime;
+ }
+
+ /**
+ * Returns the last sql statement executed.
+ */
+ public String getLastStatement() {
+ return lastStatement;
+ }
+
+ /**
+ * Called by ExtendedStatement to trace the sql being executed.
+ *
+ * Note with addBatch() this will not really work.
+ *
+ */
+ protected void setLastStatement(String lastStatement) {
+ this.lastStatement = lastStatement;
+ if (logger.isTraceEnabled()) {
+ logger.trace(".setLastStatement[" + lastStatement + "]");
+ }
+ }
+
+
+ /**
+ * Also note the read only status needs to be reset when put back into the
+ * pool.
+ */
+ public void setReadOnly(boolean readOnly) throws SQLException {
+ // A bit loose not checking for STATUS_IDLE
+ // if (status == STATUS_IDLE) {
+ // throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR +
+ // "setReadOnly()");
+ // }
+ resetIsolationReadOnlyRequired = true;
+ connection.setReadOnly(readOnly);
+ }
+
+ /**
+ * Also note the Isolation level needs to be reset when put back into the
+ * pool.
+ */
+ public void setTransactionIsolation(int level) throws SQLException {
+ if (status == STATUS_IDLE) {
+ throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setTransactionIsolation()");
+ }
+ try {
+ resetIsolationReadOnlyRequired = true;
+ connection.setTransactionIsolation(level);
+ } catch (SQLException ex) {
+ addError(ex);
+ throw ex;
+ }
+ }
+
+ //
+ //
+ // Simple wrapper methods which pass a method call onto the acutal
+ // connection object. These methods are safe-guarded to prevent use of
+ // the methods whilst the connection is in the connection pool.
+ //
+ //
+ public void clearWarnings() throws SQLException {
+ if (status == STATUS_IDLE) {
+ throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "clearWarnings()");
+ }
+ connection.clearWarnings();
+ }
+
+ public void commit() throws SQLException {
+ if (status == STATUS_IDLE) {
+ throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "commit()");
+ }
+ try {
+ status = STATUS_ENDED;
+ connection.commit();
+ } catch (SQLException ex) {
+ addError(ex);
+ throw ex;
+ }
+ }
+
+ public boolean getAutoCommit() throws SQLException {
+ if (status == STATUS_IDLE) {
+ throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getAutoCommit()");
+ }
+ return connection.getAutoCommit();
+ }
+
+ public String getCatalog() throws SQLException {
+ if (status == STATUS_IDLE) {
+ throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getCatalog()");
+ }
+ return connection.getCatalog();
+ }
+
+ public DatabaseMetaData getMetaData() throws SQLException {
+ if (status == STATUS_IDLE) {
+ throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getMetaData()");
+ }
+ return connection.getMetaData();
+ }
+
+ public int getTransactionIsolation() throws SQLException {
+ if (status == STATUS_IDLE) {
+ throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getTransactionIsolation()");
+ }
+ return connection.getTransactionIsolation();
+ }
+
+ public Map> getTypeMap() throws SQLException {
+ if (status == STATUS_IDLE) {
+ throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getTypeMap()");
+ }
+ return connection.getTypeMap();
+ }
+
+ public SQLWarning getWarnings() throws SQLException {
+ if (status == STATUS_IDLE) {
+ throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getWarnings()");
+ }
+ return connection.getWarnings();
+ }
+
+ public boolean isClosed() throws SQLException {
+ if (status == STATUS_IDLE) {
+ throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "isClosed()");
+ }
+ return connection.isClosed();
+ }
+
+ public boolean isReadOnly() throws SQLException {
+ if (status == STATUS_IDLE) {
+ throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "isReadOnly()");
+ }
+ return connection.isReadOnly();
+ }
+
+ public String nativeSQL(String sql) throws SQLException {
+ if (status == STATUS_IDLE) {
+ throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "nativeSQL()");
+ }
+ lastStatement = sql;
+ return connection.nativeSQL(sql);
+ }
+
+ public CallableStatement prepareCall(String sql) throws SQLException {
+ if (status == STATUS_IDLE) {
+ throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareCall()");
+ }
+ lastStatement = sql;
+ return connection.prepareCall(sql);
+ }
+
+ public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurreny) throws SQLException {
+ if (status == STATUS_IDLE) {
+ throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareCall()");
+ }
+ lastStatement = sql;
+ return connection.prepareCall(sql, resultSetType, resultSetConcurreny);
+ }
+
+ public void rollback() throws SQLException {
+ if (status == STATUS_IDLE) {
+ throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "rollback()");
+ }
+ try {
+ status = STATUS_ENDED;
+ connection.rollback();
+ } catch (SQLException ex) {
+ addError(ex);
+ throw ex;
+ }
+ }
+
+ public void setAutoCommit(boolean autoCommit) throws SQLException {
+ if (status == STATUS_IDLE) {
+ throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setAutoCommit()");
+ }
+ try {
+ connection.setAutoCommit(autoCommit);
+ } catch (SQLException ex) {
+ addError(ex);
+ throw ex;
+ }
+ }
+
+ public void setCatalog(String catalog) throws SQLException {
+ if (status == STATUS_IDLE) {
+ throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setCatalog()");
+ }
+ connection.setCatalog(catalog);
+ }
+
+ public void setTypeMap(Map> map) throws SQLException {
+ if (status == STATUS_IDLE) {
+ throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setTypeMap()");
+ }
+ connection.setTypeMap(map);
+ }
+
+ public Savepoint setSavepoint() throws SQLException {
+ try {
+ return connection.setSavepoint();
+ } catch (SQLException ex) {
+ addError(ex);
+ throw ex;
+ }
+ }
+
+ public Savepoint setSavepoint(String savepointName) throws SQLException {
+ try {
+ return connection.setSavepoint(savepointName);
+ } catch (SQLException ex) {
+ addError(ex);
+ throw ex;
+ }
+ }
+
+ public void rollback(Savepoint sp) throws SQLException {
+ try {
+ connection.rollback(sp);
+ } catch (SQLException ex) {
+ addError(ex);
+ throw ex;
+ }
+ }
+
+ public void releaseSavepoint(Savepoint sp) throws SQLException {
+ try {
+ connection.releaseSavepoint(sp);
+ } catch (SQLException ex) {
+ addError(ex);
+ throw ex;
+ }
+ }
+
+ public void setHoldability(int i) throws SQLException {
+ try {
+ connection.setHoldability(i);
+ } catch (SQLException ex) {
+ addError(ex);
+ throw ex;
+ }
+ }
+
+ public int getHoldability() throws SQLException {
+ try {
+ return connection.getHoldability();
+ } catch (SQLException ex) {
+ addError(ex);
+ throw ex;
+ }
+ }
+
+ public Statement createStatement(int i, int x, int y) throws SQLException {
+ try {
+ return connection.createStatement(i, x, y);
+ } catch (SQLException ex) {
+ addError(ex);
+ throw ex;
+ }
+ }
+
+ public PreparedStatement prepareStatement(String s, int i, int x, int y) throws SQLException {
+ try {
+ return connection.prepareStatement(s, i, x, y);
+ } catch (SQLException ex) {
+ addError(ex);
+ throw ex;
+ }
+ }
+
+ public PreparedStatement prepareStatement(String s, int[] i) throws SQLException {
+ try {
+ return connection.prepareStatement(s, i);
+ } catch (SQLException ex) {
+ addError(ex);
+ throw ex;
+ }
+ }
+
+ public PreparedStatement prepareStatement(String s, String[] s2) throws SQLException {
+ try {
+ return connection.prepareStatement(s, s2);
+ } catch (SQLException ex) {
+ addError(ex);
+ throw ex;
+ }
+ }
+
+ public CallableStatement prepareCall(String s, int i, int x, int y) throws SQLException {
+ try {
+ return connection.prepareCall(s, i, x, y);
+ } catch (SQLException ex) {
+ addError(ex);
+ throw ex;
+ }
+ }
+
+ /**
+ * Returns the method that created the connection.
+ *
+ * Used to help finding connection pool leaks.
+ *
+ */
+ public String getCreatedByMethod() {
+ if (createdByMethod != null) {
+ return createdByMethod;
+ }
+ if (stackTrace == null) {
+ return null;
+ }
+
+ for (int j = 0; j < stackTrace.length; j++) {
+ String methodLine = stackTrace[j].toString();
+ if (skipElement(methodLine)) {
+ // ignore these methods...
+ } else {
+ createdByMethod = methodLine;
+ return createdByMethod;
+ }
+ }
+
+ return null;
+ }
+
+ private boolean skipElement(String methodLine) {
+ if (methodLine.startsWith("java.lang.")) {
+ return true;
+ } else if (methodLine.startsWith("java.util.")) {
+ return true;
+ } else if (methodLine.startsWith("com.avaje.ebeaninternal.server.query.CallableQuery.")) {
+ // creating connection on future...
+ return true;
+ } else if (methodLine.startsWith("com.avaje.ebeaninternal.server.query.Callable")) {
+ // it is a future task being executed...
+ return false;
+ } else if (methodLine.startsWith("com.avaje.ebeaninternal")) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Set the stack trace to help find connection pool leaks.
+ */
+ protected void setStackTrace(StackTraceElement[] stackTrace) {
+ this.stackTrace = stackTrace;
+ }
+
+ /**
+ * Return the stackTrace as a String for logging purposes.
+ */
+ public String getStackTraceAsString() {
+ StackTraceElement[] stackTrace = getStackTrace();
+ if (stackTrace == null){
+ return "";
+ }
+ return Arrays.toString(stackTrace);
+ }
+
+ /**
+ * Return the full stack trace that got the connection from the pool. You
+ * could use this if getCreatedByMethod() doesn't work for you.
+ */
+ public StackTraceElement[] getStackTrace() {
+
+ if (stackTrace == null) {
+ return null;
+ }
+
+ // filter off the top of the stack that we are not interested in
+ ArrayList filteredList = new ArrayList();
+ boolean include = false;
+ for (int i = 0; i < stackTrace.length; i++) {
+ if (!include && !skipElement(stackTrace[i].toString())) {
+ include = true;
+ }
+ if (include && filteredList.size() < maxStackTrace) {
+ filteredList.add(stackTrace[i]);
+ }
+ }
+ return filteredList.toArray(new StackTraceElement[filteredList.size()]);
+
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PooledConnectionQueue.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PooledConnectionQueue.java
index 63713ba68..89088faff 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PooledConnectionQueue.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PooledConnectionQueue.java
@@ -1,532 +1,532 @@
-package com.avaje.ebeaninternal.server.lib.sql;
-
-import java.sql.SQLException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.ReentrantLock;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.avaje.ebeaninternal.server.lib.sql.DataSourcePool.Status;
-import com.avaje.ebeaninternal.server.lib.sql.PooledConnectionStatistics.LoadValues;
-
-public class PooledConnectionQueue {
-
- private static final Logger logger = LoggerFactory.getLogger(PooledConnectionQueue.class);
-
- private static final TimeUnit MILLIS_TIME_UNIT = TimeUnit.MILLISECONDS;
-
- private final String name;
-
- private final DataSourcePool pool;
-
- /**
- * A 'circular' buffer designed specifically for free connections.
- */
- private final FreeConnectionBuffer freeList;
-
- /**
- * A 'slots' buffer designed specifically for busy connections.
- * Fast add remove based on slot id.
- */
- private final BusyConnectionBuffer busyList;
-
- /**
- * Load statistics collected off connections that have closed fully (left the pool).
- */
- private final PooledConnectionStatistics collectedStats = new PooledConnectionStatistics();
-
- /**
- * Currently accumulated load statistics.
- */
- private LoadValues accumulatedValues = new LoadValues();
-
- /**
- * Main lock guarding all access
- */
- private final ReentrantLock lock;
-
- /**
- * Condition for threads waiting to take a connection
- */
- private final Condition notEmpty;
-
- private int connectionId;
-
- private final long waitTimeoutMillis;
-
- private final long leakTimeMinutes;
-
- private final long maxAgeMillis;
-
- private int warningSize;
-
- private int maxSize;
-
- private int minSize;
-
- /**
- * Number of threads in the wait queue.
- */
- private int waitingThreads;
-
- /**
- * Number of times a thread had to wait.
- */
- private int waitCount;
-
- /**
- * Number of times a connection was got from this queue.
- */
- private int hitCount;
-
- /**
- * The high water mark for the queue size.
- */
- private int highWaterMark;
-
- /**
- * Last time the pool was reset. Used to close busy connections as they are
- * returned to the pool that where created prior to the lastResetTime.
- */
- private long lastResetTime;
-
- private boolean doingShutdown;
-
- public PooledConnectionQueue(DataSourcePool pool) {
-
- this.pool = pool;
- this.name = pool.getName();
- this.minSize = pool.getMinSize();
- this.maxSize = pool.getMaxSize();
-
- this.warningSize = pool.getWarningSize();
- this.waitTimeoutMillis = pool.getWaitTimeoutMillis();
- this.leakTimeMinutes = pool.getLeakTimeMinutes();
- this.maxAgeMillis = pool.getMaxAgeMillis();
-
- this.busyList = new BusyConnectionBuffer(maxSize, 20);
- this.freeList = new FreeConnectionBuffer();
-
- this.lock = new ReentrantLock(false);
- this.notEmpty = lock.newCondition();
- }
-
- private Status createStatus() {
- return new Status(name, minSize, maxSize, freeList.size(), busyList.size(), waitingThreads, highWaterMark, waitCount, hitCount);
- }
-
- public String toString() {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- return createStatus().toString();
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Collect statistics of a connection that is fully closing
- */
- protected void reportClosingConnection(PooledConnection pooledConnection) {
-
- collectedStats.add(pooledConnection.getStatistics());
- }
-
- public DataSourcePoolStatistics getStatistics(boolean reset) {
-
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
-
- LoadValues aggregate = collectedStats.getValues(reset);
-
- freeList.collectStatistics(aggregate, reset);
- busyList.collectStatistics(aggregate, reset);
-
- aggregate.plus(accumulatedValues);
-
- this.accumulatedValues = (reset) ? new LoadValues() : aggregate;
-
- return new DataSourcePoolStatistics(aggregate.getCollectionStart(), aggregate.getCount(), aggregate.getErrorCount(), aggregate.getHwmMicros(), aggregate.getTotalMicros());
-
- } finally {
- lock.unlock();
- }
- }
-
- public Status getStatus(boolean reset) {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- Status s = createStatus();
- if (reset){
- highWaterMark = busyList.size();
- hitCount = 0;
- waitCount = 0;
- }
- return s;
- } finally {
- lock.unlock();
- }
- }
-
- public void setMinSize(int minSize) {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- if (minSize > this.maxSize){
- throw new IllegalArgumentException("minSize "+minSize+" > maxSize "+this.maxSize);
- }
- this.minSize = minSize;
- } finally {
- lock.unlock();
- }
- }
-
- public void setMaxSize(int maxSize) {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- if (maxSize < this.minSize){
- throw new IllegalArgumentException("maxSize "+maxSize+" < minSize "+this.minSize);
- }
- this.busyList.setCapacity(maxSize);
- this.maxSize = maxSize;
- } finally {
- lock.unlock();
- }
- }
-
- public void setWarningSize(int warningSize) {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- if (warningSize > this.maxSize){
- throw new IllegalArgumentException("warningSize "+warningSize+" > maxSize "+this.maxSize);
- }
- this.warningSize = warningSize;
- } finally {
- lock.unlock();
- }
- }
-
- private int totalConnections() {
- return freeList.size() + busyList.size();
- }
-
- public void ensureMinimumConnections() throws SQLException {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- int add = minSize - totalConnections();
- if (add > 0){
- for (int i = 0; i < add; i++) {
- PooledConnection c = pool.createConnectionForQueue(connectionId++);
- freeList.add(c);
- }
- notEmpty.signal();
- }
-
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Return a PooledConnection.
- */
- protected void returnPooledConnection(PooledConnection c, boolean forceClose) {
-
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- if (!busyList.remove(c)) {
- logger.error("Connection [{}] not found in BusyList? ", c);
- }
- if (forceClose || c.shouldTrimOnReturn(lastResetTime, maxAgeMillis)) {
- c.closeConnectionFully(false);
-
- } else {
- freeList.add(c);
- notEmpty.signal();
- }
- } finally {
- lock.unlock();
- }
- }
-
- private PooledConnection extractFromFreeList() {
- PooledConnection c = freeList.remove();
- registerBusyConnection(c);
- return c;
- }
-
- public PooledConnection getPooledConnection() throws SQLException {
-
- try {
- PooledConnection pc = _getPooledConnection();
- pc.resetForUse();
- return pc;
-
- } catch (InterruptedException e) {
- String msg = "Interrupted getting connection from pool "+e;
- throw new SQLException(msg);
- }
- }
-
- /**
- * Register the PooledConnection with the busyList.
- */
- private int registerBusyConnection(PooledConnection c) {
- int busySize = busyList.add(c);
- if (busySize > highWaterMark){
- highWaterMark = busySize;
- }
- return busySize;
- }
-
- private PooledConnection _getPooledConnection() throws InterruptedException, SQLException {
- final ReentrantLock lock = this.lock;
- lock.lockInterruptibly();
- try {
- if (doingShutdown) {
- throw new SQLException("Trying to access the Connection Pool when it is shutting down");
- }
-
- // this includes attempts that fail with InterruptedException
- // or SQLException but that is ok as its only an indicator
- hitCount++;
-
- // are other threads already waiting? (they get priority)
- if (waitingThreads == 0){
-
- if (!freeList.isEmpty()){
- // we have a free connection to return
- return extractFromFreeList();
- }
-
- if (busyList.size() < maxSize){
- // grow the connection pool
- PooledConnection c = pool.createConnectionForQueue(connectionId++);
- int busySize = registerBusyConnection(c);
-
- if (logger.isDebugEnabled()) {
- logger.debug("DataSourcePool [{}] grow; id[{}] busy[{}] max[{}]", name, c.getName(), busySize, maxSize);
- }
- checkForWarningSize();
- return c;
- }
- }
-
- try {
- // The pool is at maximum size. We are going to go into
- // a wait loop until connections are returned into the pool.
- waitCount++;
- waitingThreads++;
- return _getPooledConnectionWaitLoop();
- } finally {
- waitingThreads--;
- }
-
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Got into a loop waiting for connections to be returned to the pool.
- */
- private PooledConnection _getPooledConnectionWaitLoop() throws SQLException, InterruptedException {
-
- long nanos = MILLIS_TIME_UNIT.toNanos(waitTimeoutMillis);
- for (;;) {
-
- if (nanos <= 0) {
- String msg = "Unsuccessfully waited ["+waitTimeoutMillis+"] millis for a connection to be returned."
- + " No connections are free. You need to Increase the max connections of ["+maxSize+"]"
- + " or look for a connection pool leak using datasource.xxx.capturestacktrace=true";
- if (pool.isCaptureStackTrace()) {
- dumpBusyConnectionInformation();
- }
-
- throw new SQLException(msg);
- }
-
- try {
- nanos = notEmpty.awaitNanos(nanos);
- if (!freeList.isEmpty()) {
- // successfully waited
- return extractFromFreeList();
- }
- } catch (InterruptedException ie) {
- notEmpty.signal(); // propagate to non-interrupted thread
- throw ie;
- }
- }
- }
-
- public void shutdown() {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- doingShutdown = true;
- Status status = createStatus();
- DataSourcePoolStatistics statistics = pool.getStatistics(false);
- logger.debug("DataSourcePool [{}] shutdown {} - Statistics {}", name, status, statistics);
-
- closeFreeConnections(true);
-
- if (!busyList.isEmpty()) {
- logger.warn("Closing busy connections on shutdown size: "+ busyList.size());
- dumpBusyConnectionInformation();
- closeBusyConnections(0);
- }
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Close all the connections in the pool and any current busy connections
- * when they are returned. New connections will be then created on demand.
- *
- * This is typically done when a database down event occurs.
- *
- */
- public void reset(long leakTimeMinutes) {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- Status status = createStatus();
- logger.info("Reseting DataSourcePool [{}] {}", name, status);
- lastResetTime = System.currentTimeMillis();
-
- closeFreeConnections(false);
- closeBusyConnections(leakTimeMinutes);
-
- logger.info("Busy Connections:\n" + getBusyConnectionInformation());
-
- } finally {
- lock.unlock();
- }
- }
-
- public void trim(long maxInactiveMillis, long maxAgeMillis) {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- if (trimInactiveConnections(maxInactiveMillis, maxAgeMillis) > 0) {
- try {
- ensureMinimumConnections();
- } catch (SQLException e) {
- logger.error("Error trying to ensure minimum connections", e);
- }
- }
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Trim connections that have been not used for some time.
- */
- private int trimInactiveConnections(long maxInactiveMillis, long maxAgeMillis) {
-
- long usedSince = System.currentTimeMillis() - maxInactiveMillis;
- long createdSince = (maxAgeMillis == 0) ? 0 : System.currentTimeMillis() - maxAgeMillis;
-
- int trimedCount = freeList.trim(usedSince, createdSince);
- if (trimedCount > 0) {
- logger.debug("DataSourcePool [{}] trimmed [{}] inactive connections. New size[{}]", name, trimedCount, totalConnections());
- }
- return trimedCount;
- }
-
- /**
- * Close all the connections that are in the free list.
- */
- public void closeFreeConnections(boolean logErrors) {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- freeList.closeAll(logErrors);
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Close any busy connections that have not been used for some time.
- *
- * These connections are considered to have leaked from the connection pool.
- *
- *
- * Connection leaks occur when code doesn't ensure that connections are
- * closed() after they have been finished with. There should be an
- * appropriate try catch finally block to ensure connections are always
- * closed and put back into the pool.
- *
- */
- public void closeBusyConnections(long leakTimeMinutes) {
-
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- busyList.closeBusyConnections(leakTimeMinutes);
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * As the pool grows it gets closer to the maxConnections limit. We can send
- * an Alert (or warning) as we get close to this limit and hence an
- * Administrator could increase the pool size if desired.
- *
- * This is called whenever the pool grows in size (towards the max limit).
- *
- */
- private void checkForWarningSize() {
-
- // the the total number of connections that we can add
- // to the pool before it hits the maximum
- int availableGrowth = (maxSize - totalConnections());
-
- if (availableGrowth < warningSize) {
-
- closeBusyConnections(leakTimeMinutes);
-
- String msg = "DataSourcePool [" + name + "] is [" + availableGrowth+ "] connections from its maximum size.";
- pool.notifyWarning(msg);
- }
- }
-
- public String getBusyConnectionInformation() {
- return getBusyConnectionInformation(false);
- }
-
- public void dumpBusyConnectionInformation() {
- getBusyConnectionInformation(true);
- }
-
- /**
- * Returns information describing connections that are currently being used.
- */
- private String getBusyConnectionInformation(boolean toLogger) {
-
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
-
- return busyList.getBusyConnectionInformation(toLogger);
-
- } finally {
- lock.unlock();
- }
- }
-
-}
-
+package com.avaje.ebeaninternal.server.lib.sql;
+
+import java.sql.SQLException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.avaje.ebeaninternal.server.lib.sql.DataSourcePool.Status;
+import com.avaje.ebeaninternal.server.lib.sql.PooledConnectionStatistics.LoadValues;
+
+public class PooledConnectionQueue {
+
+ private static final Logger logger = LoggerFactory.getLogger(PooledConnectionQueue.class);
+
+ private static final TimeUnit MILLIS_TIME_UNIT = TimeUnit.MILLISECONDS;
+
+ private final String name;
+
+ private final DataSourcePool pool;
+
+ /**
+ * A 'circular' buffer designed specifically for free connections.
+ */
+ private final FreeConnectionBuffer freeList;
+
+ /**
+ * A 'slots' buffer designed specifically for busy connections.
+ * Fast add remove based on slot id.
+ */
+ private final BusyConnectionBuffer busyList;
+
+ /**
+ * Load statistics collected off connections that have closed fully (left the pool).
+ */
+ private final PooledConnectionStatistics collectedStats = new PooledConnectionStatistics();
+
+ /**
+ * Currently accumulated load statistics.
+ */
+ private LoadValues accumulatedValues = new LoadValues();
+
+ /**
+ * Main lock guarding all access
+ */
+ private final ReentrantLock lock;
+
+ /**
+ * Condition for threads waiting to take a connection
+ */
+ private final Condition notEmpty;
+
+ private int connectionId;
+
+ private final long waitTimeoutMillis;
+
+ private final long leakTimeMinutes;
+
+ private final long maxAgeMillis;
+
+ private int warningSize;
+
+ private int maxSize;
+
+ private int minSize;
+
+ /**
+ * Number of threads in the wait queue.
+ */
+ private int waitingThreads;
+
+ /**
+ * Number of times a thread had to wait.
+ */
+ private int waitCount;
+
+ /**
+ * Number of times a connection was got from this queue.
+ */
+ private int hitCount;
+
+ /**
+ * The high water mark for the queue size.
+ */
+ private int highWaterMark;
+
+ /**
+ * Last time the pool was reset. Used to close busy connections as they are
+ * returned to the pool that where created prior to the lastResetTime.
+ */
+ private long lastResetTime;
+
+ private boolean doingShutdown;
+
+ public PooledConnectionQueue(DataSourcePool pool) {
+
+ this.pool = pool;
+ this.name = pool.getName();
+ this.minSize = pool.getMinSize();
+ this.maxSize = pool.getMaxSize();
+
+ this.warningSize = pool.getWarningSize();
+ this.waitTimeoutMillis = pool.getWaitTimeoutMillis();
+ this.leakTimeMinutes = pool.getLeakTimeMinutes();
+ this.maxAgeMillis = pool.getMaxAgeMillis();
+
+ this.busyList = new BusyConnectionBuffer(maxSize, 20);
+ this.freeList = new FreeConnectionBuffer();
+
+ this.lock = new ReentrantLock(false);
+ this.notEmpty = lock.newCondition();
+ }
+
+ private Status createStatus() {
+ return new Status(name, minSize, maxSize, freeList.size(), busyList.size(), waitingThreads, highWaterMark, waitCount, hitCount);
+ }
+
+ public String toString() {
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ return createStatus().toString();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Collect statistics of a connection that is fully closing
+ */
+ protected void reportClosingConnection(PooledConnection pooledConnection) {
+
+ collectedStats.add(pooledConnection.getStatistics());
+ }
+
+ public DataSourcePoolStatistics getStatistics(boolean reset) {
+
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+
+ LoadValues aggregate = collectedStats.getValues(reset);
+
+ freeList.collectStatistics(aggregate, reset);
+ busyList.collectStatistics(aggregate, reset);
+
+ aggregate.plus(accumulatedValues);
+
+ this.accumulatedValues = (reset) ? new LoadValues() : aggregate;
+
+ return new DataSourcePoolStatistics(aggregate.getCollectionStart(), aggregate.getCount(), aggregate.getErrorCount(), aggregate.getHwmMicros(), aggregate.getTotalMicros());
+
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public Status getStatus(boolean reset) {
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ Status s = createStatus();
+ if (reset){
+ highWaterMark = busyList.size();
+ hitCount = 0;
+ waitCount = 0;
+ }
+ return s;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public void setMinSize(int minSize) {
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ if (minSize > this.maxSize){
+ throw new IllegalArgumentException("minSize "+minSize+" > maxSize "+this.maxSize);
+ }
+ this.minSize = minSize;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public void setMaxSize(int maxSize) {
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ if (maxSize < this.minSize){
+ throw new IllegalArgumentException("maxSize "+maxSize+" < minSize "+this.minSize);
+ }
+ this.busyList.setCapacity(maxSize);
+ this.maxSize = maxSize;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public void setWarningSize(int warningSize) {
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ if (warningSize > this.maxSize){
+ throw new IllegalArgumentException("warningSize "+warningSize+" > maxSize "+this.maxSize);
+ }
+ this.warningSize = warningSize;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private int totalConnections() {
+ return freeList.size() + busyList.size();
+ }
+
+ public void ensureMinimumConnections() throws SQLException {
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ int add = minSize - totalConnections();
+ if (add > 0){
+ for (int i = 0; i < add; i++) {
+ PooledConnection c = pool.createConnectionForQueue(connectionId++);
+ freeList.add(c);
+ }
+ notEmpty.signal();
+ }
+
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Return a PooledConnection.
+ */
+ protected void returnPooledConnection(PooledConnection c, boolean forceClose) {
+
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ if (!busyList.remove(c)) {
+ logger.error("Connection [{}] not found in BusyList? ", c);
+ }
+ if (forceClose || c.shouldTrimOnReturn(lastResetTime, maxAgeMillis)) {
+ c.closeConnectionFully(false);
+
+ } else {
+ freeList.add(c);
+ notEmpty.signal();
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private PooledConnection extractFromFreeList() {
+ PooledConnection c = freeList.remove();
+ registerBusyConnection(c);
+ return c;
+ }
+
+ public PooledConnection getPooledConnection() throws SQLException {
+
+ try {
+ PooledConnection pc = _getPooledConnection();
+ pc.resetForUse();
+ return pc;
+
+ } catch (InterruptedException e) {
+ String msg = "Interrupted getting connection from pool "+e;
+ throw new SQLException(msg);
+ }
+ }
+
+ /**
+ * Register the PooledConnection with the busyList.
+ */
+ private int registerBusyConnection(PooledConnection c) {
+ int busySize = busyList.add(c);
+ if (busySize > highWaterMark){
+ highWaterMark = busySize;
+ }
+ return busySize;
+ }
+
+ private PooledConnection _getPooledConnection() throws InterruptedException, SQLException {
+ final ReentrantLock lock = this.lock;
+ lock.lockInterruptibly();
+ try {
+ if (doingShutdown) {
+ throw new SQLException("Trying to access the Connection Pool when it is shutting down");
+ }
+
+ // this includes attempts that fail with InterruptedException
+ // or SQLException but that is ok as its only an indicator
+ hitCount++;
+
+ // are other threads already waiting? (they get priority)
+ if (waitingThreads == 0){
+
+ if (!freeList.isEmpty()){
+ // we have a free connection to return
+ return extractFromFreeList();
+ }
+
+ if (busyList.size() < maxSize){
+ // grow the connection pool
+ PooledConnection c = pool.createConnectionForQueue(connectionId++);
+ int busySize = registerBusyConnection(c);
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("DataSourcePool [{}] grow; id[{}] busy[{}] max[{}]", name, c.getName(), busySize, maxSize);
+ }
+ checkForWarningSize();
+ return c;
+ }
+ }
+
+ try {
+ // The pool is at maximum size. We are going to go into
+ // a wait loop until connections are returned into the pool.
+ waitCount++;
+ waitingThreads++;
+ return _getPooledConnectionWaitLoop();
+ } finally {
+ waitingThreads--;
+ }
+
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Got into a loop waiting for connections to be returned to the pool.
+ */
+ private PooledConnection _getPooledConnectionWaitLoop() throws SQLException, InterruptedException {
+
+ long nanos = MILLIS_TIME_UNIT.toNanos(waitTimeoutMillis);
+ for (;;) {
+
+ if (nanos <= 0) {
+ String msg = "Unsuccessfully waited ["+waitTimeoutMillis+"] millis for a connection to be returned."
+ + " No connections are free. You need to Increase the max connections of ["+maxSize+"]"
+ + " or look for a connection pool leak using datasource.xxx.capturestacktrace=true";
+ if (pool.isCaptureStackTrace()) {
+ dumpBusyConnectionInformation();
+ }
+
+ throw new SQLException(msg);
+ }
+
+ try {
+ nanos = notEmpty.awaitNanos(nanos);
+ if (!freeList.isEmpty()) {
+ // successfully waited
+ return extractFromFreeList();
+ }
+ } catch (InterruptedException ie) {
+ notEmpty.signal(); // propagate to non-interrupted thread
+ throw ie;
+ }
+ }
+ }
+
+ public void shutdown() {
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ doingShutdown = true;
+ Status status = createStatus();
+ DataSourcePoolStatistics statistics = pool.getStatistics(false);
+ logger.debug("DataSourcePool [{}] shutdown {} - Statistics {}", name, status, statistics);
+
+ closeFreeConnections(true);
+
+ if (!busyList.isEmpty()) {
+ logger.warn("Closing busy connections on shutdown size: "+ busyList.size());
+ dumpBusyConnectionInformation();
+ closeBusyConnections(0);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Close all the connections in the pool and any current busy connections
+ * when they are returned. New connections will be then created on demand.
+ *
+ * This is typically done when a database down event occurs.
+ *
+ */
+ public void reset(long leakTimeMinutes) {
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ Status status = createStatus();
+ logger.info("Reseting DataSourcePool [{}] {}", name, status);
+ lastResetTime = System.currentTimeMillis();
+
+ closeFreeConnections(false);
+ closeBusyConnections(leakTimeMinutes);
+
+ logger.info("Busy Connections:\n" + getBusyConnectionInformation());
+
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public void trim(long maxInactiveMillis, long maxAgeMillis) {
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ if (trimInactiveConnections(maxInactiveMillis, maxAgeMillis) > 0) {
+ try {
+ ensureMinimumConnections();
+ } catch (SQLException e) {
+ logger.error("Error trying to ensure minimum connections", e);
+ }
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Trim connections that have been not used for some time.
+ */
+ private int trimInactiveConnections(long maxInactiveMillis, long maxAgeMillis) {
+
+ long usedSince = System.currentTimeMillis() - maxInactiveMillis;
+ long createdSince = (maxAgeMillis == 0) ? 0 : System.currentTimeMillis() - maxAgeMillis;
+
+ int trimedCount = freeList.trim(usedSince, createdSince);
+ if (trimedCount > 0) {
+ logger.debug("DataSourcePool [{}] trimmed [{}] inactive connections. New size[{}]", name, trimedCount, totalConnections());
+ }
+ return trimedCount;
+ }
+
+ /**
+ * Close all the connections that are in the free list.
+ */
+ public void closeFreeConnections(boolean logErrors) {
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ freeList.closeAll(logErrors);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Close any busy connections that have not been used for some time.
+ *
+ * These connections are considered to have leaked from the connection pool.
+ *
+ *
+ * Connection leaks occur when code doesn't ensure that connections are
+ * closed() after they have been finished with. There should be an
+ * appropriate try catch finally block to ensure connections are always
+ * closed and put back into the pool.
+ *
+ */
+ public void closeBusyConnections(long leakTimeMinutes) {
+
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ busyList.closeBusyConnections(leakTimeMinutes);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * As the pool grows it gets closer to the maxConnections limit. We can send
+ * an Alert (or warning) as we get close to this limit and hence an
+ * Administrator could increase the pool size if desired.
+ *
+ * This is called whenever the pool grows in size (towards the max limit).
+ *
+ */
+ private void checkForWarningSize() {
+
+ // the the total number of connections that we can add
+ // to the pool before it hits the maximum
+ int availableGrowth = (maxSize - totalConnections());
+
+ if (availableGrowth < warningSize) {
+
+ closeBusyConnections(leakTimeMinutes);
+
+ String msg = "DataSourcePool [" + name + "] is [" + availableGrowth+ "] connections from its maximum size.";
+ pool.notifyWarning(msg);
+ }
+ }
+
+ public String getBusyConnectionInformation() {
+ return getBusyConnectionInformation(false);
+ }
+
+ public void dumpBusyConnectionInformation() {
+ getBusyConnectionInformation(true);
+ }
+
+ /**
+ * Returns information describing connections that are currently being used.
+ */
+ private String getBusyConnectionInformation(boolean toLogger) {
+
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+
+ return busyList.getBusyConnectionInformation(toLogger);
+
+ } finally {
+ lock.unlock();
+ }
+ }
+
+}
+
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/Prefix.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/Prefix.java
index 51676ae2e..4e58e8f1b 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/Prefix.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/Prefix.java
@@ -1,100 +1,100 @@
-package com.avaje.ebeaninternal.server.lib.sql;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Random;
-
-/**
- * Security mechanisim.
- */
-public class Prefix {
-
- private static final Logger logger = LoggerFactory.getLogger(Prefix.class);
-
- private static final int[] oa = { 50, 12, 4, 6, 8, 10, 7, 23, 45, 23, 6, 9, 12, 2, 8, 34 };
-
- public static String getProp(String prop) {
- String v = dec(prop);
- int p = v.indexOf(":");
- String r = v.substring(1, p);
- return r;
- }
-
- public static void main(String[] args) {
- String m = e(args[0]);
- logger.info("[" + m + "]");
- String o = getProp(m);
- logger.info("[" + o + "]");
- }
-
- public static String e(String msg) {
- msg = elen(msg, 40);
- return enc(msg);
- }
-
- public static byte az(byte c, int offset) {
-
- int z = c + offset;
- if (z > 122) {
- // dp("z> "+z);
- z = z - 122 + 48 - 1;
- }
- // dp("z="+z+" c:"+(int)c);
- return (byte) z;
- }
-
- public static byte bz(byte c, int offset) {
- int z = c - offset;
- if (z < (48)) {
- // dp("z< "+z);
- z = z + 122 - 48 + 1;
- }
- return (byte) z;
- }
-
- public static String enc(String msg) {
- byte[] msgbytes = msg.getBytes();
- byte[] encbytes = new byte[msgbytes.length + 1];
- Random r = new Random();
- int key = r.nextInt(70);
-
- char k = (char) (key + 48);
-
- encbytes[0] = az((byte) k, oa[0]);
- // dp("key:"+key+" encbytes[0]:"+(byte)encbytes[0]);
- int ios = key;
- for (int i = 1; i < (msgbytes.length + 1); i++) {
- encbytes[i] = az(msgbytes[i - 1], (oa[(i + ios) % oa.length]));
- }
- return new String(encbytes);
- }
-
- public static String dec(String msg) {
- byte[] msgbytes = msg.getBytes();
- byte[] encbytes = new byte[msgbytes.length];
-
- encbytes[0] = bz(msgbytes[0], oa[0]);
- byte key = encbytes[0];
- int ios = (key - 48);
- for (int i = 1; i < msgbytes.length; i++) {
- encbytes[i] = bz(msgbytes[i], oa[(i + ios) % oa.length]);
- }
- return new String(encbytes);
- }
-
- public static String elen(String msg, int len) {
- Random r = new Random();
- if (msg.length() < len) {
- int max = len - msg.length();
- StringBuilder sb = new StringBuilder();
- sb.append(msg).append(":");
- for (int i = 1; i < max; i++) {
- int bc = r.nextInt(122 - 48);
- sb.append(Character.toString((char) (bc + 48)));
- }
- return sb.toString();
- }
- return msg;
- }
-}
+package com.avaje.ebeaninternal.server.lib.sql;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Random;
+
+/**
+ * Security mechanisim.
+ */
+public class Prefix {
+
+ private static final Logger logger = LoggerFactory.getLogger(Prefix.class);
+
+ private static final int[] oa = { 50, 12, 4, 6, 8, 10, 7, 23, 45, 23, 6, 9, 12, 2, 8, 34 };
+
+ public static String getProp(String prop) {
+ String v = dec(prop);
+ int p = v.indexOf(":");
+ String r = v.substring(1, p);
+ return r;
+ }
+
+ public static void main(String[] args) {
+ String m = e(args[0]);
+ logger.info("[" + m + "]");
+ String o = getProp(m);
+ logger.info("[" + o + "]");
+ }
+
+ public static String e(String msg) {
+ msg = elen(msg, 40);
+ return enc(msg);
+ }
+
+ public static byte az(byte c, int offset) {
+
+ int z = c + offset;
+ if (z > 122) {
+ // dp("z> "+z);
+ z = z - 122 + 48 - 1;
+ }
+ // dp("z="+z+" c:"+(int)c);
+ return (byte) z;
+ }
+
+ public static byte bz(byte c, int offset) {
+ int z = c - offset;
+ if (z < (48)) {
+ // dp("z< "+z);
+ z = z + 122 - 48 + 1;
+ }
+ return (byte) z;
+ }
+
+ public static String enc(String msg) {
+ byte[] msgbytes = msg.getBytes();
+ byte[] encbytes = new byte[msgbytes.length + 1];
+ Random r = new Random();
+ int key = r.nextInt(70);
+
+ char k = (char) (key + 48);
+
+ encbytes[0] = az((byte) k, oa[0]);
+ // dp("key:"+key+" encbytes[0]:"+(byte)encbytes[0]);
+ int ios = key;
+ for (int i = 1; i < (msgbytes.length + 1); i++) {
+ encbytes[i] = az(msgbytes[i - 1], (oa[(i + ios) % oa.length]));
+ }
+ return new String(encbytes);
+ }
+
+ public static String dec(String msg) {
+ byte[] msgbytes = msg.getBytes();
+ byte[] encbytes = new byte[msgbytes.length];
+
+ encbytes[0] = bz(msgbytes[0], oa[0]);
+ byte key = encbytes[0];
+ int ios = (key - 48);
+ for (int i = 1; i < msgbytes.length; i++) {
+ encbytes[i] = bz(msgbytes[i], oa[(i + ios) % oa.length]);
+ }
+ return new String(encbytes);
+ }
+
+ public static String elen(String msg, int len) {
+ Random r = new Random();
+ if (msg.length() < len) {
+ int max = len - msg.length();
+ StringBuilder sb = new StringBuilder();
+ sb.append(msg).append(":");
+ for (int i = 1; i < max; i++) {
+ int bc = r.nextInt(122 - 48);
+ sb.append(Character.toString((char) (bc + 48)));
+ }
+ return sb.toString();
+ }
+ return msg;
+ }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PstmtCache.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PstmtCache.java
index f28bfe46d..2d6adc436 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PstmtCache.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PstmtCache.java
@@ -1,183 +1,183 @@
-package com.avaje.ebeaninternal.server.lib.sql;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.sql.SQLException;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-/**
- * A LRU based cache for PreparedStatements.
- */
-public class PstmtCache extends LinkedHashMap {
-
- private static final Logger logger = LoggerFactory.getLogger(PstmtCache.class);
-
- static final long serialVersionUID = -3096406924865550697L;
-
- /**
- * The name of the cache, for tracing purposes.
- */
- protected final String cacheName;
-
- /**
- * The maximum size of the cache. When this is exceeded the oldest entry is removed.
- */
- private final int maxSize;
-
- /**
- * The total number of entries removed from this cache.
- */
- private int removeCounter;
-
- /**
- * The number of get hits.
- */
- private int hitCounter;
-
- /**
- * The number of get() misses.
- */
- private int missCounter;
-
- /**
- * The number of puts into this cache.
- */
- private int putCounter;
-
- public PstmtCache(String cacheName, int maxCacheSize) {
-
- // note = access ordered list. This is what gives it the LRU order
- super(maxCacheSize*3, 0.75f, true);
- this.cacheName = cacheName;
- this.maxSize = maxCacheSize;
- }
-
- /**
- * Return a summary description of this cache.
- */
- public String getDescription() {
- return "size["+size()+"] max["+maxSize+"] hits["+hitCounter+"] miss["+missCounter+"] hitRatio["+getHitRatio()+"] removes["+removeCounter+"]";
- }
-
- /**
- * returns the current maximum size of the cache.
- */
- public int getMaxSize() {
- return maxSize;
- }
-
- /**
- * Gets the hit ratio. A number between 0 and 100 indicating the number of
- * hits to misses. A number approaching 100 is desirable.
- */
- public int getHitRatio() {
- if (hitCounter == 0) {
- return 0;
- } else {
- return hitCounter*100/(hitCounter+missCounter);
- }
- }
-
- /**
- * The total number of hits against this cache.
- */
- public int getHitCounter() {
- return hitCounter;
- }
-
- /**
- * The total number of misses against this cache.
- */
- public int getMissCounter() {
- return missCounter;
- }
-
- /**
- * The total number of puts against this cache.
- */
- public int getPutCounter() {
- return putCounter;
- }
-
- /**
- * Try to add the returning statement to the cache. If there is already a
- * matching ExtendedPreparedStatement in the cache return false else add
- * the statement to the cache and return true.
- */
- public boolean returnStatement(ExtendedPreparedStatement pstmt) {
-
- ExtendedPreparedStatement alreadyInCache = super.get(pstmt.getCacheKey());
- if (alreadyInCache != null) {
- return false;
- }
- // add the returning prepared statement to the cache.
- // Note that the LRUCache will automatically close fully old unused
- // PStmts when the cache has hit its maximum size.
- put(pstmt.getCacheKey(), pstmt);
- return true;
- }
-
- /**
- * additionally maintains hit and miss statistics.
- */
- public ExtendedPreparedStatement get(Object key) {
-
- ExtendedPreparedStatement o = super.get(key);
- if (o == null) {
- missCounter++;
- } else {
- hitCounter++;
- }
- return o;
- }
-
- /**
- * additionally maintains hit and miss statistics.
- */
- public ExtendedPreparedStatement remove(Object key) {
-
- ExtendedPreparedStatement o = super.remove(key);
- if (o == null) {
- missCounter++;
- } else {
- hitCounter++;
- }
- return o;
- }
-
- /**
- * additionally maintains put counter statistics.
- */
- public ExtendedPreparedStatement put(String key, ExtendedPreparedStatement value) {
-
- putCounter++;
- return super.put(key, value);
- }
-
- /**
- * will check to see if we need to remove entries and
- * if so call the cacheCleanup.cleanupEldestLRUCacheEntry() if
- * one has been set.
- */
- protected boolean removeEldestEntry(Map.Entry eldest) {
-
- if (size() < maxSize) {
- return false;
- }
-
- removeCounter++;
-
- try {
- ExtendedPreparedStatement pstmt = eldest.getValue();
- pstmt.closeDestroy();
- } catch (SQLException e) {
- logger.error("Error closing ExtendedPreparedStatement", e);
- }
- return true;
- }
-
-
-}
-
+package com.avaje.ebeaninternal.server.lib.sql;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.SQLException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * A LRU based cache for PreparedStatements.
+ */
+public class PstmtCache extends LinkedHashMap {
+
+ private static final Logger logger = LoggerFactory.getLogger(PstmtCache.class);
+
+ static final long serialVersionUID = -3096406924865550697L;
+
+ /**
+ * The name of the cache, for tracing purposes.
+ */
+ protected final String cacheName;
+
+ /**
+ * The maximum size of the cache. When this is exceeded the oldest entry is removed.
+ */
+ private final int maxSize;
+
+ /**
+ * The total number of entries removed from this cache.
+ */
+ private int removeCounter;
+
+ /**
+ * The number of get hits.
+ */
+ private int hitCounter;
+
+ /**
+ * The number of get() misses.
+ */
+ private int missCounter;
+
+ /**
+ * The number of puts into this cache.
+ */
+ private int putCounter;
+
+ public PstmtCache(String cacheName, int maxCacheSize) {
+
+ // note = access ordered list. This is what gives it the LRU order
+ super(maxCacheSize*3, 0.75f, true);
+ this.cacheName = cacheName;
+ this.maxSize = maxCacheSize;
+ }
+
+ /**
+ * Return a summary description of this cache.
+ */
+ public String getDescription() {
+ return "size["+size()+"] max["+maxSize+"] hits["+hitCounter+"] miss["+missCounter+"] hitRatio["+getHitRatio()+"] removes["+removeCounter+"]";
+ }
+
+ /**
+ * returns the current maximum size of the cache.
+ */
+ public int getMaxSize() {
+ return maxSize;
+ }
+
+ /**
+ * Gets the hit ratio. A number between 0 and 100 indicating the number of
+ * hits to misses. A number approaching 100 is desirable.
+ */
+ public int getHitRatio() {
+ if (hitCounter == 0) {
+ return 0;
+ } else {
+ return hitCounter*100/(hitCounter+missCounter);
+ }
+ }
+
+ /**
+ * The total number of hits against this cache.
+ */
+ public int getHitCounter() {
+ return hitCounter;
+ }
+
+ /**
+ * The total number of misses against this cache.
+ */
+ public int getMissCounter() {
+ return missCounter;
+ }
+
+ /**
+ * The total number of puts against this cache.
+ */
+ public int getPutCounter() {
+ return putCounter;
+ }
+
+ /**
+ * Try to add the returning statement to the cache. If there is already a
+ * matching ExtendedPreparedStatement in the cache return false else add
+ * the statement to the cache and return true.
+ */
+ public boolean returnStatement(ExtendedPreparedStatement pstmt) {
+
+ ExtendedPreparedStatement alreadyInCache = super.get(pstmt.getCacheKey());
+ if (alreadyInCache != null) {
+ return false;
+ }
+ // add the returning prepared statement to the cache.
+ // Note that the LRUCache will automatically close fully old unused
+ // PStmts when the cache has hit its maximum size.
+ put(pstmt.getCacheKey(), pstmt);
+ return true;
+ }
+
+ /**
+ * additionally maintains hit and miss statistics.
+ */
+ public ExtendedPreparedStatement get(Object key) {
+
+ ExtendedPreparedStatement o = super.get(key);
+ if (o == null) {
+ missCounter++;
+ } else {
+ hitCounter++;
+ }
+ return o;
+ }
+
+ /**
+ * additionally maintains hit and miss statistics.
+ */
+ public ExtendedPreparedStatement remove(Object key) {
+
+ ExtendedPreparedStatement o = super.remove(key);
+ if (o == null) {
+ missCounter++;
+ } else {
+ hitCounter++;
+ }
+ return o;
+ }
+
+ /**
+ * additionally maintains put counter statistics.
+ */
+ public ExtendedPreparedStatement put(String key, ExtendedPreparedStatement value) {
+
+ putCounter++;
+ return super.put(key, value);
+ }
+
+ /**
+ * will check to see if we need to remove entries and
+ * if so call the cacheCleanup.cleanupEldestLRUCacheEntry() if
+ * one has been set.
+ */
+ protected boolean removeEldestEntry(Map.Entry eldest) {
+
+ if (size() < maxSize) {
+ return false;
+ }
+
+ removeCounter++;
+
+ try {
+ ExtendedPreparedStatement pstmt = eldest.getValue();
+ pstmt.closeDestroy();
+ } catch (SQLException e) {
+ logger.error("Error closing ExtendedPreparedStatement", e);
+ }
+ return true;
+ }
+
+
+}
+
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/SimpleDataSourceAlert.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/SimpleDataSourceAlert.java
index 694103be9..4791732b2 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/SimpleDataSourceAlert.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/SimpleDataSourceAlert.java
@@ -1,106 +1,106 @@
-package com.avaje.ebeaninternal.server.lib.sql;
-
-import com.avaje.ebeaninternal.server.lib.util.MailEvent;
-import com.avaje.ebeaninternal.server.lib.util.MailListener;
-import com.avaje.ebeaninternal.server.lib.util.MailMessage;
-import com.avaje.ebeaninternal.server.lib.util.MailSender;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * A simple smtp email alert that sends a email message on dataSourceDown and
- * dataSourceUp etc.
- *
- * - alert.fromuser = the from user name
- *
- alert.fromemail = the from email account
- *
- alert.toemail = comma delimited list of email accounts to email
- *
- alert.mailserver = the smpt server name
- *
- */
-public class SimpleDataSourceAlert implements DataSourceAlert, MailListener {
-
- private static final Logger logger = LoggerFactory.getLogger(SimpleDataSourceAlert.class);
-
- private static String alertMailServerName = System.getProperty("ebean.datasource.alert.mailserver");
-
- private static String fromUser = System.getProperty("ebean.datasource.alert.fromUser");
- private static String fromEmail = System.getProperty("ebean.datasource.alert.fromEmail");
- private static String toEmail = System.getProperty("ebean.datasource.alert.toEmail");
-
- /**
- * Create a SimpleAlerter.
- */
- public SimpleDataSourceAlert() {
- }
-
- /**
- * If the email failed then log the error.
- */
- public void handleEvent(MailEvent event) {
- Throwable e = event.getError();
- if (e != null) {
- logger.error(null, e);
- }
- }
-
- /**
- * Send the dataSource down alert.
- */
- @Override
- public void dataSourceDown(String dataSourceName) {
- String msg = getSubject(true, dataSourceName);
- sendMessage(msg, msg);
- }
-
- /**
- * Send the dataSource up alert.
- */
- @Override
- public void dataSourceUp(String dataSourceName) {
- String msg = getSubject(false, dataSourceName);
- sendMessage(msg, msg);
- }
-
- /**
- * Send the warning message.
- */
- @Override
- public void dataSourceWarning(String subject, String msg) {
- sendMessage(subject, msg);
- }
-
- private String getSubject(boolean isDown, String dsName) {
- String msg = "The DataSource " + dsName;
- if (isDown) {
- msg += " is DOWN!!";
- } else {
- msg += " is UP.";
- }
- return msg;
- }
-
- private void sendMessage(String subject, String msg) {
-
- if (alertMailServerName == null) {
- return;
- }
-
- MailMessage data = new MailMessage();
- data.setSender(fromUser, fromEmail);
- data.addBodyLine(msg);
- data.setSubject(subject);
-
- String[] toList = toEmail.split(",");
- if (toList.length == 0) {
- logger.error("alert.toemail has not been set?");
- } else {
- for (int i = 0; i < toList.length; i++) {
- data.addRecipient(null, toList[i].trim());
- }
- MailSender sender = new MailSender(alertMailServerName);
- sender.setMailListener(this);
- sender.sendInBackground(data);
- }
- }
-
-}
+package com.avaje.ebeaninternal.server.lib.sql;
+
+import com.avaje.ebeaninternal.server.lib.util.MailEvent;
+import com.avaje.ebeaninternal.server.lib.util.MailListener;
+import com.avaje.ebeaninternal.server.lib.util.MailMessage;
+import com.avaje.ebeaninternal.server.lib.util.MailSender;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A simple smtp email alert that sends a email message on dataSourceDown and
+ * dataSourceUp etc.
+ *
+ * - alert.fromuser = the from user name
+ *
- alert.fromemail = the from email account
+ *
- alert.toemail = comma delimited list of email accounts to email
+ *
- alert.mailserver = the smpt server name
+ *
+ */
+public class SimpleDataSourceAlert implements DataSourceAlert, MailListener {
+
+ private static final Logger logger = LoggerFactory.getLogger(SimpleDataSourceAlert.class);
+
+ private static String alertMailServerName = System.getProperty("ebean.datasource.alert.mailserver");
+
+ private static String fromUser = System.getProperty("ebean.datasource.alert.fromUser");
+ private static String fromEmail = System.getProperty("ebean.datasource.alert.fromEmail");
+ private static String toEmail = System.getProperty("ebean.datasource.alert.toEmail");
+
+ /**
+ * Create a SimpleAlerter.
+ */
+ public SimpleDataSourceAlert() {
+ }
+
+ /**
+ * If the email failed then log the error.
+ */
+ public void handleEvent(MailEvent event) {
+ Throwable e = event.getError();
+ if (e != null) {
+ logger.error(null, e);
+ }
+ }
+
+ /**
+ * Send the dataSource down alert.
+ */
+ @Override
+ public void dataSourceDown(String dataSourceName) {
+ String msg = getSubject(true, dataSourceName);
+ sendMessage(msg, msg);
+ }
+
+ /**
+ * Send the dataSource up alert.
+ */
+ @Override
+ public void dataSourceUp(String dataSourceName) {
+ String msg = getSubject(false, dataSourceName);
+ sendMessage(msg, msg);
+ }
+
+ /**
+ * Send the warning message.
+ */
+ @Override
+ public void dataSourceWarning(String subject, String msg) {
+ sendMessage(subject, msg);
+ }
+
+ private String getSubject(boolean isDown, String dsName) {
+ String msg = "The DataSource " + dsName;
+ if (isDown) {
+ msg += " is DOWN!!";
+ } else {
+ msg += " is UP.";
+ }
+ return msg;
+ }
+
+ private void sendMessage(String subject, String msg) {
+
+ if (alertMailServerName == null) {
+ return;
+ }
+
+ MailMessage data = new MailMessage();
+ data.setSender(fromUser, fromEmail);
+ data.addBodyLine(msg);
+ data.setSubject(subject);
+
+ String[] toList = toEmail.split(",");
+ if (toList.length == 0) {
+ logger.error("alert.toemail has not been set?");
+ } else {
+ for (int i = 0; i < toList.length; i++) {
+ data.addRecipient(null, toList[i].trim());
+ }
+ MailSender sender = new MailSender(alertMailServerName);
+ sender.setMailListener(this);
+ sender.sendInBackground(data);
+ }
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/TransactionIsolation.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/TransactionIsolation.java
index d637883f6..b87ead5f4 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/TransactionIsolation.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/TransactionIsolation.java
@@ -1,69 +1,69 @@
-package com.avaje.ebeaninternal.server.lib.sql;
-
-import java.sql.Connection;
-
-/**
- * Helper object that can convert between transaction isolation descriptions and values.
- *
- */
-public class TransactionIsolation {
-
-
- /**
- * return the isolation level for a given string description.
- */
- public static int getLevel(String level) {
- level = level.toUpperCase();
- if (level.startsWith("TRANSACTION")){
- level = level.substring("TRANSACTION".length());
- }
- level = level.replace("_", "");
- if ("NONE".equalsIgnoreCase(level)){
- return Connection.TRANSACTION_NONE;
- }
- if ("READCOMMITTED".equalsIgnoreCase(level)){
- return Connection.TRANSACTION_READ_COMMITTED;
- }
- if ("READUNCOMMITTED".equalsIgnoreCase(level)){
- return Connection.TRANSACTION_READ_UNCOMMITTED;
- }
- if ("REPEATABLEREAD".equalsIgnoreCase(level)){
- return Connection.TRANSACTION_REPEATABLE_READ;
- }
- if ("SERIALIZABLE".equalsIgnoreCase(level)){
- return Connection.TRANSACTION_SERIALIZABLE;
- }
-
- throw new RuntimeException("Transaction Isolaction level [" + level + "] is not known.");
- }
-
- /**
- * Return the string description of the transaction isolation level specified.
- * Returned value is one of NONE, READ_COMMITTED,READ_UNCOMMITTED,
- * REPEATABLE_READ or SERIALIZABLE.
- *
- * @param level the transaction isolation level as per java.sql.Connection
- * @return the level description as a string.
- */
- public static String getLevelDescription(int level) {
- switch (level) {
- case Connection.TRANSACTION_NONE :
- return "NONE";
- case Connection.TRANSACTION_READ_COMMITTED :
- return "READ_COMMITTED";
- case Connection.TRANSACTION_READ_UNCOMMITTED :
- return "READ_UNCOMMITTED";
- case Connection.TRANSACTION_REPEATABLE_READ :
- return "REPEATABLE_READ";
- case Connection.TRANSACTION_SERIALIZABLE :
- return "SERIALIZABLE";
- case -1 :
- return "NotSet";
- default :
- throw new RuntimeException("Transaction Isolaction level [" + level + "] is not defined.");
- }
- }
-
-
-
-}
+package com.avaje.ebeaninternal.server.lib.sql;
+
+import java.sql.Connection;
+
+/**
+ * Helper object that can convert between transaction isolation descriptions and values.
+ *
+ */
+public class TransactionIsolation {
+
+
+ /**
+ * return the isolation level for a given string description.
+ */
+ public static int getLevel(String level) {
+ level = level.toUpperCase();
+ if (level.startsWith("TRANSACTION")){
+ level = level.substring("TRANSACTION".length());
+ }
+ level = level.replace("_", "");
+ if ("NONE".equalsIgnoreCase(level)){
+ return Connection.TRANSACTION_NONE;
+ }
+ if ("READCOMMITTED".equalsIgnoreCase(level)){
+ return Connection.TRANSACTION_READ_COMMITTED;
+ }
+ if ("READUNCOMMITTED".equalsIgnoreCase(level)){
+ return Connection.TRANSACTION_READ_UNCOMMITTED;
+ }
+ if ("REPEATABLEREAD".equalsIgnoreCase(level)){
+ return Connection.TRANSACTION_REPEATABLE_READ;
+ }
+ if ("SERIALIZABLE".equalsIgnoreCase(level)){
+ return Connection.TRANSACTION_SERIALIZABLE;
+ }
+
+ throw new RuntimeException("Transaction Isolaction level [" + level + "] is not known.");
+ }
+
+ /**
+ * Return the string description of the transaction isolation level specified.
+ * Returned value is one of NONE, READ_COMMITTED,READ_UNCOMMITTED,
+ * REPEATABLE_READ or SERIALIZABLE.
+ *
+ * @param level the transaction isolation level as per java.sql.Connection
+ * @return the level description as a string.
+ */
+ public static String getLevelDescription(int level) {
+ switch (level) {
+ case Connection.TRANSACTION_NONE :
+ return "NONE";
+ case Connection.TRANSACTION_READ_COMMITTED :
+ return "READ_COMMITTED";
+ case Connection.TRANSACTION_READ_UNCOMMITTED :
+ return "READ_UNCOMMITTED";
+ case Connection.TRANSACTION_REPEATABLE_READ :
+ return "REPEATABLE_READ";
+ case Connection.TRANSACTION_SERIALIZABLE :
+ return "SERIALIZABLE";
+ case -1 :
+ return "NotSet";
+ default :
+ throw new RuntimeException("Transaction Isolaction level [" + level + "] is not defined.");
+ }
+ }
+
+
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/CreateObjectException.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/CreateObjectException.java
index 62aeebeda..80b3ff955 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/util/CreateObjectException.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/CreateObjectException.java
@@ -1,23 +1,23 @@
-package com.avaje.ebeaninternal.server.lib.util;
-
-
-/**
- * A general exception when creating an Object.
- */
-public class CreateObjectException extends RuntimeException
-{
- static final long serialVersionUID = 7061559938704539736L;
-
- public CreateObjectException(Exception cause) {
- super(cause);
- }
-
- public CreateObjectException(String s, Exception cause) {
- super(s, cause);
- }
-
- public CreateObjectException(String s) {
- super(s);
- }
-
-}
+package com.avaje.ebeaninternal.server.lib.util;
+
+
+/**
+ * A general exception when creating an Object.
+ */
+public class CreateObjectException extends RuntimeException
+{
+ static final long serialVersionUID = 7061559938704539736L;
+
+ public CreateObjectException(Exception cause) {
+ super(cause);
+ }
+
+ public CreateObjectException(String s, Exception cause) {
+ super(s, cause);
+ }
+
+ public CreateObjectException(String s) {
+ super(s);
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/Dnode.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/Dnode.java
index 2562aa45a..8b7a0da73 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/util/Dnode.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/Dnode.java
@@ -1,324 +1,324 @@
-package com.avaje.ebeaninternal.server.lib.util;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.LinkedHashMap;
-import java.util.List;
-
-/**
- * A lightweight tree structure for simple XML handling.
- *
- * It removes support for nodes being mixed with content. That is, a node can
- * only contain content or a list of one or more child nodes. It does not
- * support mixing bits of content between the child nodes.
- *
- *
- * Although designed to simplify XML in supported cases it can be used as a
- * general tree structure with attributes of java Objects.
- *
- */
-public class Dnode {
-
- int level;
-
- String nodeName;
-
- String nodeContent;
-
- ArrayList children;
-
- LinkedHashMap attrList = new LinkedHashMap();
-
- /**
- * Create a node.
- */
- public Dnode() {
- }
-
- /**
- * Parse the raw XML string.
- */
- public static Dnode parse(String s){
- DnodeReader r = new DnodeReader();
- return r.parseXml(s);
- }
-
- /**
- * Return the node as XML.
- */
- public String toXml() {
- StringBuilder sb = new StringBuilder();
- generate(sb);
- return sb.toString();
- }
-
- /**
- * Generate this node as xml to the buffer.
- */
- public StringBuilder generate(StringBuilder sb) {
- if (sb == null) {
- sb = new StringBuilder();
- }
- sb.append("<").append(nodeName);
- for (String attr : attrList.keySet()) {
- Object attrValue = getAttribute(attr);
- sb.append(" ").append(attr).append("=\"");
- if (attrValue != null) {
- sb.append(attrValue);
- }
- sb.append("\"");
- }
-
- if (nodeContent == null && !hasChildren()) {
- sb.append(" />");
-
- } else {
- sb.append(">");
- if (children != null && children.size() > 0) {
- for (int i = 0; i < children.size(); i++) {
- Dnode child = children.get(i);
- child.generate(sb);
- }
- }
- if (nodeContent != null) {
- sb.append(nodeContent);
- }
- sb.append("").append(nodeName).append(">");
- }
- return sb;
- }
-
- /**
- * Return the node name.
- */
- public String getNodeName() {
- return nodeName;
- }
-
- /**
- * Set the node name.
- */
- public void setNodeName(String nodeName) {
- this.nodeName = nodeName;
- }
-
- /**
- * Return the node content.
- */
- public String getNodeContent() {
- return nodeContent;
- }
-
- /**
- * Set the node content.
- */
- public void setNodeContent(String nodeContent) {
- this.nodeContent = nodeContent;
- }
-
- /**
- * Return true if this node has children.
- */
- public boolean hasChildren() {
- return getChildrenCount() > 0;
- }
-
- /**
- * Return the number of children this node has.
- */
- public int getChildrenCount() {
- if (children == null) {
- return 0;
- }
- return children.size();
- }
-
- /**
- * Remove a ancestor node.
- */
- public boolean remove(Dnode node) {
- if (children == null) {
- return false;
- }
- if (children.remove(node)) {
- return true;
- }
- for (Dnode child : children) {
- if (child.remove(node)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * List of children nodes.
- */
- public List children() {
- if (children == null) {
- return null;
- }
- return children;
- }
-
- /**
- * Add a child.
- */
- public void addChild(Dnode child) {
- if (children == null) {
- children = new ArrayList();
- }
- children.add(child);
- child.setLevel(level + 1);
- }
-
- /**
- * Return the level or depth of the node from the root.
- */
- public int getLevel() {
- return level;
- }
-
- /**
- * Set the level or depth of this node from the root.
- */
- public void setLevel(int level) {
- this.level = level;
- if (children != null) {
- for (int i = 0; i < children.size(); i++) {
- Dnode child = children.get(i);
- child.setLevel(level + 1);
- }
- }
- }
-
- /**
- * Find the first matching node using nodeName. This is a depth first tree
- * search.
- */
- public Dnode find(String nodeName) {
- return find(nodeName, null, null);
- }
-
- /**
- * Find the first node matching nodeName and attribute value. This is a
- * depth first tree search.
- */
- public Dnode find(String nodeName, String attrName, Object value) {
-
- return find(nodeName, attrName, value, -1);
-
- }
-
- /**
- * Search for a single node with control over maxLevel. Find the first node
- * matching nodeName and attribute value. If attrName and value are null
- * then this will just search using the nodeName. This is a depth first tree
- * search. Once a matching node is found the search will stop.
- */
- public Dnode find(String nodeName, String attrName, Object value, int maxLevel) {
-
- ArrayList list = new ArrayList();
- findByNode(list, nodeName, true, attrName, value, maxLevel);
- if (list.size() >= 1) {
- return list.get(0);
- }
- return null;
- }
-
- /**
- * Find all the nodes that match the nodeName.
- *
- */
- public List findAll(String nodeName, int maxLevel) {
- int level = -1;
- if (maxLevel > 0) {
- level = this.level + maxLevel;
- }
- return findAll(nodeName, null, null, level);
- }
-
- /**
- * Find all the nodes that match the nodeName and attribute value.
- */
- public List findAll(String nodeName, String attrName, Object value, int maxLevel) {
-
- if (nodeName == null && attrName == null) {
- throw new RuntimeException("You can not have both nodeName and attrName null");
- }
- ArrayList list = new ArrayList();
- findByNode(list, nodeName, false, attrName, value, maxLevel);
- return list;
- }
-
- /**
- * Used for recursive calling.
- */
- private void findByNode(List list, String node, boolean findOne,String attrName, Object value, int maxLevel) {
-
- if (findOne && list.size() == 1) {
- return;
- }
- if (node == null || node.equals(nodeName)) {
- if (attrName == null || value.equals(getAttribute(attrName))) {
- list.add(this);
- if (findOne) {
- return;
- }
- }
- }
- if (maxLevel > 0 && level >= maxLevel) {
- // hit max level
-
- } else if (children != null) {
- // recursively search the children
- for (int i = 0; i < children.size(); i++) {
- Dnode child = children.get(i);
- child.findByNode(list, node, findOne, attrName, value,maxLevel);
- }
- }
- }
-
- /**
- * The attribute names as strings.
- */
- public Collection attributeNames() {
- return attrList.keySet();
- }
-
- /**
- * Return the attribute for a given name.
- */
- public String getAttribute(String name) {
- return attrList.get(name);
- }
-
- /**
- * Returns an Attribute as a String.
- *
- * Will throw a ClassCastException if the attribute is not a String.
- *
- */
- public String getStringAttr(String name, String defaultValue) {
- Object o = attrList.get(name);
- if (o == null){
- return defaultValue;
- } else {
- return o.toString();
- }
- }
-
- /**
- * Set an attribute.
- */
- public void setAttribute(String name, String value) {
- attrList.put(name, value);
- }
-
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("[").append(getNodeName()).append(" ").append(attrList).append("]");
- return sb.toString();
- }
-
-}
+package com.avaje.ebeaninternal.server.lib.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+/**
+ * A lightweight tree structure for simple XML handling.
+ *
+ * It removes support for nodes being mixed with content. That is, a node can
+ * only contain content or a list of one or more child nodes. It does not
+ * support mixing bits of content between the child nodes.
+ *
+ *
+ * Although designed to simplify XML in supported cases it can be used as a
+ * general tree structure with attributes of java Objects.
+ *
+ */
+public class Dnode {
+
+ int level;
+
+ String nodeName;
+
+ String nodeContent;
+
+ ArrayList children;
+
+ LinkedHashMap attrList = new LinkedHashMap();
+
+ /**
+ * Create a node.
+ */
+ public Dnode() {
+ }
+
+ /**
+ * Parse the raw XML string.
+ */
+ public static Dnode parse(String s){
+ DnodeReader r = new DnodeReader();
+ return r.parseXml(s);
+ }
+
+ /**
+ * Return the node as XML.
+ */
+ public String toXml() {
+ StringBuilder sb = new StringBuilder();
+ generate(sb);
+ return sb.toString();
+ }
+
+ /**
+ * Generate this node as xml to the buffer.
+ */
+ public StringBuilder generate(StringBuilder sb) {
+ if (sb == null) {
+ sb = new StringBuilder();
+ }
+ sb.append("<").append(nodeName);
+ for (String attr : attrList.keySet()) {
+ Object attrValue = getAttribute(attr);
+ sb.append(" ").append(attr).append("=\"");
+ if (attrValue != null) {
+ sb.append(attrValue);
+ }
+ sb.append("\"");
+ }
+
+ if (nodeContent == null && !hasChildren()) {
+ sb.append(" />");
+
+ } else {
+ sb.append(">");
+ if (children != null && children.size() > 0) {
+ for (int i = 0; i < children.size(); i++) {
+ Dnode child = children.get(i);
+ child.generate(sb);
+ }
+ }
+ if (nodeContent != null) {
+ sb.append(nodeContent);
+ }
+ sb.append("").append(nodeName).append(">");
+ }
+ return sb;
+ }
+
+ /**
+ * Return the node name.
+ */
+ public String getNodeName() {
+ return nodeName;
+ }
+
+ /**
+ * Set the node name.
+ */
+ public void setNodeName(String nodeName) {
+ this.nodeName = nodeName;
+ }
+
+ /**
+ * Return the node content.
+ */
+ public String getNodeContent() {
+ return nodeContent;
+ }
+
+ /**
+ * Set the node content.
+ */
+ public void setNodeContent(String nodeContent) {
+ this.nodeContent = nodeContent;
+ }
+
+ /**
+ * Return true if this node has children.
+ */
+ public boolean hasChildren() {
+ return getChildrenCount() > 0;
+ }
+
+ /**
+ * Return the number of children this node has.
+ */
+ public int getChildrenCount() {
+ if (children == null) {
+ return 0;
+ }
+ return children.size();
+ }
+
+ /**
+ * Remove a ancestor node.
+ */
+ public boolean remove(Dnode node) {
+ if (children == null) {
+ return false;
+ }
+ if (children.remove(node)) {
+ return true;
+ }
+ for (Dnode child : children) {
+ if (child.remove(node)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * List of children nodes.
+ */
+ public List children() {
+ if (children == null) {
+ return null;
+ }
+ return children;
+ }
+
+ /**
+ * Add a child.
+ */
+ public void addChild(Dnode child) {
+ if (children == null) {
+ children = new ArrayList();
+ }
+ children.add(child);
+ child.setLevel(level + 1);
+ }
+
+ /**
+ * Return the level or depth of the node from the root.
+ */
+ public int getLevel() {
+ return level;
+ }
+
+ /**
+ * Set the level or depth of this node from the root.
+ */
+ public void setLevel(int level) {
+ this.level = level;
+ if (children != null) {
+ for (int i = 0; i < children.size(); i++) {
+ Dnode child = children.get(i);
+ child.setLevel(level + 1);
+ }
+ }
+ }
+
+ /**
+ * Find the first matching node using nodeName. This is a depth first tree
+ * search.
+ */
+ public Dnode find(String nodeName) {
+ return find(nodeName, null, null);
+ }
+
+ /**
+ * Find the first node matching nodeName and attribute value. This is a
+ * depth first tree search.
+ */
+ public Dnode find(String nodeName, String attrName, Object value) {
+
+ return find(nodeName, attrName, value, -1);
+
+ }
+
+ /**
+ * Search for a single node with control over maxLevel. Find the first node
+ * matching nodeName and attribute value. If attrName and value are null
+ * then this will just search using the nodeName. This is a depth first tree
+ * search. Once a matching node is found the search will stop.
+ */
+ public Dnode find(String nodeName, String attrName, Object value, int maxLevel) {
+
+ ArrayList list = new ArrayList();
+ findByNode(list, nodeName, true, attrName, value, maxLevel);
+ if (list.size() >= 1) {
+ return list.get(0);
+ }
+ return null;
+ }
+
+ /**
+ * Find all the nodes that match the nodeName.
+ *
+ */
+ public List findAll(String nodeName, int maxLevel) {
+ int level = -1;
+ if (maxLevel > 0) {
+ level = this.level + maxLevel;
+ }
+ return findAll(nodeName, null, null, level);
+ }
+
+ /**
+ * Find all the nodes that match the nodeName and attribute value.
+ */
+ public List findAll(String nodeName, String attrName, Object value, int maxLevel) {
+
+ if (nodeName == null && attrName == null) {
+ throw new RuntimeException("You can not have both nodeName and attrName null");
+ }
+ ArrayList list = new ArrayList();
+ findByNode(list, nodeName, false, attrName, value, maxLevel);
+ return list;
+ }
+
+ /**
+ * Used for recursive calling.
+ */
+ private void findByNode(List list, String node, boolean findOne,String attrName, Object value, int maxLevel) {
+
+ if (findOne && list.size() == 1) {
+ return;
+ }
+ if (node == null || node.equals(nodeName)) {
+ if (attrName == null || value.equals(getAttribute(attrName))) {
+ list.add(this);
+ if (findOne) {
+ return;
+ }
+ }
+ }
+ if (maxLevel > 0 && level >= maxLevel) {
+ // hit max level
+
+ } else if (children != null) {
+ // recursively search the children
+ for (int i = 0; i < children.size(); i++) {
+ Dnode child = children.get(i);
+ child.findByNode(list, node, findOne, attrName, value,maxLevel);
+ }
+ }
+ }
+
+ /**
+ * The attribute names as strings.
+ */
+ public Collection attributeNames() {
+ return attrList.keySet();
+ }
+
+ /**
+ * Return the attribute for a given name.
+ */
+ public String getAttribute(String name) {
+ return attrList.get(name);
+ }
+
+ /**
+ * Returns an Attribute as a String.
+ *
+ * Will throw a ClassCastException if the attribute is not a String.
+ *
+ */
+ public String getStringAttr(String name, String defaultValue) {
+ Object o = attrList.get(name);
+ if (o == null){
+ return defaultValue;
+ } else {
+ return o.toString();
+ }
+ }
+
+ /**
+ * Set an attribute.
+ */
+ public void setAttribute(String name, String value) {
+ attrList.put(name, value);
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[").append(getNodeName()).append(" ").append(attrList).append("]");
+ return sb.toString();
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/DnodeParser.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/DnodeParser.java
index eb40ab7ba..736e515bb 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/util/DnodeParser.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/DnodeParser.java
@@ -1,200 +1,200 @@
-package com.avaje.ebeaninternal.server.lib.util;
-
-import java.util.Stack;
-
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
-
-/**
- * Parse an xml document into a Dnode tree.
- */
-public class DnodeParser extends DefaultHandler {
-
- /**
- * The root of the DContent tree.
- */
- Dnode root;
-
- /**
- * The current node being parsed.
- */
- Dnode currentNode;
-
- /**
- * The nodeContent buffer.
- */
- StringBuilder buffer;
-
- /**
- * Used to stack the nodes.
- */
- Stack stack = new Stack();
-
- /**
- * The class used to construct new nodes. Should be Dnode or a subtype of
- * Dnode.
- */
- Class> nodeClass = Dnode.class;
-
- int depth = 0;
-
- /**
- * Trim whitespace from the content.
- */
- boolean trimWhitespace = true;
-
- /**
- * The name of the tag that contains html content
- */
- String contentName;
-
- /**
- * The depth of the tag that contains the html content
- */
- int contentDepth;
-
-
- /**
- * If true then trim the whitespace from the content.
- */
- public boolean isTrimWhitespace() {
- return trimWhitespace;
- }
-
- /**
- * Set whether to trim whitespace from the content.
- */
- public void setTrimWhitespace(boolean trimWhitespace) {
- this.trimWhitespace = trimWhitespace;
- }
-
- /**
- * Return the root node of the DContent tree.
- */
- public Dnode getRoot() {
- return root;
- }
-
- /**
- * Set the type class of node to be created.
- */
- public void setNodeClass(Class> nodeClass) {
- this.nodeClass = nodeClass;
- }
-
- /**
- * Create a new Dnode using the nodeClass.
- */
- private Dnode createNewNode() {
- try {
- return (Dnode) nodeClass.newInstance();
- } catch (Exception ex) {
- throw new RuntimeException(ex);
- }
- }
-
-
- /**
- * process a startElement.
- */
- public void startElement(String uri, String localName, String qName, Attributes attributes)
- throws SAXException {
-
- super.startElement(uri, localName, qName, attributes);
- depth++;
-
- boolean isContent = (contentName != null);
-
- if (isContent){
- // must be html content... add the begin tag as content
- buffer.append("<").append(localName);
- for (int i = 0; i < attributes.getLength(); i++) {
- String key = attributes.getLocalName(i);
- String val = attributes.getValue(i);
- buffer.append(" ").append(key).append("='").append(val).append("'");
- }
- buffer.append(">");
- return;
-
- }
-
- buffer = new StringBuilder();
- Dnode node = createNewNode();
- node.setNodeName(localName);
- for (int i = 0; i < attributes.getLength(); i++) {
- String key = attributes.getLocalName(i);
- String val = attributes.getValue(i);
- node.setAttribute(key, val);
- if ("type".equalsIgnoreCase(key) && "content".equalsIgnoreCase(val)) {
- // this tag contains html content
- // no more nodes until end tag is found
- contentName = localName;
- contentDepth = depth-1;
- }
-
- }
- if (root == null) {
- root = node;
- }
- if (currentNode != null) {
- currentNode.addChild(node);
- }
- stack.push(node);
- currentNode = node;
-
- }
-
- /**
- * append the node content.
- */
- public void characters(char[] ch, int start, int length) throws SAXException {
- super.characters(ch, start, length);
- String s = new String(ch, start, length);
- int p = s.indexOf('\r');
- int p2 = s.indexOf('\n');
- if (p == -1 && p2 > -1) {
- // This is probably not an issue but tidys up content
- // in my text editor
- s = StringHelper.replaceString(s, "\n", "\r\n");
- }
- buffer.append(s);
- }
-
- /**
- * process the endElement.
- */
- public void endElement(String uri, String localName, String qName) throws SAXException {
- super.endElement(uri, localName, qName);
- depth--;
-
- if (contentName != null){
- // is this the end of the content?
- if (contentName.equals(localName) && contentDepth == depth){
- contentName = null;
-
- } else {
- // the html content end tag
- buffer.append("").append(localName).append(">");
- }
- return;
- }
- String content = buffer.toString();
- buffer.setLength(0);
- if (content.length() > 0) {
- if (trimWhitespace) {
- content = content.trim();
- }
- if (content.length() > 0) {
- currentNode.setNodeContent(content);
- }
- }
- stack.pop();
- if (!stack.isEmpty()) {
- // get the new currentNode
- currentNode = (Dnode) stack.pop();
- stack.push(currentNode);
- }
- }
-
-}
+package com.avaje.ebeaninternal.server.lib.util;
+
+import java.util.Stack;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Parse an xml document into a Dnode tree.
+ */
+public class DnodeParser extends DefaultHandler {
+
+ /**
+ * The root of the DContent tree.
+ */
+ Dnode root;
+
+ /**
+ * The current node being parsed.
+ */
+ Dnode currentNode;
+
+ /**
+ * The nodeContent buffer.
+ */
+ StringBuilder buffer;
+
+ /**
+ * Used to stack the nodes.
+ */
+ Stack stack = new Stack();
+
+ /**
+ * The class used to construct new nodes. Should be Dnode or a subtype of
+ * Dnode.
+ */
+ Class> nodeClass = Dnode.class;
+
+ int depth = 0;
+
+ /**
+ * Trim whitespace from the content.
+ */
+ boolean trimWhitespace = true;
+
+ /**
+ * The name of the tag that contains html content
+ */
+ String contentName;
+
+ /**
+ * The depth of the tag that contains the html content
+ */
+ int contentDepth;
+
+
+ /**
+ * If true then trim the whitespace from the content.
+ */
+ public boolean isTrimWhitespace() {
+ return trimWhitespace;
+ }
+
+ /**
+ * Set whether to trim whitespace from the content.
+ */
+ public void setTrimWhitespace(boolean trimWhitespace) {
+ this.trimWhitespace = trimWhitespace;
+ }
+
+ /**
+ * Return the root node of the DContent tree.
+ */
+ public Dnode getRoot() {
+ return root;
+ }
+
+ /**
+ * Set the type class of node to be created.
+ */
+ public void setNodeClass(Class> nodeClass) {
+ this.nodeClass = nodeClass;
+ }
+
+ /**
+ * Create a new Dnode using the nodeClass.
+ */
+ private Dnode createNewNode() {
+ try {
+ return (Dnode) nodeClass.newInstance();
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+
+ /**
+ * process a startElement.
+ */
+ public void startElement(String uri, String localName, String qName, Attributes attributes)
+ throws SAXException {
+
+ super.startElement(uri, localName, qName, attributes);
+ depth++;
+
+ boolean isContent = (contentName != null);
+
+ if (isContent){
+ // must be html content... add the begin tag as content
+ buffer.append("<").append(localName);
+ for (int i = 0; i < attributes.getLength(); i++) {
+ String key = attributes.getLocalName(i);
+ String val = attributes.getValue(i);
+ buffer.append(" ").append(key).append("='").append(val).append("'");
+ }
+ buffer.append(">");
+ return;
+
+ }
+
+ buffer = new StringBuilder();
+ Dnode node = createNewNode();
+ node.setNodeName(localName);
+ for (int i = 0; i < attributes.getLength(); i++) {
+ String key = attributes.getLocalName(i);
+ String val = attributes.getValue(i);
+ node.setAttribute(key, val);
+ if ("type".equalsIgnoreCase(key) && "content".equalsIgnoreCase(val)) {
+ // this tag contains html content
+ // no more nodes until end tag is found
+ contentName = localName;
+ contentDepth = depth-1;
+ }
+
+ }
+ if (root == null) {
+ root = node;
+ }
+ if (currentNode != null) {
+ currentNode.addChild(node);
+ }
+ stack.push(node);
+ currentNode = node;
+
+ }
+
+ /**
+ * append the node content.
+ */
+ public void characters(char[] ch, int start, int length) throws SAXException {
+ super.characters(ch, start, length);
+ String s = new String(ch, start, length);
+ int p = s.indexOf('\r');
+ int p2 = s.indexOf('\n');
+ if (p == -1 && p2 > -1) {
+ // This is probably not an issue but tidys up content
+ // in my text editor
+ s = StringHelper.replaceString(s, "\n", "\r\n");
+ }
+ buffer.append(s);
+ }
+
+ /**
+ * process the endElement.
+ */
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ super.endElement(uri, localName, qName);
+ depth--;
+
+ if (contentName != null){
+ // is this the end of the content?
+ if (contentName.equals(localName) && contentDepth == depth){
+ contentName = null;
+
+ } else {
+ // the html content end tag
+ buffer.append("").append(localName).append(">");
+ }
+ return;
+ }
+ String content = buffer.toString();
+ buffer.setLength(0);
+ if (content.length() > 0) {
+ if (trimWhitespace) {
+ content = content.trim();
+ }
+ if (content.length() > 0) {
+ currentNode.setNodeContent(content);
+ }
+ }
+ stack.pop();
+ if (!stack.isEmpty()) {
+ // get the new currentNode
+ currentNode = (Dnode) stack.pop();
+ stack.push(currentNode);
+ }
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/DnodeReader.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/DnodeReader.java
index 23da20240..0514bb20c 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/util/DnodeReader.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/DnodeReader.java
@@ -1,70 +1,70 @@
-package com.avaje.ebeaninternal.server.lib.util;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStreamWriter;
-import java.io.StringReader;
-
-import org.xml.sax.InputSource;
-import org.xml.sax.XMLReader;
-import org.xml.sax.helpers.XMLReaderFactory;
-
-/**
- * Parses an XML inputstream returning a Dnode tree.
- */
-public class DnodeReader {
-
- public Dnode parseXml(String str) {
-
- try {
- ByteArrayOutputStream bao = new ByteArrayOutputStream(str.length());
- OutputStreamWriter osw = new OutputStreamWriter(bao);
-
- StringReader sr = new StringReader(str);
-
- int charBufferSize = 1024;
- char[] buf = new char[charBufferSize];
- int len;
- while ((len = sr.read(buf, 0, buf.length)) != -1) {
- osw.write(buf, 0, len);
- }
- sr.close();
- osw.flush();
- osw.close();
-
- bao.flush();
- bao.close();
-
- InputStream is = new ByteArrayInputStream(bao.toByteArray());
- return parseXml(is);
-
- } catch (IOException ex){
- throw new RuntimeException(ex);
- }
- }
-
- /**
- * Parse the XML inputstream returning the Dnode tree.
- */
- public Dnode parseXml(InputStream in) {
-
- try {
- InputSource inSource = new InputSource(in);
-
- DnodeParser parser = new DnodeParser();
-
- XMLReader myReader = XMLReaderFactory.createXMLReader();
- myReader.setContentHandler(parser);
-
- myReader.parse(inSource);
-
- return parser.getRoot();
-
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
-}
+package com.avaje.ebeaninternal.server.lib.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.io.StringReader;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+/**
+ * Parses an XML inputstream returning a Dnode tree.
+ */
+public class DnodeReader {
+
+ public Dnode parseXml(String str) {
+
+ try {
+ ByteArrayOutputStream bao = new ByteArrayOutputStream(str.length());
+ OutputStreamWriter osw = new OutputStreamWriter(bao);
+
+ StringReader sr = new StringReader(str);
+
+ int charBufferSize = 1024;
+ char[] buf = new char[charBufferSize];
+ int len;
+ while ((len = sr.read(buf, 0, buf.length)) != -1) {
+ osw.write(buf, 0, len);
+ }
+ sr.close();
+ osw.flush();
+ osw.close();
+
+ bao.flush();
+ bao.close();
+
+ InputStream is = new ByteArrayInputStream(bao.toByteArray());
+ return parseXml(is);
+
+ } catch (IOException ex){
+ throw new RuntimeException(ex);
+ }
+ }
+
+ /**
+ * Parse the XML inputstream returning the Dnode tree.
+ */
+ public Dnode parseXml(InputStream in) {
+
+ try {
+ InputSource inSource = new InputSource(in);
+
+ DnodeParser parser = new DnodeParser();
+
+ XMLReader myReader = XMLReaderFactory.createXMLReader();
+ myReader.setContentHandler(parser);
+
+ myReader.parse(inSource);
+
+ return parser.getRoot();
+
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/GeneralException.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/GeneralException.java
index e2377403e..396fa7c90 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/util/GeneralException.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/GeneralException.java
@@ -1,22 +1,22 @@
-package com.avaje.ebeaninternal.server.lib.util;
-
-/**
- * A general exception that can be used for multiple purposes.
- */
-public class GeneralException extends RuntimeException {
-
- private static final long serialVersionUID = 5783084420007103280L;
-
- public GeneralException(Exception cause) {
- super(cause);
- }
-
- public GeneralException(String s, Exception cause) {
- super(s, cause);
- }
-
- public GeneralException(String s) {
- super(s);
- }
-
-}
+package com.avaje.ebeaninternal.server.lib.util;
+
+/**
+ * A general exception that can be used for multiple purposes.
+ */
+public class GeneralException extends RuntimeException {
+
+ private static final long serialVersionUID = 5783084420007103280L;
+
+ public GeneralException(Exception cause) {
+ super(cause);
+ }
+
+ public GeneralException(String s, Exception cause) {
+ super(s, cause);
+ }
+
+ public GeneralException(String s) {
+ super(s);
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/InvalidDataException.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/InvalidDataException.java
index c27a94245..90f57223b 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/util/InvalidDataException.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/InvalidDataException.java
@@ -1,23 +1,23 @@
-package com.avaje.ebeaninternal.server.lib.util;
-
-
-/**
- * A general exception for invalid data.
- */
-public class InvalidDataException extends RuntimeException
-{
- static final long serialVersionUID = 7061559938704539846L;
-
- public InvalidDataException(Exception cause) {
- super(cause);
- }
-
- public InvalidDataException(String s, Exception cause) {
- super(s, cause);
- }
-
- public InvalidDataException(String s) {
- super(s);
- }
-
-}
+package com.avaje.ebeaninternal.server.lib.util;
+
+
+/**
+ * A general exception for invalid data.
+ */
+public class InvalidDataException extends RuntimeException
+{
+ static final long serialVersionUID = 7061559938704539846L;
+
+ public InvalidDataException(Exception cause) {
+ super(cause);
+ }
+
+ public InvalidDataException(String s, Exception cause) {
+ super(s, cause);
+ }
+
+ public InvalidDataException(String s) {
+ super(s);
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailAddress.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailAddress.java
index c6543db5c..e826cda6b 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailAddress.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailAddress.java
@@ -1,45 +1,45 @@
-package com.avaje.ebeaninternal.server.lib.util;
-
-/**
- * An Email address with an associated alias.
- */
-public class MailAddress {
-
-
- String alias;
-
- String emailAddress;
-
- /**
- * Create an address with an optional alias.
- */
- public MailAddress(String alias, String emailAddress){
- this.alias = alias;
- this.emailAddress = emailAddress;
- }
-
- /**
- * Return the alias.
- * If the alias is null this returns an empty string.
- */
- public String getAlias() {
- if (alias == null){
- return "";
- }
- return alias;
- }
-
- /**
- * Return the email address.
- */
- public String getEmailAddress(){
- return emailAddress;
- }
-
- public String toString() {
- StringBuffer sb = new StringBuffer();
- sb.append(getAlias()).append(" ").append("<").append(getEmailAddress()).append(">");
- return sb.toString();
- }
-
-}
+package com.avaje.ebeaninternal.server.lib.util;
+
+/**
+ * An Email address with an associated alias.
+ */
+public class MailAddress {
+
+
+ String alias;
+
+ String emailAddress;
+
+ /**
+ * Create an address with an optional alias.
+ */
+ public MailAddress(String alias, String emailAddress){
+ this.alias = alias;
+ this.emailAddress = emailAddress;
+ }
+
+ /**
+ * Return the alias.
+ * If the alias is null this returns an empty string.
+ */
+ public String getAlias() {
+ if (alias == null){
+ return "";
+ }
+ return alias;
+ }
+
+ /**
+ * Return the email address.
+ */
+ public String getEmailAddress(){
+ return emailAddress;
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(getAlias()).append(" ").append("<").append(getEmailAddress()).append(">");
+ return sb.toString();
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailEvent.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailEvent.java
index ddeed0941..59ef0a1e6 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailEvent.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailEvent.java
@@ -1,49 +1,49 @@
-package com.avaje.ebeaninternal.server.lib.util;
-
-/**
- * Represents the success or failure of a mail send.
- */
-public class MailEvent {
-
-
- /**
- * The error indicating a send failure.
- */
- Throwable error;
-
- /**
- * The message that was sent.
- */
- MailMessage message;
-
-
- /**
- * The message send failed with an error.
- */
- public MailEvent(MailMessage message, Throwable error){
- this.message = message;
- this.error = error;
- }
-
- /**
- * The message that we attempted to send.
- */
- public MailMessage getMailMessage() {
- return message;
- }
-
- /**
- * Returns true if the message was sent successfully.
- */
- public boolean wasSuccessful() {
- return (error == null);
- }
-
- /**
- * The error indicating the send failed.
- */
- public Throwable getError() {
- return error;
- }
-
-}
+package com.avaje.ebeaninternal.server.lib.util;
+
+/**
+ * Represents the success or failure of a mail send.
+ */
+public class MailEvent {
+
+
+ /**
+ * The error indicating a send failure.
+ */
+ Throwable error;
+
+ /**
+ * The message that was sent.
+ */
+ MailMessage message;
+
+
+ /**
+ * The message send failed with an error.
+ */
+ public MailEvent(MailMessage message, Throwable error){
+ this.message = message;
+ this.error = error;
+ }
+
+ /**
+ * The message that we attempted to send.
+ */
+ public MailMessage getMailMessage() {
+ return message;
+ }
+
+ /**
+ * Returns true if the message was sent successfully.
+ */
+ public boolean wasSuccessful() {
+ return (error == null);
+ }
+
+ /**
+ * The error indicating the send failed.
+ */
+ public Throwable getError() {
+ return error;
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailListener.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailListener.java
index 43d47a61a..d1aa89a8a 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailListener.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailListener.java
@@ -1,13 +1,13 @@
-package com.avaje.ebeaninternal.server.lib.util;
-
-/**
- * Listens to see if the message was successfully sent.
- */
-public interface MailListener {
-
- /**
- * Handle the message event.
- */
- public void handleEvent(MailEvent event);
-
-}
+package com.avaje.ebeaninternal.server.lib.util;
+
+/**
+ * Listens to see if the message was successfully sent.
+ */
+public interface MailListener {
+
+ /**
+ * Handle the message event.
+ */
+ public void handleEvent(MailEvent event);
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailMessage.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailMessage.java
index 6c838d5ce..0ad5d214b 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailMessage.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailMessage.java
@@ -1,157 +1,157 @@
-package com.avaje.ebeaninternal.server.lib.util;
-
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * A simple test message that can be sent via smtp.
- */
-public class MailMessage {
-
-// /**
-// * The subject text.
-// */
-// String subject;
-
- /**
- * The body content.
- */
- ArrayList bodylines;
-
- /**
- * The sender email address.
- */
- MailAddress senderAddress;
-
- /**
- * The headers.
- */
- HashMap header = new HashMap();
-
- /**
- * the recipient of the email.
- */
- MailAddress currentRecipient;
-
- /**
- * The list of recipients.
- */
- ArrayList recipientList = new ArrayList();
-
- /**
- * Create the message.
- */
- public MailMessage() {
- bodylines = new ArrayList();
- }
-
- /**
- * Set the current recipient.
- */
- public void setCurrentRecipient(MailAddress currentRecipient){
- this.currentRecipient = currentRecipient;
- }
-
- /**
- * Return the current recipient.
- */
- public MailAddress getCurrentRecipient() {
- return currentRecipient;
- }
-
- /**
- * Add a recipient.
- */
- public void addRecipient(String alias, String emailAddress){
- recipientList.add(new MailAddress(alias, emailAddress));
- }
-
- /**
- * Set the sender details.
- */
- public void setSender(String alias, String senderEmail){
- this.senderAddress = new MailAddress(alias, senderEmail);
- }
- /**
- * Return the sender address.
- */
- public MailAddress getSender() {
- return senderAddress;
- }
-
- /**
- * Return the recipient list.
- */
- public List getRecipientList() {
- return recipientList;
- }
-
- /**
- * add a header to the message.
- */
- public void addHeader(String key, String val) {
- header.put(key, val);
- }
-
- /**
- * Set the subject text.
- */
- public void setSubject(String subject){
- addHeader("Subject", subject);
- }
-
- /**
- * Return the subject text.
- */
- public String getSubject() {
- return getHeader("Subject");
- }
-
- /**
- * Add text to the body.
- */
- public void addBodyLine(String line) {
- bodylines.add(line);
- }
-
- /**
- * Return the body text.
- */
- public List getBodyLines() {
- return bodylines;
- }
-
- /**
- * Return the headers.
- */
- public Collection getHeaderFields() {
- return header.keySet();
- }
-
- /**
- * Return a given header.
- */
- public String getHeader(String key) {
- return header.get(key);
- }
-
- public String toString() {
- StringBuilder sb = new StringBuilder(100);
- sb.append("Sender: " + senderAddress + "\tRecipient: " + recipientList + "\n");
- for (String key : header.keySet()) {
- String hline = key + ": " + header.get(key) + "\n";
- sb.append(hline);
- }
- sb.append("\n");
- for (String line : bodylines) {
- sb.append(line).append("\n");
- }
- return sb.toString();
- }
-}
-
-
-
+package com.avaje.ebeaninternal.server.lib.util;
+
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * A simple test message that can be sent via smtp.
+ */
+public class MailMessage {
+
+// /**
+// * The subject text.
+// */
+// String subject;
+
+ /**
+ * The body content.
+ */
+ ArrayList bodylines;
+
+ /**
+ * The sender email address.
+ */
+ MailAddress senderAddress;
+
+ /**
+ * The headers.
+ */
+ HashMap header = new HashMap();
+
+ /**
+ * the recipient of the email.
+ */
+ MailAddress currentRecipient;
+
+ /**
+ * The list of recipients.
+ */
+ ArrayList recipientList = new ArrayList();
+
+ /**
+ * Create the message.
+ */
+ public MailMessage() {
+ bodylines = new ArrayList();
+ }
+
+ /**
+ * Set the current recipient.
+ */
+ public void setCurrentRecipient(MailAddress currentRecipient){
+ this.currentRecipient = currentRecipient;
+ }
+
+ /**
+ * Return the current recipient.
+ */
+ public MailAddress getCurrentRecipient() {
+ return currentRecipient;
+ }
+
+ /**
+ * Add a recipient.
+ */
+ public void addRecipient(String alias, String emailAddress){
+ recipientList.add(new MailAddress(alias, emailAddress));
+ }
+
+ /**
+ * Set the sender details.
+ */
+ public void setSender(String alias, String senderEmail){
+ this.senderAddress = new MailAddress(alias, senderEmail);
+ }
+ /**
+ * Return the sender address.
+ */
+ public MailAddress getSender() {
+ return senderAddress;
+ }
+
+ /**
+ * Return the recipient list.
+ */
+ public List getRecipientList() {
+ return recipientList;
+ }
+
+ /**
+ * add a header to the message.
+ */
+ public void addHeader(String key, String val) {
+ header.put(key, val);
+ }
+
+ /**
+ * Set the subject text.
+ */
+ public void setSubject(String subject){
+ addHeader("Subject", subject);
+ }
+
+ /**
+ * Return the subject text.
+ */
+ public String getSubject() {
+ return getHeader("Subject");
+ }
+
+ /**
+ * Add text to the body.
+ */
+ public void addBodyLine(String line) {
+ bodylines.add(line);
+ }
+
+ /**
+ * Return the body text.
+ */
+ public List getBodyLines() {
+ return bodylines;
+ }
+
+ /**
+ * Return the headers.
+ */
+ public Collection getHeaderFields() {
+ return header.keySet();
+ }
+
+ /**
+ * Return a given header.
+ */
+ public String getHeader(String key) {
+ return header.get(key);
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder(100);
+ sb.append("Sender: " + senderAddress + "\tRecipient: " + recipientList + "\n");
+ for (String key : header.keySet()) {
+ String hline = key + ": " + header.get(key) + "\n";
+ sb.append(hline);
+ }
+ sb.append("\n");
+ for (String line : bodylines) {
+ sb.append(line).append("\n");
+ }
+ return sb.toString();
+ }
+}
+
+
+
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailSender.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailSender.java
index 03e52b901..c64f64696 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailSender.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MailSender.java
@@ -1,206 +1,206 @@
-package com.avaje.ebeaninternal.server.lib.util;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.net.UnknownHostException;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Sends simple MailMessages via smtp.
- */
-public class MailSender implements Runnable {
-
- private static final Logger logger = LoggerFactory.getLogger(MailSender.class);
-
- int traceLevel = 0;
-
- Socket sserver;
- String server;
-
- BufferedReader in;
-
- OutputStreamWriter out;
-
- MailMessage message;
-
- MailListener listener = null;
-
- private static final int SMTP_PORT = 25;
-
- /**
- * Create for a given mail server.
- */
- public MailSender(String server) {
- this.server = server;
- }
-
- /**
- * Set the listener to handle MessageEvents.
- */
- public void setMailListener(MailListener listener) {
- this.listener = listener;
- }
-
- /**
- * Send the message.
- */
- public void run() {
- send(message);
- }
-
- /**
- * Send the message in a background thread.
- */
- public void sendInBackground(MailMessage message) {
- this.message = message;
- Thread thread = new Thread(this);
- thread.start();
- }
-
- /**
- * Send the message in the current thread.
- */
- public void send(MailMessage message) {
- try {
- for (MailAddress recipientAddress : message.getRecipientList()) {
- sserver = new Socket(server, SMTP_PORT);
- send(message, sserver, recipientAddress);
- sserver.close();
-
- if (listener != null) {
- MailEvent event = new MailEvent(message, null);
- listener.handleEvent(event);
- }
- }
- } catch (Exception ex) {
- if (listener != null) {
- MailEvent event = new MailEvent(message, ex);
- listener.handleEvent(event);
- } else {
- logger.error(null, ex);
- }
- }
- }
-
- private void send(MailMessage message, Socket sserver, MailAddress recipientAddress) throws IOException {
-
- // A bit convoluted, but doesn't depend on DNS in any way...
- InetAddress localhost = sserver.getLocalAddress();
- String localaddress = localhost.getHostAddress();
- MailAddress sender = message.getSender();
- message.setCurrentRecipient(recipientAddress);
-
- // Mandatory header fields, Date and From
- if (message.getHeader("Date") == null) {
- message.addHeader("Date", new java.util.Date().toString());
- }
- if (message.getHeader("From") == null) {
- message.addHeader("From", sender.getAlias() + " <" + sender.getEmailAddress() + ">");
- }
-
- // if (message.getHeader("From") == null){
- message.addHeader("To", recipientAddress.getAlias() + " <" + recipientAddress.getEmailAddress() + ">");
- // }
-
- out = new OutputStreamWriter(sserver.getOutputStream());
- in = new BufferedReader(new InputStreamReader(sserver.getInputStream()));
- String sintro = readln();
- if (!sintro.startsWith("220")) { // 220
- logger.debug("SmtpSender: intro==" + sintro);
- return;
- }
-
- writeln("EHLO " + localaddress);
- if (!expect250()) {
- return;
- }
-
- writeln("MAIL FROM:<" + sender.getEmailAddress() + ">");
- if (!expect250()) {
- return;
- }
- writeln("RCPT TO:<" + recipientAddress.getEmailAddress() + ">");
- if (!expect250()) {
- return;
- }
- writeln("DATA");
- while (true) { // may be multiple 250 replies pending from server
- String line = readln();
- if (line.startsWith("3"))
- break; // ready to send
- if (!line.startsWith("2")) {
- logger.debug("SmtpSender.send reponse to DATA: " + line);
- return;
- }
- }
- for (String key : message.getHeaderFields()) {
- writeln(key + ": " + message.getHeader(key));
- }
- writeln(""); // end of header;
- for (String bline : message.getBodyLines()) {
- if (bline.startsWith(".")) {
- bline = "." + bline;
- }
- writeln(bline);
- }
- writeln(".");
- expect250();
- writeln("QUIT");
-
- }
-
- private boolean expect250() throws IOException {
- String line = readln();
- if (!line.startsWith("2")) {
- logger.info("SmtpSender.expect250: " + line);
- return false;
- }
- return true;
- }
-
- private void writeln(String s) throws IOException {
- if (traceLevel > 2) {
- logger.debug("From client: " + s);
- }
- out.write(s + "\r\n");
- out.flush();
- }
-
- private String readln() throws IOException {
- String line = in.readLine();
- if (traceLevel > 1) {
- logger.debug("From server: " + line);
- }
- return line;
- }
-
- /**
- * Set the trace level.
- */
- public void setTraceLevel(int traceLevel) {
- this.traceLevel = traceLevel;
- }
-
- /**
- * Return the hostname of the local machine.
- */
- public String getLocalHostName() {
- try {
- InetAddress ipaddress = InetAddress.getLocalHost();
- String localHost = ipaddress.getHostName();
- if (localHost == null) {
- return "localhost";
- } else {
- return localHost;
- }
- } catch (UnknownHostException e) {
- return "localhost";
- }
- }
-}
+package com.avaje.ebeaninternal.server.lib.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Sends simple MailMessages via smtp.
+ */
+public class MailSender implements Runnable {
+
+ private static final Logger logger = LoggerFactory.getLogger(MailSender.class);
+
+ int traceLevel = 0;
+
+ Socket sserver;
+ String server;
+
+ BufferedReader in;
+
+ OutputStreamWriter out;
+
+ MailMessage message;
+
+ MailListener listener = null;
+
+ private static final int SMTP_PORT = 25;
+
+ /**
+ * Create for a given mail server.
+ */
+ public MailSender(String server) {
+ this.server = server;
+ }
+
+ /**
+ * Set the listener to handle MessageEvents.
+ */
+ public void setMailListener(MailListener listener) {
+ this.listener = listener;
+ }
+
+ /**
+ * Send the message.
+ */
+ public void run() {
+ send(message);
+ }
+
+ /**
+ * Send the message in a background thread.
+ */
+ public void sendInBackground(MailMessage message) {
+ this.message = message;
+ Thread thread = new Thread(this);
+ thread.start();
+ }
+
+ /**
+ * Send the message in the current thread.
+ */
+ public void send(MailMessage message) {
+ try {
+ for (MailAddress recipientAddress : message.getRecipientList()) {
+ sserver = new Socket(server, SMTP_PORT);
+ send(message, sserver, recipientAddress);
+ sserver.close();
+
+ if (listener != null) {
+ MailEvent event = new MailEvent(message, null);
+ listener.handleEvent(event);
+ }
+ }
+ } catch (Exception ex) {
+ if (listener != null) {
+ MailEvent event = new MailEvent(message, ex);
+ listener.handleEvent(event);
+ } else {
+ logger.error(null, ex);
+ }
+ }
+ }
+
+ private void send(MailMessage message, Socket sserver, MailAddress recipientAddress) throws IOException {
+
+ // A bit convoluted, but doesn't depend on DNS in any way...
+ InetAddress localhost = sserver.getLocalAddress();
+ String localaddress = localhost.getHostAddress();
+ MailAddress sender = message.getSender();
+ message.setCurrentRecipient(recipientAddress);
+
+ // Mandatory header fields, Date and From
+ if (message.getHeader("Date") == null) {
+ message.addHeader("Date", new java.util.Date().toString());
+ }
+ if (message.getHeader("From") == null) {
+ message.addHeader("From", sender.getAlias() + " <" + sender.getEmailAddress() + ">");
+ }
+
+ // if (message.getHeader("From") == null){
+ message.addHeader("To", recipientAddress.getAlias() + " <" + recipientAddress.getEmailAddress() + ">");
+ // }
+
+ out = new OutputStreamWriter(sserver.getOutputStream());
+ in = new BufferedReader(new InputStreamReader(sserver.getInputStream()));
+ String sintro = readln();
+ if (!sintro.startsWith("220")) { // 220
+ logger.debug("SmtpSender: intro==" + sintro);
+ return;
+ }
+
+ writeln("EHLO " + localaddress);
+ if (!expect250()) {
+ return;
+ }
+
+ writeln("MAIL FROM:<" + sender.getEmailAddress() + ">");
+ if (!expect250()) {
+ return;
+ }
+ writeln("RCPT TO:<" + recipientAddress.getEmailAddress() + ">");
+ if (!expect250()) {
+ return;
+ }
+ writeln("DATA");
+ while (true) { // may be multiple 250 replies pending from server
+ String line = readln();
+ if (line.startsWith("3"))
+ break; // ready to send
+ if (!line.startsWith("2")) {
+ logger.debug("SmtpSender.send reponse to DATA: " + line);
+ return;
+ }
+ }
+ for (String key : message.getHeaderFields()) {
+ writeln(key + ": " + message.getHeader(key));
+ }
+ writeln(""); // end of header;
+ for (String bline : message.getBodyLines()) {
+ if (bline.startsWith(".")) {
+ bline = "." + bline;
+ }
+ writeln(bline);
+ }
+ writeln(".");
+ expect250();
+ writeln("QUIT");
+
+ }
+
+ private boolean expect250() throws IOException {
+ String line = readln();
+ if (!line.startsWith("2")) {
+ logger.info("SmtpSender.expect250: " + line);
+ return false;
+ }
+ return true;
+ }
+
+ private void writeln(String s) throws IOException {
+ if (traceLevel > 2) {
+ logger.debug("From client: " + s);
+ }
+ out.write(s + "\r\n");
+ out.flush();
+ }
+
+ private String readln() throws IOException {
+ String line = in.readLine();
+ if (traceLevel > 1) {
+ logger.debug("From server: " + line);
+ }
+ return line;
+ }
+
+ /**
+ * Set the trace level.
+ */
+ public void setTraceLevel(int traceLevel) {
+ this.traceLevel = traceLevel;
+ }
+
+ /**
+ * Return the hostname of the local machine.
+ */
+ public String getLocalHostName() {
+ try {
+ InetAddress ipaddress = InetAddress.getLocalHost();
+ String localHost = ipaddress.getHostName();
+ if (localHost == null) {
+ return "localhost";
+ } else {
+ return localHost;
+ }
+ } catch (UnknownHostException e) {
+ return "localhost";
+ }
+ }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/MapFromString.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MapFromString.java
index cec1ed413..a98ee19c0 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/util/MapFromString.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MapFromString.java
@@ -1,63 +1,63 @@
-package com.avaje.ebeaninternal.server.lib.util;
-
-import java.util.LinkedHashMap;
-
-/**
- * Utility String class that supports String manipulation functions.
- */
-public class MapFromString {
-
- LinkedHashMap map = new LinkedHashMap();
-
- String mapToString;
-
- int stringLength;
- int keyStart = 0;
- int eqPos = 0;
- int valEnd = 0;
-
- public static LinkedHashMap parse(String mapToString) {
- MapFromString c = new MapFromString(mapToString);
- return c.parse();
- }
-
- private MapFromString(String mapToString) {
- if (mapToString.charAt(0) == '{'){
- mapToString = mapToString.substring(1);
- }
- if (mapToString.charAt(mapToString.length()-1) == '}'){
- mapToString = mapToString.substring(0, mapToString.length()-1);
- }
-
- this.mapToString = mapToString;
- this.stringLength = mapToString.length();
- }
-
- private LinkedHashMap parse() {
- while(findNext()){
- }
- return map;
- }
-
- private boolean findNext() {
- if (keyStart > stringLength){
- return false;
- }
- eqPos = mapToString.indexOf("=",keyStart);
- if (eqPos == -1){
- throw new RuntimeException("No = after "+keyStart);
- }
- valEnd = mapToString.indexOf(", ",eqPos);
- if (valEnd == -1){
- valEnd = mapToString.length();
- }
- // check that the next valEnd occurs after the next eqPos
-
- String keyValue = mapToString.substring(keyStart,eqPos);
- String valValue = mapToString.substring(eqPos+1,valEnd);
- map.put(keyValue, valValue);
- keyStart = valEnd + 2;
- return true;
- }
-
-}
+package com.avaje.ebeaninternal.server.lib.util;
+
+import java.util.LinkedHashMap;
+
+/**
+ * Utility String class that supports String manipulation functions.
+ */
+public class MapFromString {
+
+ LinkedHashMap map = new LinkedHashMap();
+
+ String mapToString;
+
+ int stringLength;
+ int keyStart = 0;
+ int eqPos = 0;
+ int valEnd = 0;
+
+ public static LinkedHashMap parse(String mapToString) {
+ MapFromString c = new MapFromString(mapToString);
+ return c.parse();
+ }
+
+ private MapFromString(String mapToString) {
+ if (mapToString.charAt(0) == '{'){
+ mapToString = mapToString.substring(1);
+ }
+ if (mapToString.charAt(mapToString.length()-1) == '}'){
+ mapToString = mapToString.substring(0, mapToString.length()-1);
+ }
+
+ this.mapToString = mapToString;
+ this.stringLength = mapToString.length();
+ }
+
+ private LinkedHashMap parse() {
+ while(findNext()){
+ }
+ return map;
+ }
+
+ private boolean findNext() {
+ if (keyStart > stringLength){
+ return false;
+ }
+ eqPos = mapToString.indexOf("=",keyStart);
+ if (eqPos == -1){
+ throw new RuntimeException("No = after "+keyStart);
+ }
+ valEnd = mapToString.indexOf(", ",eqPos);
+ if (valEnd == -1){
+ valEnd = mapToString.length();
+ }
+ // check that the next valEnd occurs after the next eqPos
+
+ String keyValue = mapToString.substring(keyStart,eqPos);
+ String valValue = mapToString.substring(eqPos+1,valEnd);
+ map.put(keyValue, valValue);
+ keyStart = valEnd + 2;
+ return true;
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/MimeTypeHelper.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MimeTypeHelper.java
index 2554616d4..199281cd0 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/util/MimeTypeHelper.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/MimeTypeHelper.java
@@ -1,40 +1,40 @@
-package com.avaje.ebeaninternal.server.lib.util;
-
-
-import java.util.MissingResourceException;
-import java.util.ResourceBundle;
-
-/**
- * Helper methods to determine the mime type based on a file name.
- */
-public class MimeTypeHelper {
-
- /**
- * Return the mimeType for a given file path.
- * This will extract the file extension, and then use that
- * to look up an appropriate mime type (from the mimetypes.props file).
- *
- * To add a new mime type, add it to the mimetype.props file.
- */
- public static String getMimeType(String filePath) {
-
- int lastPeriod = filePath.lastIndexOf(".");
- if (lastPeriod > -1) {
- filePath = filePath.substring(lastPeriod+1);
- }
-
- try {
- return resources.getString(filePath.toLowerCase());
-
- } catch (MissingResourceException e) {
- return null;
- //String m = "Unable to locate mimetype for ["+filePath.toLowerCase()+"] in mimetypes.properties";
- //throw new NotFoundException(m);
- }
-
- }
-
- private static ResourceBundle resources = ResourceBundle.getBundle("com.avaje.lib.util.mimetypes");
-
-
-};
+package com.avaje.ebeaninternal.server.lib.util;
+
+
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+/**
+ * Helper methods to determine the mime type based on a file name.
+ */
+public class MimeTypeHelper {
+
+ /**
+ * Return the mimeType for a given file path.
+ * This will extract the file extension, and then use that
+ * to look up an appropriate mime type (from the mimetypes.props file).
+ *
+ * To add a new mime type, add it to the mimetype.props file.
+ */
+ public static String getMimeType(String filePath) {
+
+ int lastPeriod = filePath.lastIndexOf(".");
+ if (lastPeriod > -1) {
+ filePath = filePath.substring(lastPeriod+1);
+ }
+
+ try {
+ return resources.getString(filePath.toLowerCase());
+
+ } catch (MissingResourceException e) {
+ return null;
+ //String m = "Unable to locate mimetype for ["+filePath.toLowerCase()+"] in mimetypes.properties";
+ //throw new NotFoundException(m);
+ }
+
+ }
+
+ private static ResourceBundle resources = ResourceBundle.getBundle("com.avaje.lib.util.mimetypes");
+
+
+};
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/NotFoundException.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/NotFoundException.java
index 02fe023e4..89665573c 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/util/NotFoundException.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/NotFoundException.java
@@ -1,23 +1,23 @@
-package com.avaje.ebeaninternal.server.lib.util;
-
-
-/**
- * A general exception where data is not found.
- */
-public class NotFoundException extends RuntimeException
-{
- static final long serialVersionUID = 7061559938704539845L;
-
- public NotFoundException(Exception cause) {
- super(cause);
- }
-
- public NotFoundException(String s, Exception cause) {
- super(s, cause);
- }
-
- public NotFoundException(String s) {
- super(s);
- }
-
-}
+package com.avaje.ebeaninternal.server.lib.util;
+
+
+/**
+ * A general exception where data is not found.
+ */
+public class NotFoundException extends RuntimeException
+{
+ static final long serialVersionUID = 7061559938704539845L;
+
+ public NotFoundException(Exception cause) {
+ super(cause);
+ }
+
+ public NotFoundException(String s, Exception cause) {
+ super(s, cause);
+ }
+
+ public NotFoundException(String s) {
+ super(s);
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/StringHelper.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/StringHelper.java
index cd7e6763f..1b8185d17 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/util/StringHelper.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/StringHelper.java
@@ -1,599 +1,599 @@
-package com.avaje.ebeaninternal.server.lib.util;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Utility String class that supports String manipulation functions.
- */
-public class StringHelper {
-
- private static final char SINGLE_QUOTE = '\'';
-
- private static final char DOUBLE_QUOTE = '"';
-
- /**
- * parses a String of the form name1='value1' name2='value2'. Note that you
- * can use either single or double quotes for any particular name value pair
- * and the end quote must match the begin quote.
- */
- public static HashMap parseNameQuotedValue(String tag) throws RuntimeException {
-
- if (tag == null || tag.length() < 1) {
- return null;
- }
-
- // make sure that the quotes are matched...
- // int remainer = countOccurances(tag, ""+quote) % 2;
- // if (remainer == 1) {
- // dp("remainder = "+remainer);
- // throw new StringParsingException("Unmatched quote in "+tag);
- // }
-
- // make sure that th last character is not an equals...
- // (check now so I don't need to check this every time..)
- if (tag.charAt(tag.length() - 1) == '=') {
- throw new RuntimeException("missing quoted value at the end of " + tag);
- }
-
- HashMap map = new HashMap();
- // recursively parse out the name value pairs...
- return parseNameQuotedValue(map, tag, 0);
- }
-
- /**
- * recursively parse out name value pairs (where the value is quoted, with
- * either single or double quotes).
- */
- private static HashMap parseNameQuotedValue(HashMap map,
- String tag, int pos) throws RuntimeException {
-
- int equalsPos = tag.indexOf("=", pos);
- if (equalsPos > -1) {
- // check for begin quote...
- char firstQuote = tag.charAt(equalsPos + 1);
- if (firstQuote != SINGLE_QUOTE && firstQuote != DOUBLE_QUOTE) {
- throw new RuntimeException("missing begin quote at " + (equalsPos) + "["
- + tag.charAt(equalsPos + 1) + "] in [" + tag + "]");
- }
-
- // check for end quote...
- int endQuotePos = tag.indexOf(firstQuote, equalsPos + 2);
- if (endQuotePos == -1) {
- throw new RuntimeException("missing end quote [" + firstQuote + "] after " + pos
- + " in [" + tag + "]");
- }
-
- // we have a valid name and value...
- // dp("pos="+pos+" equalsPos="+equalsPos+"
- // endQuotePos="+endQuotePos);
- String name = tag.substring(pos, equalsPos);
- String value = tag.substring(equalsPos + 2, endQuotePos);
- // dp("name="+name+"; value="+value+";");
-
- // trim off any whitespace from the front of name...
- name = trimFront(name, " ");
- if ((name.indexOf(SINGLE_QUOTE) > -1) || (name.indexOf(DOUBLE_QUOTE) > -1)) {
- throw new RuntimeException("attribute name contains a quote [" + name + "]");
- }
- map.put(name, value);
-
- return parseNameQuotedValue(map, tag, endQuotePos + 1);
-
- } else {
- // no more equals... stop parsing...
- return map;
- }
- }
-
- /**
- * Returns the number of times a particular String occurs in another String.
- * e.g. count the number of single quotes.
- */
- public static int countOccurances(String content, String occurs) {
- return countOccurances(content, occurs, 0, 0);
- }
-
- private static int countOccurances(String content, String occurs, int pos, int countSoFar) {
- int equalsPos = content.indexOf(occurs, pos);
- if (equalsPos > -1) {
- countSoFar = countSoFar + 1;
- pos = equalsPos + occurs.length();
- // dp("countSoFar="+countSoFar+" pos="+pos);
- return countOccurances(content, occurs, pos, countSoFar);
- } else {
- return countSoFar;
- }
- }
-
- /**
- * Parses out a list of Name Value pairs that are delimited together. Will
- * always return a StringMap. If allNameValuePairs is null, or no name
- * values can be parsed out an empty StringMap is returned.
- *
- * @param allNameValuePairs
- * the entire string to be parsed.
- * @param listDelimiter
- * (typically ';') the delimited between the list
- * @param nameValueSeparator
- * (typically '=') the separator between the name and value
- */
- public static Map delimitedToMap(String allNameValuePairs,
- String listDelimiter, String nameValueSeparator) {
-
- HashMap params = new HashMap();
- if ((allNameValuePairs == null) || (allNameValuePairs.length() == 0)) {
- return params;
- }
- // trim off any leading listDelimiter...
- allNameValuePairs = trimFront(allNameValuePairs, listDelimiter);
- return getKeyValue(params, 0, allNameValuePairs, listDelimiter, nameValueSeparator);
- }
-
- /**
- * Trims off recurring strings from the front of a string.
- *
- * @param source
- * the source string
- * @param trim
- * the string to trim off the front
- */
- public static String trimFront(String source, String trim) {
- if (source == null) {
- return null;
- }
- if (source.indexOf(trim) == 0) {
- // dp("trim ...");
- return trimFront(source.substring(trim.length()), trim);
- } else {
- return source;
- }
- }
-
- /**
- * Return true if the value is null or an empty string.
- */
- public static boolean isNull(String value) {
- if (value == null || value.trim().length() == 0) {
- return true;
- }
- return false;
- }
-
- /**
- * Recursively pulls out the key value pairs from a raw string.
- */
- private static HashMap getKeyValue(HashMap map, int pos,
- String allNameValuePairs, String listDelimiter, String nameValueSeparator) {
-
- if (pos >= allNameValuePairs.length()) {
- // dp("end as "+pos+" >= "+allNameValuePairs.length() );
- return map;
- }
-
- int equalsPos = allNameValuePairs.indexOf(nameValueSeparator, pos);
- int delimPos = allNameValuePairs.indexOf(listDelimiter, pos);
-
- if (delimPos == -1) {
- delimPos = allNameValuePairs.length();
- }
- if (equalsPos == -1) {
- // dp("no more equals...");
- return map;
- }
- if (delimPos == (equalsPos + 1)) {
- // dp("Ignoring as nothing between delim and equals...
- // delim:"+delimPos+" eq:"+equalsPos);
- return getKeyValue(map, delimPos + 1, allNameValuePairs, listDelimiter,
- nameValueSeparator);
- }
- if (equalsPos > delimPos) {
- // there is a key without a value?
- String key = allNameValuePairs.substring(pos, delimPos);
- key = key.trim();
- if (key.length() > 0) {
- map.put(key, null);
- }
- return getKeyValue(map, delimPos + 1, allNameValuePairs, listDelimiter,
- nameValueSeparator);
-
- }
- String key = allNameValuePairs.substring(pos, equalsPos);
-
- if (delimPos > -1) {
- String value = allNameValuePairs.substring(equalsPos + 1, delimPos);
- // dp("cont "+key+","+value+" pos:"+pos+"
- // len:"+allNameValuePairs.length());
- key = key.trim();
-
- map.put(key, value);
- pos = delimPos + 1;
-
- // recurse the rest of the values...
- return getKeyValue(map, pos, allNameValuePairs, listDelimiter, nameValueSeparator);
- } else {
- // dp("ERROR: delimPos < 0 ???");
- return map;
- }
- }
-
- /**
- * Convert a string that has delimited values (say comma delimited) in a
- * String[]. You must explicitly choose whether or not to include empty
- * values (say two commas that a right beside each other.
- *
- *
- * e.g. "alpha,beta,,theta"
- * With keepEmpties true, this results in a String[] of size 4 with the
- * third one having a String of 0 length. With keepEmpties false, this
- * results in a String[] of size 3.
- *
- *
- *
- *
- * e.g. ",alpha,beta,,theta,"
- * With keepEmpties true, this results in a String[] of size 6 with the
- * 1st,4th and 6th one having a String of 0 length. With keepEmpties false,
- * this results in a String[] of size 3.
- *
- */
- public static String[] delimitedToArray(String str, String delimiter, boolean keepEmpties) {
-
- ArrayList list = new ArrayList();
- int startPos = 0;
- delimiter(str, delimiter, keepEmpties, startPos, list);
- String[] result = new String[list.size()];
- return (String[]) list.toArray(result);
- }
-
- private static void delimiter(String str, String delimiter, boolean keepEmpties, int startPos,
- ArrayList list) {
-
- int endPos = str.indexOf(delimiter, startPos);
- if (endPos == -1) {
- if (startPos <= str.length()) {
- String lastValue = str.substring(startPos, str.length());
- // dp("lastValue="+lastValue);
- if (!keepEmpties && lastValue.length() == 0) {
- // dp("not keeping...");
- } else {
- list.add(lastValue);
- }
- }
- // we have finished parsing the string...
- return;
- } else {
- // get the delimited value... add it..
- String value = str.substring(startPos, endPos);
- // dp(startPos+","+endPos+" value="+value);
- if (!keepEmpties && value.length() == 0) {
- // dp("not keeping...");
- } else {
- list.add(value);
- }
- // recursively search as we are not at the end yet...
- delimiter(str, delimiter, keepEmpties, endPos + 1, list);
- }
- }
-
- /**
- * This returns the FIRST string in str that is bounded on the left by
- * leftBound, and bounded on the right by rightBound. This will return null
- * if the leftBound is not found within str.
- *
- *
- * If leftBound can't be found this returns null.
- *
- *
- * This rightBound can't be found then this throws a
- * StringIndexOutOfBoundsException.
- *
- *
- * @param str
- * the base string that we will search for the bounded string.
- * @param leftBound
- * the left bound of the string.
- * @param rightBound
- * the right bound of the string.
- */
- public static String getBoundedString(String str, String leftBound, String rightBound)
- throws RuntimeException {
-
- if (str == null) {
- throw new RuntimeException("string to parse is null?");
- }
- int startPos = str.indexOf(leftBound);
- if (startPos > -1) {
- startPos = startPos + leftBound.length();
- int endPos = str.indexOf(rightBound, startPos);
- // dp(str+" start:"+startPos+" end:"+endPos);
- if (endPos == -1) {
- throw new RuntimeException("Can't find rightBound: " + rightBound);
- }
- return str.substring(startPos, endPos);
- } else {
- // if no leftBound can be found.. return null... could be in a
- // search n parse type loop?
- // this keeps "no tag"==null different from "tag not formed
- // properly"==StringParsingException
- return null;
- }
- }
-
- /**
- * Takes the String bounded by leftBound & rightBound, and replaces it with
- * replaceString. Actually removes the left and right bound strings aswell.
- */
- public static String setBoundedString(String str, String leftBound, String rightBound,
- String replaceString) {
-
- int startPos = str.indexOf(leftBound);
- if (startPos > -1) {
- // startPos = startPos;
- int endPos = str.indexOf(rightBound, startPos + leftBound.length());
- if (endPos > -1) {
- String toReplace = str.substring(startPos, endPos + 1);
- return replaceString(str, toReplace, replaceString);
- } else {
- return str;
- }
- } else {
- return str;
- }
- }
-
- // public static String replaceString(String str, String oldSub, String
- // newSub) {
- //
- // if (str == null) {
- // return null;
- // }
- // StringBuilder newSB = new StringBuilder(str.length()+20);
- // int iPos = 0;
- // int iPrevPos = 0;
- //
- // while (true) {
- // iPos = str.indexOf(oldSub, iPrevPos);
- // if (iPos > -1) {
- // // found
- // newSB.append(str.substring(iPrevPos, iPos));
- // newSB.append(newSub);
- // iPrevPos = iPos + oldSub.length();
- // } else {
- // // not found
- // newSB.append(str.substring(iPrevPos));
- // break;
- // }
- // }
- //
- // return newSB.toString();
- // }
-
- /**
- * This method takes a String and will replace all occurrences of the match
- * String with that of the replace String.
- *
- * @param source
- * the source string
- * @param match
- * the string used to find a match
- * @param replace
- * the string used to replace match with
- * @return the source string after the search and replace
- */
- public static String replaceString(String source, String match, String replace) {
- if (source == null){
- return null;
- }
- if (replace == null){
- return source;
- }
- if (match == null){
- throw new NullPointerException("match is null?");
- }
- if (match.equals(replace)){
- return source;
- }
- return replaceString(source, match, replace, 30, 0, source.length());
- }
-
- /**
- * Additionally specify the additionalSize to add to the buffer. This will
- * make the buffer bigger so that it doesn't have to grow when replacement
- * occurs.
- */
- public static String replaceString(String source, String match, String replace,
- int additionalSize, int startPos, int endPos) {
-
- if (source == null){
- return source;
- }
-
- char match0 = match.charAt(0);
-
- int matchLength = match.length();
-
- if (matchLength == 1 && replace.length() == 1) {
- char replace0 = replace.charAt(0);
- return source.replace(match0, replace0);
- }
- if (matchLength >= replace.length()) {
- additionalSize = 0;
- }
-
-
- int sourceLength = source.length();
- int lastMatch = endPos - matchLength;
-
- StringBuilder sb = new StringBuilder(sourceLength + additionalSize);
-
- if (startPos > 0) {
- sb.append(source.substring(0, startPos));
- }
-
- char sourceChar;
- boolean isMatch;
- int sourceMatchPos;
-
- for (int i = startPos; i < sourceLength; i++) {
- sourceChar = source.charAt(i);
- if (i > lastMatch || sourceChar != match0) {
- sb.append(sourceChar);
-
- } else {
- // check to see if this is a match
- isMatch = true;
- sourceMatchPos = i;
-
- // check each following character...
- for (int j = 1; j < matchLength; j++) {
- sourceMatchPos++;
- if (source.charAt(sourceMatchPos) != match.charAt(j)) {
- isMatch = false;
- break;
- }
- }
- if (isMatch) {
- i = i + matchLength - 1;
- sb.append(replace);
- } else {
- // was not a match
- sb.append(sourceChar);
- }
- }
- }
-
- return sb.toString();
- }
-
- /**
- * A search and replace with multiple matching strings.
- *
- * Useful when converting CRNL CR and NL all to a BR tag for example.
- *
- *
- *
- * String[] multi = { "\r\n", "\r", "\n" };
- * content = StringHelper.replaceStringMulti(content, multi, "<br/>");
- *
- */
- public static String replaceStringMulti(String source, String[] match, String replace) {
- return replaceStringMulti(source, match, replace, 30, 0, source.length());
- }
-
- /**
- * Additionally specify an additional size estimate for the buffer plus
- * start and end positions.
- *
- * The start and end positions can limit the search and replace. Otherwise
- * these default to startPos = 0 and endPos = source.length().
- *
- */
- public static String replaceStringMulti(String source, String[] match, String replace,
- int additionalSize, int startPos, int endPos) {
-
- int shortestMatch = match[0].length();
-
- char[] match0 = new char[match.length];
- for (int i = 0; i < match0.length; i++) {
- match0[i] = match[i].charAt(0);
- if (match[i].length() < shortestMatch) {
- shortestMatch = match[i].length();
- }
- }
-
- StringBuilder sb = new StringBuilder(source.length() + additionalSize);
-
- char sourceChar;
-
- int len = source.length();
- int lastMatch = endPos - shortestMatch;
-
- if (startPos > 0) {
- sb.append(source.substring(0, startPos));
- }
-
- int matchCount = 0;
-
- for (int i = startPos; i < len; i++) {
- sourceChar = source.charAt(i);
- if (i > lastMatch) {
- sb.append(sourceChar);
- } else {
- matchCount = 0;
- for (int k = 0; k < match0.length; k++) {
- if (matchCount == 0 && sourceChar == match0[k]) {
- if (match[k].length() + i <= len) {
-
- ++matchCount;
- int j = 1;
- for (; j < match[k].length(); j++) {
- if (source.charAt(i + j) != match[k].charAt(j)) {
- --matchCount;
- break;
- }
- }
- if (matchCount > 0) {
- i = i + j - 1;
- sb.append(replace);
- break;
- }
- }
- }
- }
- if (matchCount == 0) {
- sb.append(sourceChar);
- }
- }
- }
-
- return sb.toString();
- }
-
- /**
- * This method takes a String as an argument and removes all occurrences of
- * the supplied Char. It returns the resulting String.
- */
- public static String removeChar(String s, char chr) {
-
- StringBuilder sb = new StringBuilder(s.length());
-
- for (int i = 0; i < s.length(); i++) {
- char c = s.charAt(i);
- if (c != chr){
- sb.append(c);
- }
- }
-
- return sb.toString();
- }
-
- /**
- * This method takes a String as an argument and removes all occurrences of
- * the supplied Chars. It returns the resulting String.
- */
- public static String removeChars(String s, char[] chr) {
-
- StringBuilder sb = new StringBuilder(s.length());
-
- for (int i = 0; i < s.length(); i++) {
- char c = s.charAt(i);
- if (!charMatch(c, chr)){
- sb.append(c);
- }
- }
-
- return sb.toString();
- }
-
- private static boolean charMatch(int iChr, char[] chr) {
- for (int i = 0; i < chr.length; i++) {
- if (iChr == chr[i]) {
- return true;
- }
- }
- return false;
- }
-
-}
+package com.avaje.ebeaninternal.server.lib.util;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Utility String class that supports String manipulation functions.
+ */
+public class StringHelper {
+
+ private static final char SINGLE_QUOTE = '\'';
+
+ private static final char DOUBLE_QUOTE = '"';
+
+ /**
+ * parses a String of the form name1='value1' name2='value2'. Note that you
+ * can use either single or double quotes for any particular name value pair
+ * and the end quote must match the begin quote.
+ */
+ public static HashMap parseNameQuotedValue(String tag) throws RuntimeException {
+
+ if (tag == null || tag.length() < 1) {
+ return null;
+ }
+
+ // make sure that the quotes are matched...
+ // int remainer = countOccurances(tag, ""+quote) % 2;
+ // if (remainer == 1) {
+ // dp("remainder = "+remainer);
+ // throw new StringParsingException("Unmatched quote in "+tag);
+ // }
+
+ // make sure that th last character is not an equals...
+ // (check now so I don't need to check this every time..)
+ if (tag.charAt(tag.length() - 1) == '=') {
+ throw new RuntimeException("missing quoted value at the end of " + tag);
+ }
+
+ HashMap map = new HashMap();
+ // recursively parse out the name value pairs...
+ return parseNameQuotedValue(map, tag, 0);
+ }
+
+ /**
+ * recursively parse out name value pairs (where the value is quoted, with
+ * either single or double quotes).
+ */
+ private static HashMap parseNameQuotedValue(HashMap map,
+ String tag, int pos) throws RuntimeException {
+
+ int equalsPos = tag.indexOf("=", pos);
+ if (equalsPos > -1) {
+ // check for begin quote...
+ char firstQuote = tag.charAt(equalsPos + 1);
+ if (firstQuote != SINGLE_QUOTE && firstQuote != DOUBLE_QUOTE) {
+ throw new RuntimeException("missing begin quote at " + (equalsPos) + "["
+ + tag.charAt(equalsPos + 1) + "] in [" + tag + "]");
+ }
+
+ // check for end quote...
+ int endQuotePos = tag.indexOf(firstQuote, equalsPos + 2);
+ if (endQuotePos == -1) {
+ throw new RuntimeException("missing end quote [" + firstQuote + "] after " + pos
+ + " in [" + tag + "]");
+ }
+
+ // we have a valid name and value...
+ // dp("pos="+pos+" equalsPos="+equalsPos+"
+ // endQuotePos="+endQuotePos);
+ String name = tag.substring(pos, equalsPos);
+ String value = tag.substring(equalsPos + 2, endQuotePos);
+ // dp("name="+name+"; value="+value+";");
+
+ // trim off any whitespace from the front of name...
+ name = trimFront(name, " ");
+ if ((name.indexOf(SINGLE_QUOTE) > -1) || (name.indexOf(DOUBLE_QUOTE) > -1)) {
+ throw new RuntimeException("attribute name contains a quote [" + name + "]");
+ }
+ map.put(name, value);
+
+ return parseNameQuotedValue(map, tag, endQuotePos + 1);
+
+ } else {
+ // no more equals... stop parsing...
+ return map;
+ }
+ }
+
+ /**
+ * Returns the number of times a particular String occurs in another String.
+ * e.g. count the number of single quotes.
+ */
+ public static int countOccurances(String content, String occurs) {
+ return countOccurances(content, occurs, 0, 0);
+ }
+
+ private static int countOccurances(String content, String occurs, int pos, int countSoFar) {
+ int equalsPos = content.indexOf(occurs, pos);
+ if (equalsPos > -1) {
+ countSoFar = countSoFar + 1;
+ pos = equalsPos + occurs.length();
+ // dp("countSoFar="+countSoFar+" pos="+pos);
+ return countOccurances(content, occurs, pos, countSoFar);
+ } else {
+ return countSoFar;
+ }
+ }
+
+ /**
+ * Parses out a list of Name Value pairs that are delimited together. Will
+ * always return a StringMap. If allNameValuePairs is null, or no name
+ * values can be parsed out an empty StringMap is returned.
+ *
+ * @param allNameValuePairs
+ * the entire string to be parsed.
+ * @param listDelimiter
+ * (typically ';') the delimited between the list
+ * @param nameValueSeparator
+ * (typically '=') the separator between the name and value
+ */
+ public static Map delimitedToMap(String allNameValuePairs,
+ String listDelimiter, String nameValueSeparator) {
+
+ HashMap params = new HashMap();
+ if ((allNameValuePairs == null) || (allNameValuePairs.length() == 0)) {
+ return params;
+ }
+ // trim off any leading listDelimiter...
+ allNameValuePairs = trimFront(allNameValuePairs, listDelimiter);
+ return getKeyValue(params, 0, allNameValuePairs, listDelimiter, nameValueSeparator);
+ }
+
+ /**
+ * Trims off recurring strings from the front of a string.
+ *
+ * @param source
+ * the source string
+ * @param trim
+ * the string to trim off the front
+ */
+ public static String trimFront(String source, String trim) {
+ if (source == null) {
+ return null;
+ }
+ if (source.indexOf(trim) == 0) {
+ // dp("trim ...");
+ return trimFront(source.substring(trim.length()), trim);
+ } else {
+ return source;
+ }
+ }
+
+ /**
+ * Return true if the value is null or an empty string.
+ */
+ public static boolean isNull(String value) {
+ if (value == null || value.trim().length() == 0) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Recursively pulls out the key value pairs from a raw string.
+ */
+ private static HashMap getKeyValue(HashMap map, int pos,
+ String allNameValuePairs, String listDelimiter, String nameValueSeparator) {
+
+ if (pos >= allNameValuePairs.length()) {
+ // dp("end as "+pos+" >= "+allNameValuePairs.length() );
+ return map;
+ }
+
+ int equalsPos = allNameValuePairs.indexOf(nameValueSeparator, pos);
+ int delimPos = allNameValuePairs.indexOf(listDelimiter, pos);
+
+ if (delimPos == -1) {
+ delimPos = allNameValuePairs.length();
+ }
+ if (equalsPos == -1) {
+ // dp("no more equals...");
+ return map;
+ }
+ if (delimPos == (equalsPos + 1)) {
+ // dp("Ignoring as nothing between delim and equals...
+ // delim:"+delimPos+" eq:"+equalsPos);
+ return getKeyValue(map, delimPos + 1, allNameValuePairs, listDelimiter,
+ nameValueSeparator);
+ }
+ if (equalsPos > delimPos) {
+ // there is a key without a value?
+ String key = allNameValuePairs.substring(pos, delimPos);
+ key = key.trim();
+ if (key.length() > 0) {
+ map.put(key, null);
+ }
+ return getKeyValue(map, delimPos + 1, allNameValuePairs, listDelimiter,
+ nameValueSeparator);
+
+ }
+ String key = allNameValuePairs.substring(pos, equalsPos);
+
+ if (delimPos > -1) {
+ String value = allNameValuePairs.substring(equalsPos + 1, delimPos);
+ // dp("cont "+key+","+value+" pos:"+pos+"
+ // len:"+allNameValuePairs.length());
+ key = key.trim();
+
+ map.put(key, value);
+ pos = delimPos + 1;
+
+ // recurse the rest of the values...
+ return getKeyValue(map, pos, allNameValuePairs, listDelimiter, nameValueSeparator);
+ } else {
+ // dp("ERROR: delimPos < 0 ???");
+ return map;
+ }
+ }
+
+ /**
+ * Convert a string that has delimited values (say comma delimited) in a
+ * String[]. You must explicitly choose whether or not to include empty
+ * values (say two commas that a right beside each other.
+ *
+ *
+ * e.g. "alpha,beta,,theta"
+ * With keepEmpties true, this results in a String[] of size 4 with the
+ * third one having a String of 0 length. With keepEmpties false, this
+ * results in a String[] of size 3.
+ *
+ *
+ *
+ *
+ * e.g. ",alpha,beta,,theta,"
+ * With keepEmpties true, this results in a String[] of size 6 with the
+ * 1st,4th and 6th one having a String of 0 length. With keepEmpties false,
+ * this results in a String[] of size 3.
+ *
+ */
+ public static String[] delimitedToArray(String str, String delimiter, boolean keepEmpties) {
+
+ ArrayList list = new ArrayList();
+ int startPos = 0;
+ delimiter(str, delimiter, keepEmpties, startPos, list);
+ String[] result = new String[list.size()];
+ return (String[]) list.toArray(result);
+ }
+
+ private static void delimiter(String str, String delimiter, boolean keepEmpties, int startPos,
+ ArrayList list) {
+
+ int endPos = str.indexOf(delimiter, startPos);
+ if (endPos == -1) {
+ if (startPos <= str.length()) {
+ String lastValue = str.substring(startPos, str.length());
+ // dp("lastValue="+lastValue);
+ if (!keepEmpties && lastValue.length() == 0) {
+ // dp("not keeping...");
+ } else {
+ list.add(lastValue);
+ }
+ }
+ // we have finished parsing the string...
+ return;
+ } else {
+ // get the delimited value... add it..
+ String value = str.substring(startPos, endPos);
+ // dp(startPos+","+endPos+" value="+value);
+ if (!keepEmpties && value.length() == 0) {
+ // dp("not keeping...");
+ } else {
+ list.add(value);
+ }
+ // recursively search as we are not at the end yet...
+ delimiter(str, delimiter, keepEmpties, endPos + 1, list);
+ }
+ }
+
+ /**
+ * This returns the FIRST string in str that is bounded on the left by
+ * leftBound, and bounded on the right by rightBound. This will return null
+ * if the leftBound is not found within str.
+ *
+ *
+ * If leftBound can't be found this returns null.
+ *
+ *
+ * This rightBound can't be found then this throws a
+ * StringIndexOutOfBoundsException.
+ *
+ *
+ * @param str
+ * the base string that we will search for the bounded string.
+ * @param leftBound
+ * the left bound of the string.
+ * @param rightBound
+ * the right bound of the string.
+ */
+ public static String getBoundedString(String str, String leftBound, String rightBound)
+ throws RuntimeException {
+
+ if (str == null) {
+ throw new RuntimeException("string to parse is null?");
+ }
+ int startPos = str.indexOf(leftBound);
+ if (startPos > -1) {
+ startPos = startPos + leftBound.length();
+ int endPos = str.indexOf(rightBound, startPos);
+ // dp(str+" start:"+startPos+" end:"+endPos);
+ if (endPos == -1) {
+ throw new RuntimeException("Can't find rightBound: " + rightBound);
+ }
+ return str.substring(startPos, endPos);
+ } else {
+ // if no leftBound can be found.. return null... could be in a
+ // search n parse type loop?
+ // this keeps "no tag"==null different from "tag not formed
+ // properly"==StringParsingException
+ return null;
+ }
+ }
+
+ /**
+ * Takes the String bounded by leftBound & rightBound, and replaces it with
+ * replaceString. Actually removes the left and right bound strings aswell.
+ */
+ public static String setBoundedString(String str, String leftBound, String rightBound,
+ String replaceString) {
+
+ int startPos = str.indexOf(leftBound);
+ if (startPos > -1) {
+ // startPos = startPos;
+ int endPos = str.indexOf(rightBound, startPos + leftBound.length());
+ if (endPos > -1) {
+ String toReplace = str.substring(startPos, endPos + 1);
+ return replaceString(str, toReplace, replaceString);
+ } else {
+ return str;
+ }
+ } else {
+ return str;
+ }
+ }
+
+ // public static String replaceString(String str, String oldSub, String
+ // newSub) {
+ //
+ // if (str == null) {
+ // return null;
+ // }
+ // StringBuilder newSB = new StringBuilder(str.length()+20);
+ // int iPos = 0;
+ // int iPrevPos = 0;
+ //
+ // while (true) {
+ // iPos = str.indexOf(oldSub, iPrevPos);
+ // if (iPos > -1) {
+ // // found
+ // newSB.append(str.substring(iPrevPos, iPos));
+ // newSB.append(newSub);
+ // iPrevPos = iPos + oldSub.length();
+ // } else {
+ // // not found
+ // newSB.append(str.substring(iPrevPos));
+ // break;
+ // }
+ // }
+ //
+ // return newSB.toString();
+ // }
+
+ /**
+ * This method takes a String and will replace all occurrences of the match
+ * String with that of the replace String.
+ *
+ * @param source
+ * the source string
+ * @param match
+ * the string used to find a match
+ * @param replace
+ * the string used to replace match with
+ * @return the source string after the search and replace
+ */
+ public static String replaceString(String source, String match, String replace) {
+ if (source == null){
+ return null;
+ }
+ if (replace == null){
+ return source;
+ }
+ if (match == null){
+ throw new NullPointerException("match is null?");
+ }
+ if (match.equals(replace)){
+ return source;
+ }
+ return replaceString(source, match, replace, 30, 0, source.length());
+ }
+
+ /**
+ * Additionally specify the additionalSize to add to the buffer. This will
+ * make the buffer bigger so that it doesn't have to grow when replacement
+ * occurs.
+ */
+ public static String replaceString(String source, String match, String replace,
+ int additionalSize, int startPos, int endPos) {
+
+ if (source == null){
+ return source;
+ }
+
+ char match0 = match.charAt(0);
+
+ int matchLength = match.length();
+
+ if (matchLength == 1 && replace.length() == 1) {
+ char replace0 = replace.charAt(0);
+ return source.replace(match0, replace0);
+ }
+ if (matchLength >= replace.length()) {
+ additionalSize = 0;
+ }
+
+
+ int sourceLength = source.length();
+ int lastMatch = endPos - matchLength;
+
+ StringBuilder sb = new StringBuilder(sourceLength + additionalSize);
+
+ if (startPos > 0) {
+ sb.append(source.substring(0, startPos));
+ }
+
+ char sourceChar;
+ boolean isMatch;
+ int sourceMatchPos;
+
+ for (int i = startPos; i < sourceLength; i++) {
+ sourceChar = source.charAt(i);
+ if (i > lastMatch || sourceChar != match0) {
+ sb.append(sourceChar);
+
+ } else {
+ // check to see if this is a match
+ isMatch = true;
+ sourceMatchPos = i;
+
+ // check each following character...
+ for (int j = 1; j < matchLength; j++) {
+ sourceMatchPos++;
+ if (source.charAt(sourceMatchPos) != match.charAt(j)) {
+ isMatch = false;
+ break;
+ }
+ }
+ if (isMatch) {
+ i = i + matchLength - 1;
+ sb.append(replace);
+ } else {
+ // was not a match
+ sb.append(sourceChar);
+ }
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * A search and replace with multiple matching strings.
+ *
+ * Useful when converting CRNL CR and NL all to a BR tag for example.
+ *
+ *
+ *
+ * String[] multi = { "\r\n", "\r", "\n" };
+ * content = StringHelper.replaceStringMulti(content, multi, "<br/>");
+ *
+ */
+ public static String replaceStringMulti(String source, String[] match, String replace) {
+ return replaceStringMulti(source, match, replace, 30, 0, source.length());
+ }
+
+ /**
+ * Additionally specify an additional size estimate for the buffer plus
+ * start and end positions.
+ *
+ * The start and end positions can limit the search and replace. Otherwise
+ * these default to startPos = 0 and endPos = source.length().
+ *
+ */
+ public static String replaceStringMulti(String source, String[] match, String replace,
+ int additionalSize, int startPos, int endPos) {
+
+ int shortestMatch = match[0].length();
+
+ char[] match0 = new char[match.length];
+ for (int i = 0; i < match0.length; i++) {
+ match0[i] = match[i].charAt(0);
+ if (match[i].length() < shortestMatch) {
+ shortestMatch = match[i].length();
+ }
+ }
+
+ StringBuilder sb = new StringBuilder(source.length() + additionalSize);
+
+ char sourceChar;
+
+ int len = source.length();
+ int lastMatch = endPos - shortestMatch;
+
+ if (startPos > 0) {
+ sb.append(source.substring(0, startPos));
+ }
+
+ int matchCount = 0;
+
+ for (int i = startPos; i < len; i++) {
+ sourceChar = source.charAt(i);
+ if (i > lastMatch) {
+ sb.append(sourceChar);
+ } else {
+ matchCount = 0;
+ for (int k = 0; k < match0.length; k++) {
+ if (matchCount == 0 && sourceChar == match0[k]) {
+ if (match[k].length() + i <= len) {
+
+ ++matchCount;
+ int j = 1;
+ for (; j < match[k].length(); j++) {
+ if (source.charAt(i + j) != match[k].charAt(j)) {
+ --matchCount;
+ break;
+ }
+ }
+ if (matchCount > 0) {
+ i = i + j - 1;
+ sb.append(replace);
+ break;
+ }
+ }
+ }
+ }
+ if (matchCount == 0) {
+ sb.append(sourceChar);
+ }
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * This method takes a String as an argument and removes all occurrences of
+ * the supplied Char. It returns the resulting String.
+ */
+ public static String removeChar(String s, char chr) {
+
+ StringBuilder sb = new StringBuilder(s.length());
+
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (c != chr){
+ sb.append(c);
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * This method takes a String as an argument and removes all occurrences of
+ * the supplied Chars. It returns the resulting String.
+ */
+ public static String removeChars(String s, char[] chr) {
+
+ StringBuilder sb = new StringBuilder(s.length());
+
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (!charMatch(c, chr)){
+ sb.append(c);
+ }
+ }
+
+ return sb.toString();
+ }
+
+ private static boolean charMatch(int iChr, char[] chr) {
+ for (int i = 0; i < chr.length; i++) {
+ if (iChr == chr[i]) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/StringParsingException.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/StringParsingException.java
index cdd091a54..e55872587 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/util/StringParsingException.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/StringParsingException.java
@@ -1,15 +1,15 @@
-package com.avaje.ebeaninternal.server.lib.util;
-
-
-/**
- * A general string parsing exception.
- */
-public class StringParsingException extends RuntimeException {
-
- static final long serialVersionUID = -3070423471260426402L;
-
- public StringParsingException(String message) {
- super(message);
- }
-};
-
+package com.avaje.ebeaninternal.server.lib.util;
+
+
+/**
+ * A general string parsing exception.
+ */
+public class StringParsingException extends RuntimeException {
+
+ static final long serialVersionUID = -3070423471260426402L;
+
+ public StringParsingException(String message) {
+ super(message);
+ }
+};
+
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/util/ThrowablePrinter.java b/src/main/java/com/avaje/ebeaninternal/server/lib/util/ThrowablePrinter.java
index a21a4cb0e..4c995f98e 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/util/ThrowablePrinter.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/util/ThrowablePrinter.java
@@ -1,91 +1,91 @@
-package com.avaje.ebeaninternal.server.lib.util;
-
-/**
- * Builds a string from a stack trace.
- *
- * Generally used to flatten a stack trace into a single string
- * removing \r\n and limiting the size of any given stack.
- *
- */
-public class ThrowablePrinter {
-
- private static final String atString = " at ";
-
- private String newLineChar = "\\r\\n";
-
- private int maxStackTraceLines = 3;
-
- /**
- * Set the maximum number of lines in any one part of
- * the stack trace. This is not the total maximum.
- */
- public void setMaxStackTraceLines(int maxStackTraceLines) {
- this.maxStackTraceLines = maxStackTraceLines;
- }
-
- /**
- * Set the new line character used to replace \r\n with.
- * This is useful so that the stack is placed on a single line
- * in a log file.
- */
- public void setNewLineChar(String newLineChar) {
- this.newLineChar = newLineChar;
- }
-
- /**
- * Convert the error into a string representation.
- *
- * Replaces the \r\n and limits the stack lines.
- *
- */
- public String print(Throwable e) {
- StringBuffer sb = new StringBuffer();
- printThrowable(sb, e, false);
-
- String line = sb.toString();
- line = StringHelper.replaceString(line, "\r", "\\r");
- line = StringHelper.replaceString(line, "\n", "\\n");
-
- return line;
- }
-
- /**
- * Recursively output the Throwable stack trace to the log.
- *
- * @param sb the buffer to write the stack trace to
- * @param e the source throwable
- * @param isCause flag to indicate if this is the top level throwable or a
- * cause
- */
- protected void printThrowable(StringBuffer sb, Throwable e, boolean isCause) {
- if (e != null) {
- if (isCause) {
- sb.append("Caused by: ");
- }
- sb.append(e.getClass().getName());
- sb.append(":");
- sb.append(e.getMessage()).append(newLineChar);
-
- StackTraceElement[] ste = e.getStackTrace();
- int outputStackLines = ste.length;
- int notShownCount = 0;
- if (ste.length > maxStackTraceLines) {
- outputStackLines = maxStackTraceLines;
- notShownCount = ste.length - outputStackLines;
- }
- for (int i = 0; i < outputStackLines; i++) {
- sb.append(atString);
- sb.append(ste[i].toString()).append(newLineChar);
- }
- if (notShownCount > 0) {
- sb.append(" ... ");
- sb.append(notShownCount);
- sb.append(" more").append(newLineChar);
- }
- Throwable cause = e.getCause();
- if (cause != null) {
- printThrowable(sb, cause, true);
- }
- }
- }
-}
+package com.avaje.ebeaninternal.server.lib.util;
+
+/**
+ * Builds a string from a stack trace.
+ *
+ * Generally used to flatten a stack trace into a single string
+ * removing \r\n and limiting the size of any given stack.
+ *
+ */
+public class ThrowablePrinter {
+
+ private static final String atString = " at ";
+
+ private String newLineChar = "\\r\\n";
+
+ private int maxStackTraceLines = 3;
+
+ /**
+ * Set the maximum number of lines in any one part of
+ * the stack trace. This is not the total maximum.
+ */
+ public void setMaxStackTraceLines(int maxStackTraceLines) {
+ this.maxStackTraceLines = maxStackTraceLines;
+ }
+
+ /**
+ * Set the new line character used to replace \r\n with.
+ * This is useful so that the stack is placed on a single line
+ * in a log file.
+ */
+ public void setNewLineChar(String newLineChar) {
+ this.newLineChar = newLineChar;
+ }
+
+ /**
+ * Convert the error into a string representation.
+ *
+ * Replaces the \r\n and limits the stack lines.
+ *
+ */
+ public String print(Throwable e) {
+ StringBuffer sb = new StringBuffer();
+ printThrowable(sb, e, false);
+
+ String line = sb.toString();
+ line = StringHelper.replaceString(line, "\r", "\\r");
+ line = StringHelper.replaceString(line, "\n", "\\n");
+
+ return line;
+ }
+
+ /**
+ * Recursively output the Throwable stack trace to the log.
+ *
+ * @param sb the buffer to write the stack trace to
+ * @param e the source throwable
+ * @param isCause flag to indicate if this is the top level throwable or a
+ * cause
+ */
+ protected void printThrowable(StringBuffer sb, Throwable e, boolean isCause) {
+ if (e != null) {
+ if (isCause) {
+ sb.append("Caused by: ");
+ }
+ sb.append(e.getClass().getName());
+ sb.append(":");
+ sb.append(e.getMessage()).append(newLineChar);
+
+ StackTraceElement[] ste = e.getStackTrace();
+ int outputStackLines = ste.length;
+ int notShownCount = 0;
+ if (ste.length > maxStackTraceLines) {
+ outputStackLines = maxStackTraceLines;
+ notShownCount = ste.length - outputStackLines;
+ }
+ for (int i = 0; i < outputStackLines; i++) {
+ sb.append(atString);
+ sb.append(ste[i].toString()).append(newLineChar);
+ }
+ if (notShownCount > 0) {
+ sb.append(" ... ");
+ sb.append(notShownCount);
+ sb.append(" more").append(newLineChar);
+ }
+ Throwable cause = e.getCause();
+ if (cause != null) {
+ printThrowable(sb, cause, true);
+ }
+ }
+ }
+}