Skip to content

Commit

Permalink
KNOX-3051: Ability to extend classpath with configurable paths
Browse files Browse the repository at this point in the history
  • Loading branch information
hanicz committed Dec 1, 2024
1 parent 683c499 commit 95953e8
Show file tree
Hide file tree
Showing 3 changed files with 278 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
Expand Down Expand Up @@ -53,14 +54,17 @@ class Command {
Boolean fork = Boolean.FALSE;
Boolean redirect = Boolean.FALSE; // Controls redirecting stderr to stdout if forking.
Boolean restream = Boolean.TRUE; // Controls creation of threads to read/write stdin, stdout, stderr of child if forking.
Extender extender;

Command( File base, Properties config, String[] args ) throws MalformedURLException {
Command( File base, Properties config, String[] args ) throws IOException {
this.base = base;
this.mainArgs = args ;
this.extender = new Extender( base, config );
consumeConfig( config );
}

void consumeConfig( Properties config ) throws MalformedURLException {
void consumeConfig( Properties config ) throws IOException {
extender.extendClassPathProperty();
mainClass = config.getProperty( MAIN_CLASS );
config.remove( MAIN_CLASS );
mainMethod = config.getProperty( MAIN_METHOD, mainMethod );
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.knox.gateway.launcher;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Extender {

private static final String CLASSPATH_EXTENSION_PROPERTY = "gateway.server.classpath.extension";
private static final String CLASSPATH_PROPERTY_PATTERN = "<property>\\s*<name>" + CLASSPATH_EXTENSION_PROPERTY + "</name>\\s*<value>(.*?)</value>\\s*</property>";
private static final String CONFIG_FILE = "gateway-site.xml";
private static final String CONFIG_PATH = "../conf/" + CONFIG_FILE;
private static final String CLASS_PATH_PROPERTY = "class.path";
private static final String MAIN_CLASS_PROPERTY = "main.class";
private static final String GATEWAY_SERVER_MAIN_CLASS = "org.apache.knox.gateway.GatewayServer";
private static final String[] CLASS_PATH_DELIMITERS = new String[]{",", ";"};

private final File base;
private final Properties properties;

public Extender(File base, Properties properties) {
this.base = base;
this.properties = properties;
}

public void extendClassPathProperty() throws IOException {
Path configFilePath = Paths.get(base.getPath(), CONFIG_PATH);
if (GATEWAY_SERVER_MAIN_CLASS.equals(properties.getProperty(MAIN_CLASS_PROPERTY)) && Files.isReadable(configFilePath)) {
String configContent = new String(Files.readAllBytes(configFilePath), StandardCharsets.UTF_8);
extractExtensionPathIntoProperty(configContent);
}
}

protected void extractExtensionPathIntoProperty(String configContent) {
Pattern pattern = Pattern.compile(CLASSPATH_PROPERTY_PATTERN, Pattern.DOTALL);
Matcher matcher = pattern.matcher(configContent);

if (matcher.find()) {
StringBuilder newClassPath = new StringBuilder(matcher.group(1).trim());
if (newClassPath.length() > 0) {
if (!endsWithDelimiter(newClassPath.toString())) {
newClassPath.append(CLASS_PATH_DELIMITERS[1]);
}
newClassPath.append(properties.getProperty(CLASS_PATH_PROPERTY));
properties.setProperty(CLASS_PATH_PROPERTY, newClassPath.toString());
}
}
}

private boolean endsWithDelimiter(String path) {
return Arrays.stream(CLASS_PATH_DELIMITERS).anyMatch(path::endsWith);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.knox.gateway.launcher;


import org.junit.Test;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Properties;

import static org.junit.Assert.assertEquals;

public class ExtenderTest {

private Path tempDir;
private Path confDir;
private Path configFilePath;

@Test
public void extendClassPathPropertyTest() throws IOException {
this.setupDirs();
Properties properties = new Properties();
properties.setProperty("class.path", "classpath");
properties.setProperty("main.class", "org.apache.knox.gateway.GatewayServer");
Extender extender = new Extender(confDir.toFile(), properties);

String configContent = this.getConfigContent("/new/classp/*");
Files.write(configFilePath, configContent.getBytes(StandardCharsets.UTF_8));
extender.extendClassPathProperty();

assertEquals("/new/classp/*;classpath", properties.getProperty("class.path"));
this.cleanUpDirs();
}

@Test
public void extendClassPathPropertyDifferentMainClassTest() throws IOException {
this.setupDirs();
Properties properties = new Properties();
properties.setProperty("class.path", "classpath");
properties.setProperty("main.class", "org.apache.knox.gateway.KnoxCLI");
Extender extender = new Extender(confDir.toFile(), properties);

String configContent = this.getConfigContent("/new/classp/*");
Files.write(configFilePath, configContent.getBytes(StandardCharsets.UTF_8));
extender.extendClassPathProperty();

assertEquals("classpath", properties.getProperty("class.path"));
this.cleanUpDirs();
}

@Test
public void extractExtensionPathIntoPropertyNoDelimTest() {
Properties properties = new Properties();
properties.setProperty("class.path", "classpath");
Extender extender = new Extender(null, properties);

String configContent = this.getConfigContent("/new/classp/*");
extender.extractExtensionPathIntoProperty(configContent);

assertEquals("/new/classp/*;classpath", properties.getProperty("class.path"));
}

@Test
public void extractExtensionPathIntoPropertyXMLFormatTest() {
Properties properties = new Properties();
properties.setProperty("class.path", "classpath");
Extender extender = new Extender(null, properties);

String configContent = this.getConfigContent("/new/classp/*;");
extender.extractExtensionPathIntoProperty(configContent);

assertEquals("/new/classp/*;classpath", properties.getProperty("class.path"));
}

@Test
public void extractExtensionPathIntoPropertyWhitespaceTest() {
Properties properties = new Properties();
properties.setProperty("class.path", "classpath");
Extender extender = new Extender(null, properties);

String configContent = this.getConfigContent(" /new/classp/*; ");
extender.extractExtensionPathIntoProperty(configContent);

assertEquals("/new/classp/*;classpath", properties.getProperty("class.path"));
}

@Test
public void extractExtensionPathIntoPropertyMultipleTest() {
Properties properties = new Properties();
properties.setProperty("class.path", "classpath");
Extender extender = new Extender(null, properties);

String configContent = this.getConfigContent("/new/classp/*,../classp");
extender.extractExtensionPathIntoProperty(configContent);

assertEquals("/new/classp/*,../classp;classpath", properties.getProperty("class.path"));
}

@Test
public void extractExtensionPathIntoPropertyEmptyTest() {
Properties properties = new Properties();
properties.setProperty("class.path", "classpath");
Extender extender = new Extender(null, properties);

String configContent = this.getConfigContent("");
extender.extractExtensionPathIntoProperty(configContent);

assertEquals("classpath", properties.getProperty("class.path"));
}

@Test
public void extractExtensionPathIntoPropertyEmptyWhitespaceTest() {
Properties properties = new Properties();
properties.setProperty("class.path", "classpath");
Extender extender = new Extender(null, properties);

String configContent = this.getConfigContent(" ");
extender.extractExtensionPathIntoProperty(configContent);

assertEquals("classpath", properties.getProperty("class.path"));
}

@Test
public void extractExtensionPathIntoPropertyNoConfigTest() {
Properties properties = new Properties();
properties.setProperty("class.path", "classpath");
Extender extender = new Extender(null, properties);

String configContent =
"<configuration>\n" +
" <property>\n" +
" <name>gateway.webshell.read.buffer.size</name>\n" +
" <value>1024</value>\n" +
" <description>Web Shell buffer size for reading</description>\n" +
" </property>\n" +
"\n" +
" <!-- @since 2.0.0 websocket JWT validation configs -->\n" +
" <property>\n" +
" <name>gateway.websocket.JWT.validation.feature.enabled</name>\n" +
" <value>true</value>\n" +
" <description>Enable/Disable websocket JWT validation at websocket layer.</description>\n" +
" </property>\n" +
"\n" +
" <!-- @since 1.5.0 homepage logout -->\n" +
" <property>\n" +
" <name>knox.homepage.logout.enabled</name>\n" +
" <value>true</value>\n" +
" <description>Enable/disable logout from the Knox Homepage.</description>\n" +
" </property>\n" +
"</configuration>";

extender.extractExtensionPathIntoProperty(configContent);

assertEquals("classpath", properties.getProperty("class.path"));
}

private String getConfigContent(String extensionValue) {
return "<configuration>\n" +
" <property>\n" +
" <name>gateway.server.classpath.extension</name>\n" +
" <value>" + extensionValue + "</value>\n" +
" </property>\n" +
"</configuration>";
}

private void setupDirs() throws IOException {
tempDir = Files.createTempDirectory("cp_extender_test");
confDir = Files.createDirectory(tempDir.resolve("conf"));
configFilePath = confDir.resolve("gateway-site.xml");
}

private void cleanUpDirs() throws IOException {
Files.deleteIfExists(configFilePath);
Files.deleteIfExists(confDir);
Files.deleteIfExists(tempDir);
}
}

0 comments on commit 95953e8

Please sign in to comment.