/**
* Copyright 2015-2016 Red Hat, Inc, and individual contributors.
*
* 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.wildfly.swarm.datasources.runtime;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarFile;
import org.jboss.modules.DependencySpec;
import org.jboss.modules.Module;
import org.jboss.modules.ModuleIdentifier;
import org.jboss.modules.ModuleLoadException;
import org.jboss.modules.ModuleSpec;
import org.jboss.modules.ResourceLoaderSpec;
import org.jboss.modules.ResourceLoaders;
import org.wildfly.swarm.bootstrap.modules.DynamicModuleFinder;
import org.wildfly.swarm.config.datasources.DataSource;
import org.wildfly.swarm.config.datasources.DataSourceConsumer;
import org.wildfly.swarm.config.datasources.JDBCDriver;
import org.wildfly.swarm.datasources.DatasourcesFraction;
/**
* Auto-detection and default DS information for detectable JDBC drivers.
*
* <p>For each detectable JDBC driver, a subclass should be created.</p>
*
* <p>Each subclass should be marked as a {@link javax.inject.Singleton}, due
* to the fact that state is retained as to if the driver has or has not
* been detected.</p>
*
* @author Bob McWhirter
*/
public abstract class DriverInfo {
private final String name;
private final ModuleIdentifier moduleIdentifier;
private final String detectableClassName;
private final String[] optionalClassNames;
private boolean installed;
protected DriverInfo(String name,
ModuleIdentifier moduleIdentifier,
String detectableClassName,
String... optionalClassNames) {
this.name = name;
this.moduleIdentifier = moduleIdentifier;
this.detectableClassName = detectableClassName;
this.optionalClassNames = optionalClassNames;
}
private static final String FILE_PREFIX = "file:";
private static final String JAR_FILE_PREFIX = "jar:file:";
public String name() {
return this.name;
}
protected void configureDriver(JDBCDriver driver) {
// no-op, but overridable
}
protected abstract void configureDefaultDS(DataSource datasource);
public boolean detect(DatasourcesFraction fraction) {
if (fraction.subresources().jdbcDriver(this.name) != null) {
// already installed
return true;
}
DatasourcesMessages.MESSAGES.attemptToAutoDetectJdbcDriver(this.name);
File primaryJar = attemptDetection();
if (primaryJar != null) {
Set<File> optionalJars = findOptionalJars();
optionalJars.add(primaryJar);
fraction.jdbcDriver(this.name, (driver) -> {
driver.driverModuleName(this.moduleIdentifier.getName());
driver.moduleSlot(this.moduleIdentifier.getSlot());
this.configureDriver(driver);
});
DynamicModuleFinder.register(this.moduleIdentifier, (id, loader) -> {
ModuleSpec.Builder builder = ModuleSpec.build(id);
for (File eachJar : optionalJars) {
try {
JarFile jar = new JarFile(eachJar);
builder.addResourceRoot(ResourceLoaderSpec.createResourceLoaderSpec(
ResourceLoaders.createIterableJarResourceLoader(jar.getName(), jar)
));
} catch (IOException e) {
DatasourcesMessages.MESSAGES.errorLoadingAutodetectedJdbcDriver(this.name, e);
return null;
}
}
builder.addDependency(DependencySpec.createModuleDependencySpec(ModuleIdentifier.create("javax.api")));
builder.addDependency(DependencySpec.createModuleDependencySpec(ModuleIdentifier.create("javax.transactions.api"), false, true));
builder.addDependency(DependencySpec.createLocalDependencySpec());
return builder.create();
});
this.installed = true;
}
return this.installed;
}
private File attemptDetection() {
return findLocationOfClass(this.detectableClassName);
}
private Set<File> findOptionalJars() {
Set<File> optionalJars = new HashSet<>();
if (this.optionalClassNames != null) {
for (String each : this.optionalClassNames) {
File file = findLocationOfClass(each);
if (file != null) {
optionalJars.add(file);
}
}
}
return optionalJars;
}
private File findLocationOfClass(String className) {
try {
ClassLoader cl = Module.getBootModuleLoader().loadModule(ModuleIdentifier.create("swarm.application")).getClassLoader();
File candidate = findLocationOfClass(cl, className);
if (candidate == null) {
candidate = findLocationOfClass(ClassLoader.getSystemClassLoader(), className);
}
return candidate;
} catch (ModuleLoadException e) {
// ignore
} catch (IOException e) {
DatasourcesMessages.MESSAGES.errorLoadingAutodetectedJdbcDriver(this.name, e);
}
return null;
}
private File findLocationOfClass(ClassLoader classLoader, String className) throws IOException {
try {
Class<?> driverClass = classLoader.loadClass(className);
URL location = driverClass.getProtectionDomain().getCodeSource().getLocation();
String locationStr = location.toExternalForm();
if (locationStr.startsWith(JAR_FILE_PREFIX)) {
locationStr = locationStr.substring(JAR_FILE_PREFIX.length());
} else if (locationStr.startsWith(FILE_PREFIX)) {
locationStr = locationStr.substring(FILE_PREFIX.length());
}
int bangLoc = locationStr.indexOf('!');
if (bangLoc >= 0) {
locationStr = locationStr.substring(0, bangLoc);
}
locationStr = getPlatformPath(locationStr);
File locationFile = Paths.get(locationStr).toFile();
return locationFile;
} catch (ClassNotFoundException e) {
// ignore;
}
return null;
}
protected String getPlatformPath(String path) {
if (!isWindows()) {
return path;
}
URI uri = URI.create("file://" + path);
return Paths.get(uri).toString();
}
protected boolean isWindows() {
return System.getProperty("os.name").toLowerCase().contains("win");
}
public boolean isInstalled() {
return this.installed;
}
public String toString() {
return "[DriverInfo: detectable=" + this.detectableClassName + "]";
}
@SuppressWarnings("unchecked")
public void installDatasource(DatasourcesFraction fraction, String dsName, DataSourceConsumer config) {
fraction.dataSource(dsName, (ds) -> {
ds.driverName(this.name);
this.configureDefaultDS(ds);
config.accept(ds);
});
}
}