Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Glassfish service name detector #579

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.resourceproviders;

import static java.util.logging.Level.FINE;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import java.util.logging.Logger;
import java.util.stream.Stream;
import javax.annotation.Nullable;

final class AppServerServiceNameDetector implements ServiceNameDetector {

private static final Logger logger =
Logger.getLogger(AppServerServiceNameDetector.class.getName());

private final AppServer appServer;
private final ParseBuddy parseBuddy;
private final DirectoryTool dirTool;

AppServerServiceNameDetector(AppServer appServer) {
this(appServer, new ParseBuddy(appServer), new DirectoryTool());
}

// Exists for testing
AppServerServiceNameDetector(AppServer appServer, ParseBuddy parseBuddy, DirectoryTool dirTool) {
this.appServer = appServer;
this.parseBuddy = parseBuddy;
this.dirTool = dirTool;
}

@Override
public @Nullable String detect() throws Exception {
if (appServer.getServerClass() == null) {
return null;
}

Path deploymentDir = appServer.getDeploymentDir();
if (deploymentDir == null) {
return null;
}

if (!dirTool.isDirectory(deploymentDir)) {
logger.log(FINE, "Deployment dir '{0}' doesn't exist.", deploymentDir);
return null;
}

logger.log(FINE, "Looking for deployments in '{0}'.", deploymentDir);
try (Stream<Path> stream = dirTool.list(deploymentDir)) {
return stream.map(this::detectName).filter(Objects::nonNull).findFirst().orElse(null);
}
}

private String detectName(Path path) {
if (!appServer.isValidAppName(path)) {
logger.log(FINE, "Skipping '{0}'.", path);
return null;
}

logger.log(FINE, "Attempting service name detection in '{0}'.", path);
String name = path.getFileName().toString();
if (dirTool.isDirectory(path)) {
return parseBuddy.handleExplodedApp(path);
}
if (name.endsWith(".war")) {
return parseBuddy.handlePackagedWar(path);
}
if (appServer.supportsEar() && name.endsWith(".ear")) {
return parseBuddy.handlePackagedEar(path);
}

return null;
}

// Exists for testing
static class DirectoryTool {
boolean isDirectory(Path path) {
return Files.isDirectory(path);
}

Stream<Path> list(Path path) throws IOException {
return Files.list(path);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package io.opentelemetry.resourceproviders;

import java.net.URL;
import java.util.Collections;
import java.util.List;

Expand All @@ -21,7 +22,29 @@ static ServiceNameDetector create() {
private CommonAppServersServiceNameDetector() {}

private static List<ServiceNameDetector> detectors() {
// TBD: This will contain common app server detector implementations
return Collections.emptyList();
ResourceLocator locator = new ResourceLocatorImpl();
// Additional implementations will be added to this list.
return Collections.singletonList(detectorFor(new GlassfishAppServer(locator)));
}

private static AppServerServiceNameDetector detectorFor(AppServer appServer) {
return new AppServerServiceNameDetector(appServer);
}

private static class ResourceLocatorImpl implements ResourceLocator {

@Override
public Class<?> findClass(String className) {
try {
return Class.forName(className, false, ClassLoader.getSystemClassLoader());
} catch (ClassNotFoundException | LinkageError exception) {
return null;
}
}

@Override
public URL getClassLocation(Class<?> clazz) {
return clazz.getProtectionDomain().getCodeSource().getLocation();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.resourceproviders;

import java.nio.file.Path;
import java.nio.file.Paths;

class GlassfishAppServer implements AppServer {

private final String SERVICE_CLASS_NAME = "com.sun.enterprise.glassfish.bootstrap.ASMain";
private final ResourceLocator locator;

GlassfishAppServer(ResourceLocator locator) {
this.locator = locator;
}

@Override
public Path getDeploymentDir() {
String instanceRoot = System.getProperty("com.sun.aas.instanceRoot");
if (instanceRoot == null) {
return null;
}

// besides autodeploy directory it is possible to deploy applications through admin console and
// asadmin script, to detect those we would need to parse config/domain.xml
return Paths.get(instanceRoot, "autodeploy");
}

@Override
public Class<?> getServerClass() {
return locator.findClass(SERVICE_CLASS_NAME);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.resourceproviders;

import static java.util.logging.Level.FINE;
import static java.util.logging.Level.WARNING;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/** Helper class for parsing webserver xml files from various locations. */
class ParseBuddy {

private static final Logger logger = Logger.getLogger(ParseBuddy.class.getName());

private final AppServer appServer;
private final Filesystem filesystem;

ParseBuddy(AppServer appServer) {
this(appServer, new Filesystem());
}

// Exists for testing
ParseBuddy(AppServer appServer, Filesystem filesystem) {
this.appServer = appServer;
this.filesystem = filesystem;
}

String handleExplodedApp(Path path) {
String warResult = handleExplodedWar(path);
if (warResult != null) {
return warResult;
}
if (appServer.supportsEar()) {
return handleExplodedEar(path);
}
return null;
}

String handlePackagedWar(Path path) {
return handlePackaged(path, "WEB-INF/web.xml", newWebXmlHandler());
}

String handlePackagedEar(Path path) {
return handlePackaged(path, "META-INF/application.xml", newAppXmlHandler());
}

private String handlePackaged(Path path, String descriptorPath, DescriptorHandler handler) {
try (ZipFile zip = filesystem.openZipFile(path)) {
ZipEntry zipEntry = zip.getEntry(descriptorPath);
if (zipEntry != null) {
return handle(() -> zip.getInputStream(zipEntry), path, handler);
}
} catch (IOException exception) {
if (logger.isLoggable(WARNING)) {
logger.log(
WARNING, "Failed to read '" + descriptorPath + "' from zip '" + path + "'.", exception);
}
}

return null;
}

String handleExplodedWar(Path path) {
return handleExploded(path, path.resolve("WEB-INF/web.xml"), newWebXmlHandler());
}

String handleExplodedEar(Path path) {
return handleExploded(path, path.resolve("META-INF/application.xml"), newAppXmlHandler());
}

private String handleExploded(Path path, Path descriptor, DescriptorHandler handler) {
if (filesystem.isRegularFile(descriptor)) {
return handle(() -> filesystem.newInputStream(descriptor), path, handler);
}

return null;
}

private String handle(InputStreamSupplier supplier, Path path, DescriptorHandler handler) {
try {
try (InputStream inputStream = supplier.supply()) {
String candidate = parseDescriptor(inputStream, handler);
if (appServer.isValidResult(path, candidate)) {
return candidate;
}
}
} catch (Exception exception) {
logger.log(WARNING, "Failed to parse descriptor", exception);
}

return null;
}

private static String parseDescriptor(InputStream inputStream, DescriptorHandler handler)
throws ParserConfigurationException, SAXException, IOException {
if (SaxParserFactoryHolder.saxParserFactory == null) {
return null;
}
SAXParser saxParser = SaxParserFactoryHolder.saxParserFactory.newSAXParser();
saxParser.parse(inputStream, handler);
return handler.displayName;
}

private interface InputStreamSupplier {
InputStream supply() throws IOException;
}

private static DescriptorHandler newWebXmlHandler() {
return new DescriptorHandler("web-app");
}

private static DescriptorHandler newAppXmlHandler() {
return new DescriptorHandler("application");
}

private static final class DescriptorHandler extends DefaultHandler {
private final String rootElementName;
private final Deque<String> currentElement = new ArrayDeque<>();
private boolean setDisplayName;
String displayName;

DescriptorHandler(String rootElementName) {
this.rootElementName = rootElementName;
}

@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) {
if (displayName == null
&& rootElementName.equals(currentElement.peek())
&& "display-name".equals(qName)) {
String lang = attributes.getValue("xml:lang");
if (lang == null || "".equals(lang)) {
lang = "en"; // en is the default language
}
if ("en".equals(lang)) {
setDisplayName = true;
}
}
currentElement.push(qName);
}

@Override
public void endElement(String uri, String localName, String qName) {
currentElement.pop();
setDisplayName = false;
}

@Override
public void characters(char[] ch, int start, int length) {
if (setDisplayName) {
displayName = new String(ch, start, length);
}
}
}

private static class SaxParserFactoryHolder {
private static final SAXParserFactory saxParserFactory = getSaxParserFactory();

private static SAXParserFactory getSaxParserFactory() {
try {
return SAXParserFactory.newInstance();
} catch (Throwable throwable) {
logger.log(FINE, "XML parser not available.", throwable);
}
return null;
}
}

// Exists for testing
static class Filesystem {
boolean isRegularFile(Path path) {
return Files.isRegularFile(path);
}

InputStream newInputStream(Path path) throws IOException {
return Files.newInputStream(path);
}

ZipFile openZipFile(Path path) throws IOException {
return new ZipFile(path.toFile());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.resourceproviders;

import java.net.URL;

interface ResourceLocator {

Class<?> findClass(String className);

URL getClassLocation(Class<?> clazz);
}
Loading