diff --git a/server/configs/application.properties b/server/configs/application.properties index 6988270391..9f405cb8d7 100644 --- a/server/configs/application.properties +++ b/server/configs/application.properties @@ -32,6 +32,20 @@ context.validationQuery[0]=SELECT 1 #useLocalBuild#context.webAppLocation=@@pathToServer@@/build/deploy/labkeyWebapp context.encryptionKey=@@encryptionKey@@ + +# By default, we'll deploy to the root context path. However, some servers have historically used /labkey or even /cpas +#context.contextPath=/labkey + +# Using a legacy context path provides backwards compatibility with old deployments. A typical use case would be to +# deploy to the root context (the default) and configure /labkey as the legacy path. GETs will be redirected, and +# non-GETs will be handled server-side via a servlet forward. +#context.legacyContextPath=/labkey + +# Other webapps to be deployed, most commonly to deliver a set of static files. Additional +# webapps can be specified via additional indices. docBase should point at the root of the webapp's content +#webapps.contextPath[0]=/anotherWebapp +#webapps.docBase[0]=/my/webapp/path + #context.oldEncryptionKey= #context.requiredModules= #context.pipelineConfig=/path/to/pipeline/config/dir diff --git a/server/embedded/src/org/labkey/embedded/LabKeyServer.java b/server/embedded/src/org/labkey/embedded/LabKeyServer.java index 04d2f8b309..bddf870aa9 100644 --- a/server/embedded/src/org/labkey/embedded/LabKeyServer.java +++ b/server/embedded/src/org/labkey/embedded/LabKeyServer.java @@ -90,6 +90,12 @@ public JmsProperties jmsSource() return new JmsProperties(); } + @Bean + public WebappProperties additionalWebappSource() + { + return new WebappProperties(); + } + @Bean public CSPFilterProperties cspSource() { @@ -160,7 +166,7 @@ protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) } // set the root path to the context explicitly - context.setPath(""); + context.setPath(contextProperties.getContextPath()); // Push the JDBC connection for the primary DB into the context so that the LabKey webapp finds them getDataSourceResources(contextProperties, context).forEach(contextResource -> context.getNamingResources().addResource(contextResource)); @@ -187,6 +193,14 @@ protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) context.addParameter("OldEncryptionKey", contextProperties.getOldEncryptionKey()); } + if (contextProperties.getLegacyContextPath() != null) + { + if (contextProperties.getContextPath() != null && !contextProperties.getContextPath().isEmpty() && !contextProperties.getContextPath().equals("/")) + { + throw new IllegalArgumentException("contextPath.legacyContextPath is only intended for use when deploying the LabKey application to the root context path. Please update application.properties."); + } + context.addParameter("legacyContextPath", contextProperties.getLegacyContextPath()); + } if (contextProperties.getRequiredModules() != null) { context.addParameter("requiredModules", contextProperties.getRequiredModules()); @@ -226,6 +240,18 @@ protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) configureJsonAccessLogging(tomcat, logConfig); } + WebappProperties additionalWebapps = additionalWebappSource(); + if (additionalWebapps.getContextPath().size() != additionalWebapps.getDocBase().size()) + { + throw new IllegalArgumentException("Additional webapps must have paired contextPath and docBase properties"); + } + for (int i = 0; i < additionalWebapps.getContextPath().size(); i++) + { + String contextPath = additionalWebapps.getContextPath().get(i); + String docBase = additionalWebapps.getDocBase().get(i); + tomcat.addWebapp(contextPath, docBase); + } + return super.getTomcatWebServer(tomcat); } @@ -583,6 +609,35 @@ public void setConditionUnless(String conditionUnless) } } + @Configuration + @ConfigurationProperties("webapps") + public static class WebappProperties + { + private List contextPath = new ArrayList<>(); + + private List docBase = new ArrayList<>(); + + public List getContextPath() + { + return contextPath; + } + + public void setContextPath(List contextPath) + { + this.contextPath = contextPath; + } + + public List getDocBase() + { + return docBase; + } + + public void setDocBase(List docBase) + { + this.docBase = docBase; + } + } + @Validated @Configuration @ConfigurationProperties("context") @@ -604,6 +659,10 @@ public static class ContextProperties @NotNull (message = "Must provide encryptionKey") private String encryptionKey; private String oldEncryptionKey; + private String legacyContextPath; + + // Default to deploying to the root context path + private String contextPath = ""; private String pipelineConfig; private String requiredModules; private boolean bypass2FA = false; @@ -706,6 +765,26 @@ public void setOldEncryptionKey(String oldEncryptionKey) this.oldEncryptionKey = oldEncryptionKey; } + public String getLegacyContextPath() + { + return legacyContextPath; + } + + public void setLegacyContextPath(String legacyContextPath) + { + this.legacyContextPath = legacyContextPath; + } + + public String getContextPath() + { + return contextPath; + } + + public void setContextPath(String contextPath) + { + this.contextPath = contextPath; + } + public String getPipelineConfig() { return pipelineConfig;