/*
* Copyright 2016 ThoughtWorks, Inc.
*
* 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 com.thoughtworks.go.domain.materials.tfs;
import com.thoughtworks.go.util.NestedJarClassLoader;
import com.thoughtworks.go.util.SystemEnvironment;
import com.thoughtworks.go.util.command.CommandArgument;
import com.thoughtworks.go.util.command.UrlArgument;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Paths;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
/**
* Builds the TFSSDK Commmand
*/
class TfsSDKCommandBuilder {
private static final Logger LOGGER = LoggerFactory.getLogger(TfsSDKCommandBuilder.class);
private final File tempFolder = new File("data/tfs-sdk");
private final ClassLoader sdkLoader;
private static TfsSDKCommandBuilder ME;
private TfsSDKCommandBuilder() throws IOException, URISyntaxException {
this.sdkLoader = initSdkLoader();
}
/*
* Used in tests
*/
@Deprecated
TfsSDKCommandBuilder(ClassLoader sdkLoader) {
this.sdkLoader = sdkLoader;
}
TfsCommand buildTFSSDKCommand(String materialFingerPrint, UrlArgument url, String domain, String userName,
String password, final String computedWorkspaceName, String projectPath) {
try {
return instantitateAdapter(materialFingerPrint, url, domain, userName, password, computedWorkspaceName, projectPath);
} catch (Exception e) {
String message = "[TFS SDK] Could not create TFS SDK Command ";
LOGGER.error(message, e);
throw new RuntimeException(message, e);
}
}
private TfsCommand instantitateAdapter(String materialFingerPrint, UrlArgument url, String domain, String userName, String password, String computedWorkspaceName, String projectPath)
throws NoSuchMethodException, ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, IOException {
ClassLoader tcl = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(sdkLoader);
Class<?> adapterClass = Class.forName(tfsSdkCommandTCLAdapterClassName(), true, sdkLoader);
Constructor<?> constructor = adapterClass.getConstructor(String.class, CommandArgument.class, String.class, String.class, String.class, String.class, String.class);
return (TfsCommand) constructor.newInstance(materialFingerPrint, url, domain, userName, password, computedWorkspaceName, projectPath);
} finally {
Thread.currentThread().setContextClassLoader(tcl);
}
}
private String tfsSdkCommandTCLAdapterClassName() {
return "com.thoughtworks.go.tfssdk.TfsSDKCommandTCLAdapter";
}
static TfsSDKCommandBuilder getBuilder() throws IOException, URISyntaxException {
if (ME == null) {
synchronized (TfsSDKCommandBuilder.class) {
if (ME == null) {
ME = new TfsSDKCommandBuilder();
}
}
}
return ME;
}
private ClassLoader initSdkLoader() throws URISyntaxException, IOException {
FileUtils.deleteQuietly(tempFolder);
tempFolder.mkdirs();
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
FileUtils.deleteQuietly(tempFolder);
}
});
explodeNatives();
setNativePath(tempFolder);
String useTheParentLog4jConfiguration = "log4j";
return new NestedJarClassLoader(getJarURL(), useTheParentLog4jConfiguration);
}
private void setNativePath(File tempFolder) {
String sdkNativePath = Paths.get(tempFolder.getAbsolutePath(), "tfssdk", "native").toString();
LOGGER.info("[TFS SDK] Setting native lib path, com.microsoft.tfs.jni.native.base-directory={}", sdkNativePath);
System.setProperty("com.microsoft.tfs.jni.native.base-directory", sdkNativePath);
}
private void explodeNatives() throws IOException {
URL urlOfJar = getJarURL();
LOGGER.info("[TFS SDK] Exploding natives from {} to folder {}", urlOfJar.toString(), tempFolder.getAbsolutePath());
JarInputStream jarStream = new JarInputStream(urlOfJar.openStream());
JarEntry entry;
while ((entry = jarStream.getNextJarEntry()) != null) {
if (!entry.isDirectory() && entry.getName().startsWith("tfssdk/native/")) {
File newFile = new File(tempFolder, entry.getName());
newFile.getParentFile().mkdirs();
LOGGER.info("[TFS SDK] Extract {} -> {}", entry.getName(), newFile);
try (OutputStream fos = new FileOutputStream(newFile)) {
IOUtils.copy(jarStream, fos);
}
}
}
}
private URL getJarURL() throws IOException {
return TFSJarDetector.create(new SystemEnvironment()).getJarURL();
}
}