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(""); - } - 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(""); + } + 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(""); - } - 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(""); + } + 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); + } + } + } +}