Skip to content

Commit

Permalink
KNOX-3051: Ability to extend classpath with configurable paths (#971)
Browse files Browse the repository at this point in the history
* KNOX-3051: Ability to extend classpath with configurable paths

* KNOX-3051: Code cleanup

* KNOX-3051: Rename Extender class, modified Extender to not retain reference to the properties
  • Loading branch information
hanicz authored Dec 5, 2024
1 parent 683c499 commit 4748771
Show file tree
Hide file tree
Showing 4 changed files with 467 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.
GatewayServerClasspathExtender gatewayServerClasspathExtender;

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.gatewayServerClasspathExtender = new GatewayServerClasspathExtender( base );
consumeConfig( config );
}

void consumeConfig( Properties config ) throws MalformedURLException {
void consumeConfig( Properties config ) throws IOException {
gatewayServerClasspathExtender.extendClassPathProperty(config);
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,75 @@
/*
* 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 GatewayServerClasspathExtender {

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 Pattern pattern = Pattern.compile(CLASSPATH_PROPERTY_PATTERN, Pattern.DOTALL);

public GatewayServerClasspathExtender(File base) {
this.base = base;
}

public void extendClassPathProperty(Properties properties) 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, properties);
}
}

protected void extractExtensionPathIntoProperty(String configContent, Properties properties) {
final 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,176 @@
/*
* 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.After;
import org.junit.Before;
import org.junit.Test;

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.util.Properties;

import static org.junit.Assert.assertEquals;

public class GatewayServerClasspathExtenderTest {

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

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

@After
public void cleanUpDirs() throws IOException {
Files.deleteIfExists(configFilePath);
Files.deleteIfExists(confDir);
Files.deleteIfExists(tempDir);
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

String configContent = this.getConfigContent("");
gatewayServerClasspathExtender.extractExtensionPathIntoProperty(configContent, properties);

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

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

String configContent = this.getConfigContent(" ");
gatewayServerClasspathExtender.extractExtensionPathIntoProperty(configContent, properties);

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

@Test
public void extractExtensionPathIntoPropertyNoConfigTest() throws IOException {
Properties properties = new Properties();
properties.setProperty("class.path", "classpath");
GatewayServerClasspathExtender gatewayServerClasspathExtender = new GatewayServerClasspathExtender(null);

ClassLoader classLoader = getClass().getClassLoader();
File file = new File(classLoader.getResource("gateway-site-test.xml").getFile());

gatewayServerClasspathExtender.extractExtensionPathIntoProperty(new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8), properties);

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>";
}
}
Loading

0 comments on commit 4748771

Please sign in to comment.