diff --git a/api/src/org/labkey/api/ApiModule.java b/api/src/org/labkey/api/ApiModule.java index aea66d0ac4a..cd6765adee2 100644 --- a/api/src/org/labkey/api/ApiModule.java +++ b/api/src/org/labkey/api/ApiModule.java @@ -106,7 +106,6 @@ import org.labkey.api.module.ModuleLoader; import org.labkey.api.module.ModuleLoader.StartupPropertyStartupListener; import org.labkey.api.module.ModuleXml; -import org.labkey.api.module.TomcatVersion; import org.labkey.api.query.AbstractQueryUpdateService; import org.labkey.api.query.AliasManager; import org.labkey.api.query.DetailsURL; @@ -522,7 +521,6 @@ public void registerServlets(ServletContext servletCtx) Table.TestCase.class, TableSelectorTestCase.class, TempTableInClauseGenerator.TestCase.class, - TomcatVersion.TestCase.class, URLHelper.TestCase.class, UserManager.TestCase.class, ViewCategoryManager.TestCase.class, diff --git a/api/src/org/labkey/api/data/TempTableTracker.java b/api/src/org/labkey/api/data/TempTableTracker.java index 3ce1c067499..d62b76b4c31 100644 --- a/api/src/org/labkey/api/data/TempTableTracker.java +++ b/api/src/org/labkey/api/data/TempTableTracker.java @@ -16,53 +16,77 @@ package org.labkey.api.data; -import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.labkey.api.cache.CacheManager; import org.labkey.api.data.dialect.SqlDialect; import org.labkey.api.util.FileUtil; import org.labkey.api.util.ShutdownListener; +import org.labkey.api.util.logging.LogHelper; -import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; -import java.lang.ref.Reference; -import java.lang.ref.ReferenceQueue; -import java.lang.ref.WeakReference; +import java.lang.ref.Cleaner; import java.util.Map; import java.util.TreeMap; import java.util.TreeSet; -import java.util.concurrent.atomic.AtomicBoolean; /** * User: Matthew * Date: May 4, 2006 * Time: 7:27:50 PM */ -public class TempTableTracker extends WeakReference +public class TempTableTracker { - private static final Logger _log = LogManager.getLogger(TempTableTracker.class); + private static final Logger _log = LogHelper.getLogger(TempTableTracker.class, "Manages temp tables and their deletion"); private static final String LOGFILE = "CPAS_sqlTempTables.log"; private static final Map createdTableNames = new TreeMap<>(); - private static final ReferenceQueue cleanupQueue = new ReferenceQueue<>(); + private static final Cleaner cleaner = Cleaner.create(); private static RandomAccessFile tempTableLog = null; - private final DbSchema schema; private final String schemaName; private final String tableName; private final String qualifiedName; + private final Cleaner.Cleanable cleanable; + private final CleanupState state; - private boolean deleted = false; + private static class CleanupState implements Runnable + { + private final DbSchema schema; + private final String tableName; + private final String qualifiedName; + private final String schemaName; + private boolean deleted = false; + private CleanupState(DbSchema schema, String tableName, String qualifiedName, String schemaName) + { + this.schema = schema; + this.tableName = tableName; + this.qualifiedName = qualifiedName; + this.schemaName = schemaName; + } + + @Override + public void run() + { + if (!deleted) + { + _log.debug("Deleting table " + schema.getName() + "." + tableName); + schema.dropTableIfExists(tableName); + + deleted = true; + untrack(qualifiedName, schemaName, tableName); + } + } + } private TempTableTracker(DbSchema schema, String tableName, Object ref) { - super(ref, cleanupQueue); - this.schema = schema; this.schemaName = schema.getName(); this.tableName = tableName; this.qualifiedName = this.schemaName + "." + this.tableName; + this.state = new CleanupState(schema, tableName, qualifiedName, schemaName); + cleanable = cleaner.register(ref, state); } @@ -71,21 +95,19 @@ private TempTableTracker(String schemaName, String tableName, Object ref) this(DbSchema.get(schemaName), tableName, ref); // TODO: Treat as provisioned? } - private static final Object initlock = new Object(); + private static final Object LOCK = new Object(); private static boolean initialized = false; // make sure temp table tracker is initialized public static void init() { - synchronized(initlock) + synchronized(LOCK) { if (!initialized) { initialized = true; synchronizeLog(true); purgeTempSchema(); - tempTableThread.setDaemon(true); - tempTableThread.start(); } } } @@ -120,56 +142,22 @@ private static TempTableTracker track(TempTableTracker ttt) } } - - private DbSchema getSchema() - { - return schema; - } - - public synchronized void delete() { - if (deleted) - return; - - sqlDelete(); - - deleted = true; - untrack(); - } - - - private boolean sqlDelete() - { - DbSchema schema = getSchema(); - _log.debug("Deleting table " + schema.getName() + "." + tableName); - schema.dropTableIfExists(tableName); - return true; + cleanable.clean(); } - - @Override - protected void finalize() throws Throwable - { - if (!deleted) - _log.error("finalizing undeleted TempTableTracker: " + qualifiedName); - super.finalize(); - } - - - private void untrack() + private static void untrack(String qualifiedName, String schemaName, String tableName) { _log.debug("untrack(" + qualifiedName + ")"); synchronized(createdTableNames) { - var ttt = createdTableNames.remove(qualifiedName); + createdTableNames.remove(qualifiedName); appendToLog("-" + schemaName + "\t" + tableName + "\n"); if (createdTableNames.isEmpty() || System.currentTimeMillis() > lastSync + CacheManager.DAY) synchronizeLog(false); - - ttt.clear(); } } @@ -267,7 +255,7 @@ else if (s.charAt(0) == '-') tempTableLog.setLength(0); for (TempTableTracker ttt : createdTableNames.values()) { - if (!ttt.deleted) + if (!ttt.state.deleted) appendToLog("+" + ttt.schemaName + "\t" + ttt.tableName + "\n"); } } @@ -285,47 +273,12 @@ else if (s.charAt(0) == '-') static final TempTableThread tempTableThread = new TempTableThread(); - static class TempTableThread extends Thread implements ShutdownListener + public static class TempTableThread implements ShutdownListener { - AtomicBoolean _shutdown = new AtomicBoolean(false); - - TempTableThread() - { - super("Temp table cleanup"); - setDaemon(true); - } - - @Override - public void run() - { - while (true) - { - try - { - Reference r = _shutdown.get() ? cleanupQueue.poll() : cleanupQueue.remove(); - if (_shutdown.get() && r == null) - return; - //noinspection RedundantCast - TempTableTracker t = (TempTableTracker)(Object)r; - t.delete(); - } - catch (InterruptedException x) - { - _log.debug("interrupted"); - } - catch (Throwable x) - { - _log.error("unexpected error", x); - } - } - } - - @Override - public void shutdownPre() + public String getName() { - _shutdown.set(true); - interrupt(); + return "Temp table cleanup"; } @Override @@ -335,16 +288,10 @@ public void shutdownStarted() { for (TempTableTracker ttt : createdTableNames.values()) { - ttt.sqlDelete(); - ttt.deleted = true; + ttt.state.run(); } } - try - { - join(5000); - } - catch (InterruptedException ignored) {} synchronizeLog(false); } } diff --git a/api/src/org/labkey/api/module/ModuleLoader.java b/api/src/org/labkey/api/module/ModuleLoader.java index 1f3064b3b06..b487ce09903 100644 --- a/api/src/org/labkey/api/module/ModuleLoader.java +++ b/api/src/org/labkey/api/module/ModuleLoader.java @@ -173,7 +173,6 @@ public class ModuleLoader implements MemTrackerListener, ShutdownListener private static Throwable _startupFailure = null; private static boolean _newInstall = false; - private static TomcatVersion _tomcatVersion = null; private static JavaVersion _javaVersion = null; private static final String BANNER = """ @@ -535,8 +534,6 @@ private void doInit(Execution execution) throws ServletException AppProps.getInstance().setContextPath(_servletContext.getContextPath()); - setTomcatVersion(); - File root = FileUtil.getAbsoluteCaseSensitiveFile(new File(_servletContext.getRealPath(""))).getParentFile(); FileLike labkeyRoot = new FileSystemLike.Builder(root).readwrite().noMemCheck().root(); _webappDir = labkeyRoot.resolveChild("labkeyWebapp"); @@ -1399,23 +1396,6 @@ public JavaVersion getJavaVersion() return _javaVersion; } - /** - * Sets the running Tomcat version, if servlet container is recognized and supported. Otherwise, ConfigurationException is thrown and server fails to start. - *

- * Warnings for deprecated Tomcat versions are handled in CoreWarningProvider. - * - * @throws ConfigurationException if Tomcat version is not recognized or supported - */ - private void setTomcatVersion() - { - _tomcatVersion = TomcatVersion.get(); - } - - public TomcatVersion getTomcatVersion() - { - return _tomcatVersion; - } - /** * Initialize and update the Core module first. We want to change the core tables before we display pages, request * login, check permissions, or initialize any of the other modules. diff --git a/api/src/org/labkey/api/module/TomcatVersion.java b/api/src/org/labkey/api/module/TomcatVersion.java deleted file mode 100644 index 73deb4a0760..00000000000 --- a/api/src/org/labkey/api/module/TomcatVersion.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2017-2018 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.labkey.api.module; - -import org.jetbrains.annotations.NotNull; -import org.junit.Assert; -import org.junit.Test; -import org.labkey.api.util.ConfigurationException; - -import java.util.Arrays; -import java.util.Comparator; -import java.util.Map; -import java.util.stream.Collectors; - -/** - * Enum that specifies the versions of Apache Tomcat that LabKey supports plus their properties - * - * Created by adam on 5/27/2017. - */ -public enum TomcatVersion -{ - TOMCAT_UNSUPPORTED(-1, true), - TOMCAT_10_1(101, false), - TOMCAT_FUTURE(Integer.MAX_VALUE, true); - - private final int _version; - private final boolean _deprecated; - - TomcatVersion(int version, boolean deprecated) - { - _version = version; - _deprecated = deprecated; - } - - // Should LabKey warn administrators that support for this Tomcat version will be removed soon? - public boolean isDeprecated() - { - return _deprecated; - } - - private static final String APACHE_TOMCAT_SERVER_NAME_PREFIX = "Apache Tomcat/"; - - private final static Map VERSION_MAP = Arrays.stream(values()) - .collect(Collectors.toMap(jv->jv._version, jv->jv)); - - private final static int MAX_KNOWN_VERSION = Arrays.stream(values()) - .filter(v->TOMCAT_FUTURE != v) - .map(v->v._version) - .max(Comparator.naturalOrder()) - .orElseThrow(); - - public static TomcatVersion get() - { - String serverInfo = ModuleLoader.getServletContext().getServerInfo(); - - if (serverInfo.startsWith(APACHE_TOMCAT_SERVER_NAME_PREFIX)) - { - String[] versionParts = serverInfo.substring(APACHE_TOMCAT_SERVER_NAME_PREFIX.length()).split("\\."); - - if (versionParts.length > 1) - { - int majorVersion = Integer.valueOf(versionParts[0]); - int minorVersion = Integer.valueOf(versionParts[1]); - - TomcatVersion tv = get(majorVersion * 10 + minorVersion); - - if (TOMCAT_UNSUPPORTED != tv) - return tv; - } - } - - throw new ConfigurationException("Unsupported Tomcat version: " + serverInfo + ". LabKey Server requires Apache Tomcat 10.1.x."); - } - - private static @NotNull TomcatVersion get(int version) - { - if (version > MAX_KNOWN_VERSION) - { - return TOMCAT_FUTURE; - } - else - { - TomcatVersion tv = VERSION_MAP.get(version); - return null != tv ? tv : TOMCAT_UNSUPPORTED; - } - } - - public static class TestCase extends Assert - { - @Test - public void test() - { - // Good - test(101, TOMCAT_10_1); - - // Future - test(110, TOMCAT_FUTURE); - test(120, TOMCAT_FUTURE); - - // Bad - test(100, TOMCAT_UNSUPPORTED); - test(90, TOMCAT_UNSUPPORTED); - test(85, TOMCAT_UNSUPPORTED); - test(80, TOMCAT_UNSUPPORTED); - test(70, TOMCAT_UNSUPPORTED); - test(60, TOMCAT_UNSUPPORTED); - test(50, TOMCAT_UNSUPPORTED); - test(40, TOMCAT_UNSUPPORTED); - } - - private void test(int version, TomcatVersion expectedVersion) - { - Assert.assertEquals(get(version), expectedVersion); - } - } -} diff --git a/api/src/org/labkey/api/util/CheckedInputStream.java b/api/src/org/labkey/api/util/CheckedInputStream.java index 523732a62c3..9c37b0edb58 100644 --- a/api/src/org/labkey/api/util/CheckedInputStream.java +++ b/api/src/org/labkey/api/util/CheckedInputStream.java @@ -15,47 +15,75 @@ */ package org.labkey.api.util; -import org.apache.logging.log4j.LogManager; -import org.jetbrains.annotations.Nullable; +import org.apache.logging.log4j.Logger; import org.labkey.api.miniprofiler.MiniProfiler; +import org.labkey.api.util.logging.LogHelper; import java.io.IOException; import java.io.InputStream; +import java.lang.ref.Cleaner; /** * Verifies that close() was called at some point before finalization; logs an error and creation stack trace if not. * User: adam * Date: 7/2/12 */ - public class CheckedInputStream extends InputStreamWrapper { - @Nullable - private final StackTraceElement[] _creationStackTrace; - private boolean _closed = false; + private static final Cleaner CLEANER = Cleaner.create(); + public static final Logger LOG = LogHelper.getLogger(CheckedInputStream.class, "Utility to ensure InputStreams are closed"); + private final State _state; - public CheckedInputStream(InputStream is) + private static class State implements Runnable { - super(is); - _creationStackTrace = MiniProfiler.getTroubleshootingStackTrace(); + private final InputStream _is; + private final StackTraceElement[] _creationStackTrace; + private boolean _closed = false; + + private State(InputStream is, StackTraceElement[] creationStackTrace) + { + _is = is; + _creationStackTrace = creationStackTrace; + } + + @Override + public void run() + { + if (!_closed) + { + LOG.error("InputStream was not closed. Creation stacktrace:" + ExceptionUtil.renderStackTrace(_creationStackTrace)); + close(); + } + } + + private void close() + { + try + { + _is.close(); + } + catch (IOException e) + { + LOG.error("Failed to close InputStream", e); + } + finally + { + _closed = true; + } + } } - @Override - public void close() throws IOException + public CheckedInputStream(InputStream is) { - _closed = true; - super.close(); + super(is); + StackTraceElement[] creationStackTrace = MiniProfiler.getTroubleshootingStackTrace(); + _state = new State(is, creationStackTrace); + CLEANER.register(this, _state); } @Override - protected void finalize() throws Throwable + public void close() throws IOException { - if (!_closed) - { - LogManager.getLogger(CheckedInputStream.class).error("InputStream was not closed. Creation stacktrace:" + ExceptionUtil.renderStackTrace(_creationStackTrace)); - super.close(); - } - - super.finalize(); + _state.close(); } } diff --git a/api/src/org/labkey/api/util/ExceptionUtil.java b/api/src/org/labkey/api/util/ExceptionUtil.java index 4e2555cf644..b45f6ab80a9 100644 --- a/api/src/org/labkey/api/util/ExceptionUtil.java +++ b/api/src/org/labkey/api/util/ExceptionUtil.java @@ -1572,6 +1572,12 @@ public void sendRedirect(String s) redirect = s; } + @Override + public void sendRedirect(String s, int i, boolean b) throws IOException + { + redirect = s; + } + @Override public void setDateHeader(String s, long l) { diff --git a/core/src/org/labkey/core/view/template/bootstrap/CoreWarningProvider.java b/core/src/org/labkey/core/view/template/bootstrap/CoreWarningProvider.java index a30729155af..ff03803248c 100644 --- a/core/src/org/labkey/core/view/template/bootstrap/CoreWarningProvider.java +++ b/core/src/org/labkey/core/view/template/bootstrap/CoreWarningProvider.java @@ -16,6 +16,7 @@ package org.labkey.core.view.template.bootstrap; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Strings; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -296,21 +297,15 @@ public static Set collectAllDeployedApps() private void addTomcatWarnings(Warnings warnings, boolean showAllWarnings) { - if (showAllWarnings || ModuleLoader.getInstance().getTomcatVersion().isDeprecated()) - { - String serverInfo = ModuleLoader.getServletContext().getServerInfo(); - addStandardWarning(warnings, "The deployed version of Tomcat, " + serverInfo + ", is not supported.", "supported", "Supported Technologies page"); - } - try { Set deployedWebapps = new HashSet<>(collectAllDeployedApps()); deployedWebapps.remove(StringUtils.strip(AppProps.getInstance().getContextPath(),"/")); boolean defaultTomcatWebappFound = deployedWebapps.stream().anyMatch(webapp -> - StringUtils.startsWithIgnoreCase(webapp,"docs") || - StringUtils.startsWithIgnoreCase(webapp,"host-manager") || - StringUtils.startsWithIgnoreCase(webapp,"examples") || - StringUtils.startsWithIgnoreCase(webapp,"manager") + Strings.CI.startsWith(webapp,"docs") || + Strings.CI.startsWith(webapp,"host-manager") || + Strings.CI.startsWith(webapp,"examples") || + Strings.CI.startsWith(webapp,"manager") ); if (showAllWarnings || (defaultTomcatWebappFound && !AppProps.getInstance().isDevMode()))