diff --git a/.gitattributes b/.gitattributes index 34eb4ac7c18..13dd8dfbeb3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -253,7 +253,6 @@ api/src/org/labkey/api/assay/AssaySaveHandler.java -text api/src/org/labkey/api/assay/AssaySchema.java -text api/src/org/labkey/api/assay/AssayService.java -text api/src/org/labkey/api/assay/AssayTableMetadata.java -text -api/src/org/labkey/api/assay/AssayUploadFileResolver.java -text api/src/org/labkey/api/assay/AssayUploadXarContext.java -text api/src/org/labkey/api/assay/AssayUrls.java -text api/src/org/labkey/api/assay/AssayView.java -text @@ -618,7 +617,6 @@ api/src/org/labkey/api/exceptions/TableNotFoundException.java -text api/src/org/labkey/api/exp/AbstractParameter.java -text api/src/org/labkey/api/exp/api/AbstractExperimentDataHandler.java -text api/src/org/labkey/api/exp/api/AbstractProtocolInputCriteria.java -text -api/src/org/labkey/api/exp/api/AssayDomainType.java -text api/src/org/labkey/api/exp/api/AssayJSONConverter.java -text api/src/org/labkey/api/exp/api/DataType.java -text api/src/org/labkey/api/exp/api/DefaultExperimentDataHandler.java -text diff --git a/api/src/org/labkey/api/action/ConfirmAction.java b/api/src/org/labkey/api/action/ConfirmAction.java index afc2c83d738..515a18598c5 100644 --- a/api/src/org/labkey/api/action/ConfirmAction.java +++ b/api/src/org/labkey/api/action/ConfirmAction.java @@ -77,7 +77,7 @@ public final ModelAndView handleRequest() throws Exception ModelAndView mv = getSuccessView(form); if (null != mv) return mv; - throw new RedirectException(getSuccessURL(form)); + throw new RedirectException(getSuccessURL(form), false); } } else diff --git a/api/src/org/labkey/api/settings/LookAndFeelProperties.java b/api/src/org/labkey/api/settings/LookAndFeelProperties.java index d0d909d9437..91069e1bd4b 100644 --- a/api/src/org/labkey/api/settings/LookAndFeelProperties.java +++ b/api/src/org/labkey/api/settings/LookAndFeelProperties.java @@ -277,7 +277,7 @@ public Boolean isDiscussionEnabledStored() public String getUnsubstitutedLogoHref() { - return lookupStringValue(logoHref, AppProps.getInstance().getHomePageUrl().replaceAll("^" + AppProps.getInstance().getContextPath(), "\\${contextPath}")); + return lookupStringValue(logoHref, AppProps.getInstance().getHomePageActionURL().getLocalURIString().replaceAll("^" + AppProps.getInstance().getContextPath(), "\\${contextPath}")); } public String getUnsubstitutedLogoHrefStored() diff --git a/api/src/org/labkey/api/view/HttpView.java b/api/src/org/labkey/api/view/HttpView.java index a3a543ed2a3..e4f39169f3e 100644 --- a/api/src/org/labkey/api/view/HttpView.java +++ b/api/src/org/labkey/api/view/HttpView.java @@ -594,13 +594,12 @@ protected void renderInternal(Object model, PrintWriter out) throws IOException public static HttpView redirect(URLHelper url, boolean allowAbsoluteUrl) { - String redirectUrl = (!allowAbsoluteUrl || url.isLocalUri(getRootContext())) ? url.getLocalURIString() : url.getURIString(); - throw new RedirectException(redirectUrl); + throw new RedirectException(url, allowAbsoluteUrl); } public static HttpView redirect(ActionURL url) { - throw new RedirectException(url.getLocalURIString()); + throw new RedirectException(url); } /** diff --git a/api/src/org/labkey/api/view/PermanentRedirectException.java b/api/src/org/labkey/api/view/PermanentRedirectException.java index e2153508513..8a57ee905c6 100644 --- a/api/src/org/labkey/api/view/PermanentRedirectException.java +++ b/api/src/org/labkey/api/view/PermanentRedirectException.java @@ -5,12 +5,12 @@ import jakarta.servlet.http.HttpServletResponse; -/** Use when we want search engines, browsers, etc to assume that the redirecting URL is defunct and the target URL should be used going forward */ +/** Use when we want search engines, browsers, etc. to assume that the redirecting URL is defunct and the target URL should be used going forward */ public class PermanentRedirectException extends RedirectException { public PermanentRedirectException(@NotNull URLHelper url) { - super(url); + super(url, false); } @Override diff --git a/api/src/org/labkey/api/view/RedirectException.java b/api/src/org/labkey/api/view/RedirectException.java index 429adcafd0b..1c8040defec 100644 --- a/api/src/org/labkey/api/view/RedirectException.java +++ b/api/src/org/labkey/api/view/RedirectException.java @@ -29,7 +29,18 @@ public class RedirectException extends RuntimeException implements SkipMothershi { private final String _url; + @Deprecated // Call the variant that takes allowAbsoluteUrl public RedirectException(@NotNull URLHelper url) + { + this(url, false); + } + + public RedirectException(@NotNull URLHelper url, boolean allowAbsoluteUrl) + { + this(!allowAbsoluteUrl || url.isLocalUri(HttpView.getRootContext()) ? url.getLocalURIString() : url.getURIString()); + } + + public RedirectException(@NotNull ActionURL url) { this(url.getLocalURIString()); } diff --git a/core/src/org/labkey/core/login/termsOfUse.jsp b/core/src/org/labkey/core/login/termsOfUse.jsp index 5face19abc0..8cfa7bb0445 100644 --- a/core/src/org/labkey/core/login/termsOfUse.jsp +++ b/core/src/org/labkey/core/login/termsOfUse.jsp @@ -42,7 +42,7 @@ // Redirect immediately if terms are blank or null if (HtmlString.isBlank(termsHtml)) - throw new RedirectException(returnUrl); + throw new RedirectException(returnUrl, false); ActionURL formURL = urlFor(AgreeToTermsAction.class); %> diff --git a/core/src/org/labkey/core/thumbnail/ThumbnailCache.java b/core/src/org/labkey/core/thumbnail/ThumbnailCache.java index e0d656bfc23..46ebc30662e 100644 --- a/core/src/org/labkey/core/thumbnail/ThumbnailCache.java +++ b/core/src/org/labkey/core/thumbnail/ThumbnailCache.java @@ -54,7 +54,7 @@ public static CacheableWriter getThumbnailWriter(ThumbnailProvider provider, Ima URLHelper redirectURL = ThumbnailUtil.getStaticThumbnailURL(provider, imageType); if (null != redirectURL) - throw new RedirectException(redirectURL); + throw new RedirectException(redirectURL, false); } return dynamic; diff --git a/core/src/org/labkey/core/webdav/DavController.java b/core/src/org/labkey/core/webdav/DavController.java index 94fe49602f8..89243402e5f 100644 --- a/core/src/org/labkey/core/webdav/DavController.java +++ b/core/src/org/labkey/core/webdav/DavController.java @@ -18,6 +18,12 @@ package org.labkey.core.webdav; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponseWrapper; +import jakarta.servlet.http.HttpSession; +import jakarta.servlet.http.Part; import org.apache.commons.beanutils.ConversionException; import org.apache.commons.collections4.IteratorUtils; import org.apache.commons.io.FileUtils; @@ -72,7 +78,28 @@ import org.labkey.api.settings.LookAndFeelProperties; import org.labkey.api.settings.OptionalFeatureService; import org.labkey.api.test.TestWhen; -import org.labkey.api.util.*; +import org.labkey.api.util.CSRFException; +import org.labkey.api.util.CSRFUtil; +import org.labkey.api.util.ConfigurationException; +import org.labkey.api.util.DateUtil; +import org.labkey.api.util.ErrorRenderer; +import org.labkey.api.util.ExceptionUtil; +import org.labkey.api.util.FileStream; +import org.labkey.api.util.FileUtil; +import org.labkey.api.util.GUID; +import org.labkey.api.util.HeartBeat; +import org.labkey.api.util.HttpUtil; +import org.labkey.api.util.MemTracker; +import org.labkey.api.util.PageFlowUtil; +import org.labkey.api.util.Pair; +import org.labkey.api.util.Path; +import org.labkey.api.util.ResponseHelper; +import org.labkey.api.util.ShutdownListener; +import org.labkey.api.util.StringUtilsLabKey; +import org.labkey.api.util.URIUtil; +import org.labkey.api.util.URLHelper; +import org.labkey.api.util.UnexpectedException; +import org.labkey.api.util.XmlBeansUtil; import org.labkey.api.util.logging.LogHelper; import org.labkey.api.view.ActionURL; import org.labkey.api.view.DefaultModelAndView; @@ -115,14 +142,7 @@ import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpServletResponseWrapper; -import jakarta.servlet.http.HttpSession; -import jakarta.servlet.http.Part; import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; @@ -194,8 +214,6 @@ /** - * User: matthewb - * Date: Oct 3, 2007 * Derived from Tomcat's WebdavServlet */ public class DavController extends SpringActionController @@ -873,7 +891,7 @@ public final WebdavStatus doMethod() throws DavException, IOException throw x; // NOTE: AppProps.getInstance().getSiteWelcomePageUrlString() is handled before we even get here. See WebdavServlet. if (c.isRoot()) - throw new RedirectException(AppProps.getInstance().getHomePageUrl()); + throw new RedirectException(AppProps.getInstance().getHomePageActionURL()); throw new RedirectException(c.getStartURL(getUser())); } } diff --git a/devtools/src/org/labkey/devtools/DevtoolsContainerListener.java b/devtools/src/org/labkey/devtools/DevtoolsContainerListener.java deleted file mode 100644 index 3a33c224027..00000000000 --- a/devtools/src/org/labkey/devtools/DevtoolsContainerListener.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2019 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.devtools; - -import org.jetbrains.annotations.NotNull; -import org.labkey.api.data.Container; -import org.labkey.api.data.ContainerManager.ContainerListener; -import org.labkey.api.security.User; -import java.util.Collections; -import java.util.Collection; - -import java.beans.PropertyChangeEvent; - -public class DevtoolsContainerListener implements ContainerListener -{ - @Override - public void containerCreated(Container c, User user) - { - } - - @Override - public void containerDeleted(Container c, User user) - { - } - - @Override - public void propertyChange(PropertyChangeEvent evt) - { - } - - @Override - public void containerMoved(Container c, Container oldParent, User user) - { - } - - @NotNull @Override - public Collection canMove(Container c, Container newParent, User user) - { - return Collections.emptyList(); - } -} \ No newline at end of file diff --git a/devtools/src/org/labkey/devtools/DevtoolsManager.java b/devtools/src/org/labkey/devtools/DevtoolsManager.java deleted file mode 100644 index 61039e7603f..00000000000 --- a/devtools/src/org/labkey/devtools/DevtoolsManager.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2019 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.devtools; - -public class DevtoolsManager -{ - private static final DevtoolsManager _instance = new DevtoolsManager(); - - private DevtoolsManager() - { - // prevent external construction with a private default constructor - } - - public static DevtoolsManager get() - { - return _instance; - } -} \ No newline at end of file diff --git a/devtools/src/org/labkey/devtools/DevtoolsModule.java b/devtools/src/org/labkey/devtools/DevtoolsModule.java index 057033145c1..d391f62baa0 100644 --- a/devtools/src/org/labkey/devtools/DevtoolsModule.java +++ b/devtools/src/org/labkey/devtools/DevtoolsModule.java @@ -17,7 +17,6 @@ package org.labkey.devtools; import org.jetbrains.annotations.NotNull; -import org.labkey.api.data.ContainerManager; import org.labkey.api.exp.property.Domain; import org.labkey.api.module.CodeOnlyModule; import org.labkey.api.module.ModuleContext; @@ -72,8 +71,6 @@ protected void init() @Override public void doStartup(ModuleContext moduleContext) { - // add a container listener so we'll know when our container is deleted: - ContainerManager.addContainerListener(new DevtoolsContainerListener()); } @Override diff --git a/devtools/src/org/labkey/devtools/ToolsController.java b/devtools/src/org/labkey/devtools/ToolsController.java index e2a8289ae21..07ea6c23182 100644 --- a/devtools/src/org/labkey/devtools/ToolsController.java +++ b/devtools/src/org/labkey/devtools/ToolsController.java @@ -1,5 +1,7 @@ package org.labkey.devtools; +import org.apache.commons.collections4.MultiValuedMap; +import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -7,7 +9,10 @@ import org.labkey.api.action.SimpleErrorView; import org.labkey.api.action.SimpleViewAction; import org.labkey.api.action.SpringActionController; +import org.labkey.api.data.ColumnInfo; +import org.labkey.api.data.DbSchemaType; import org.labkey.api.data.DbScope; +import org.labkey.api.data.TableInfo.IndexType; import org.labkey.api.module.Module; import org.labkey.api.module.ModuleLoader; import org.labkey.api.module.SupportedDatabase; @@ -16,6 +21,7 @@ import org.labkey.api.security.permissions.AdminPermission; import org.labkey.api.util.BaseScanner.Handler; import org.labkey.api.util.ButtonBuilder; +import org.labkey.api.util.DOM; import org.labkey.api.util.FileUtil; import org.labkey.api.util.HtmlString; import org.labkey.api.util.HtmlStringBuilder; @@ -43,6 +49,7 @@ import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; +import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; @@ -55,6 +62,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.labkey.api.util.DOM.Attribute.style; import static org.labkey.api.util.PageFlowUtil.filter; public class ToolsController extends SpringActionController @@ -688,7 +696,7 @@ public ModelAndView getView(Object o, BindException errors) .map(Module::getName) .toList(); - return new HtmlView(HtmlString.of(names.toString())); + return new HtmlView(HtmlString.of(names.isEmpty() ? "None" : names.toString())); } @Override @@ -698,4 +706,114 @@ public void addNavTrail(NavTree root) root.addChild("PostgreSQL-Only Modules That Still Have SQL Server Scripts"); } } + + @RequiresPermission(AdminPermission.class) + public class OverlappingIndicesAction extends SimpleViewAction + { + @Override + public ModelAndView getView(Object o, BindException errors) + { + MultiValuedMap mmap = new ArrayListValuedHashMap<>(); + DbScope scope = DbScope.getLabKeyScope(); + + ModuleLoader.getInstance().getModules().stream() + .flatMap(module -> module.getSchemaNames().stream().filter(name -> !module.getProvisionedSchemaNames().contains(name))) + .sorted(String.CASE_INSENSITIVE_ORDER) + .map(name -> scope.getSchema(name, DbSchemaType.Module)) + .flatMap(schema -> schema.getTableNames().stream().map(schema::getTable)) + .forEach(table -> { + var indices = table.getAllIndices(); + indices.forEach((indexName1, indexDef1) -> indices.forEach((indexName2, indexDef2) -> { + if (indexDef1 != indexDef2) + { + OverlapType type = overlap(indexDef1.getKey(), indexDef1.getValue(), indexDef2.getKey(), indexDef2.getValue()); + + if (type != null) + { + switch (type) + { + case Identical -> mmap.put(type, new Row(table.getSchema().getName(), indexName1 + " vs. " + indexName2 + ": " + join(indexDef1.getValue()))); + case Overlap, UniqueOverlappingNonUnique -> mmap.put(type, new Row(table.getSchema().getName(), indexName1 + " " + join(indexDef1.getValue()) + " vs. " + indexName2 + " " + join(indexDef2.getValue()))); + } + } + } + })); + }); + + return new HtmlView(DOM.createHtmlFragment( + Arrays.stream(OverlapType.values()).flatMap(type -> + Stream.of( + type != OverlapType.Identical ? DOM.BR() : null, + DOM.STRONG(StringUtilsLabKey.pluralize(mmap.get(type).size(), "index has ", "indices have ") + type.getDescription() + ":", DOM.BR()), + DOM.TABLE( + mmap.get(type).stream() + .map(row -> DOM.TR( + DOM.TD(DOM.at(style, "width:120px;"), row.schemaName()), + DOM.TD(row.message())) + ) + ) + ) + )) + ); + } + + private record Row(String schemaName, String message) {} + + private enum OverlapType + { + Identical("identical column sets"), + Overlap("column sets that overlap at the start"), + UniqueOverlappingNonUnique("column sets that overlap at the start, but the first index is a unique constraint"); + + private final String _description; + + OverlapType(String description) + { + _description = description; + } + + public String getDescription() + { + return _description; + } + } + + private @Nullable OverlapType overlap(IndexType type1, List cols1, IndexType type2, List cols2) + { + String key1 = getKey(cols1); + String key2 = getKey(cols2); + if (key1.equals(key2)) + return OverlapType.Identical; + if (key2.startsWith(key1)) + { + if (type2 == IndexType.NonUnique && (type1 == IndexType.Primary || type1 == IndexType.Unique)) + return OverlapType.UniqueOverlappingNonUnique; + else + return OverlapType.Overlap; + } + return null; + } + + private String getKey(List cols) + { + String delim = Character.toString(31); // Non-printing character that's very unlikely to be in a column name + return cols.stream() + .map(col -> col.getName().toLowerCase()) + .collect(Collectors.joining(delim)) + delim; + } + + private List join(List cols) + { + return cols.stream() + .map(ColumnInfo::getName) + .toList(); + } + + @Override + public void addNavTrail(NavTree root) + { + addBeginNavTrail(root); + root.addChild("Overlapping Indices"); + } + } } diff --git a/experiment/src/org/labkey/experiment/controllers/exp/ExperimentController.java b/experiment/src/org/labkey/experiment/controllers/exp/ExperimentController.java index c920f3c7e20..17197b3a885 100644 --- a/experiment/src/org/labkey/experiment/controllers/exp/ExperimentController.java +++ b/experiment/src/org/labkey/experiment/controllers/exp/ExperimentController.java @@ -2510,7 +2510,7 @@ protected ModelAndView getDataView(DataForm form, BindException errors) throws I URLHelper url = h.getShowFileURL(_data); if (url != null) { - throw new RedirectException(url); + throw new RedirectException(url, false); } } }