Skip to content

Commit

Permalink
Add archive builder
Browse files Browse the repository at this point in the history
  • Loading branch information
ngallagher committed Jun 24, 2018
1 parent 8c499d9 commit 114ffd5
Show file tree
Hide file tree
Showing 13 changed files with 450 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.snapscript.studio.agent.local;

import java.util.ArrayList;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

import org.snapscript.core.module.Path;
import org.snapscript.studio.agent.cli.CommandLine;
import org.snapscript.studio.agent.cli.CommandLineBuilder;

public class LocalJarProcess {

public static final String MAIN_SCRIPT = "Main-Script";

public static void main(String[] arguments) throws Exception {
CommandLineBuilder builder = LocalOption.getBuilder();
CommandLine line = builder.build(arguments);
LocalCommandLine local = new LocalCommandLine(line);
Path path = local.getScript();

if(path == null) {
String[] empty = new String[]{};
List<String> expanded = new ArrayList<String>();
Attributes.Name key = new Attributes.Name(MAIN_SCRIPT);

Manifest manifest = LocalManifestReader.readManifest();
String script = (String)manifest.getMainAttributes().get(key);

for(String argument : arguments) {
expanded.add(argument);
}
String argument = String.format("--%s=%s", LocalOption.SCRIPT.name, script);
expanded.add(argument);
arguments = expanded.toArray(empty);
}
LocalProcess.main(arguments);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.snapscript.studio.agent.local;

import java.io.InputStream;
import java.util.jar.Manifest;

public class LocalManifestReader {

public static Manifest readManifest() {
try {
InputStream resource = LocalManifestReader.class.getResourceAsStream("META-INF/MANIFEST.MF");

if(resource == null) {
resource = LocalManifestReader.class.getResourceAsStream("/META-INF/MANIFEST.MF");
}
return new Manifest(resource);
} catch(Exception e) {
throw new IllegalStateException("Could not read manifest file", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
package org.snapscript.studio.project;

import static org.springframework.core.io.support.ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.codehaus.plexus.util.StringUtils;
import org.snapscript.core.type.extend.FileExtension;
import org.snapscript.studio.agent.local.LocalJarProcess;
import org.snapscript.studio.agent.local.LocalProcess;
import org.snapscript.studio.project.generate.ClassPathConfigFile;
import org.snapscript.studio.project.generate.ClassPathFileGenerator;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

@Slf4j
public class ArchiveBuilder {

private static final ArchivePath[] RUNTIME_PACKAGES = new ArchivePath[]{
new ArchivePath("org/snapscript/studio/agent", true),
new ArchivePath("org/snapscript/compile", true),
new ArchivePath("org/snapscript/common", true),
new ArchivePath("org/snapscript/platform", true),
new ArchivePath("org/snapscript/cglib", true),
new ArchivePath("org/snapscript/asm", true),
new ArchivePath("org/snapscript/dx", true),
new ArchivePath("org/snapscript/parse", true),
new ArchivePath("org/snapscript/core", true),
new ArchivePath("org/snapscript/tree", true),
new ArchivePath("import.txt", false),
new ArchivePath("grammar.txt", false)
};

private final PathMatchingResourcePatternResolver resolver;
private final ClassPathFileGenerator generator;
private final FileExtension extension;
private final ProjectContext context;
private final Project project;

public ArchiveBuilder(Project project, ProjectContext context) {
this.resolver = new PathMatchingResourcePatternResolver();
this.generator = new ClassPathFileGenerator();
this.extension = new FileExtension();
this.project = project;
this.context = context;
}

public File exportArchive(String mainScript) {
String name = project.getProjectName();
String tempDir = System.getProperty("java.io.tmpdir");

try {
File destDir = new File(tempDir, name);
File outputFile = new File(tempDir, name + ".jar");

collectResources(destDir);
extractRuntime(destDir);
extractClassPath(destDir);
archiveDirectory(destDir, outputFile, mainScript);

return outputFile;
}catch(Exception e) {
log.info("Could not export {}", name);
throw new IllegalStateException("Could not export " + name, e);
}
}

private void archiveDirectory(File sourceDir, File outputFile, String mainScript) throws Exception {
Manifest manifest = new Manifest();
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");

if(!StringUtils.isBlank(mainScript)) {
Attributes.Name mainScriptKey = new Attributes.Name(LocalJarProcess.MAIN_SCRIPT);

manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, LocalJarProcess.class.getName());
manifest.getMainAttributes().put(mainScriptKey, mainScript);
} else {
manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, LocalProcess.class.getName());
}
OutputStream stream = new FileOutputStream(outputFile);
JarOutputStream out = new JarOutputStream(stream, manifest);
Set<String> done = new HashSet<String>();

try {
List<File> files = extension.findFiles(sourceDir, ".*");
String sourcePath = sourceDir.getCanonicalPath();
int length = sourcePath.length();

for(File entry : files) {
if(entry.isFile()) {
String entryPath = entry.getCanonicalPath();
String relativePath = entryPath.substring(length).replace(File.separatorChar, '/');

if(done.add(relativePath) && !relativePath.endsWith("MANIFEST.MF")) {
if(relativePath.startsWith("/")) {
relativePath = relativePath.substring(1); // /org/domain/Type.class -> org/domain/Type.class
}
JarEntry jarEntry = new JarEntry(relativePath);
byte[] data = FileUtils.readFileToByteArray(entry);

log.info("Compressing {}", relativePath);
out.putNextEntry(jarEntry);
out.write(data);
}
}
}
} finally {
out.close();
stream.close();
}
}

private void collectResources(File destDir) throws Exception {
Map<String, File> files = context.getFiles();
Set<Entry<String, File>> entries = files.entrySet();

for(Entry<String, File> entry : entries) {
String path = entry.getKey();
File file = entry.getValue();

if(file.isFile()) {
byte[] content = FileUtils.readFileToByteArray(file);

try {
File outputFile = new File(destDir, path);

if(!outputFile.getParentFile().exists()) {
outputFile.getParentFile().mkdirs();
}
log.info("Archiving {}", path);
FileUtils.writeByteArrayToFile(outputFile, content);
} catch(Exception e) {
log.info("Could not collect {}", path);
}
}
}
}

private void extractRuntime(File destDir) throws Exception{
for(ArchivePath path : RUNTIME_PACKAGES) {
String prefix = path.getPath();
String pattern = path.getPattern();
Resource[] resources = resolver.getResources(CLASSPATH_ALL_URL_PREFIX + pattern);

for(Resource resource : resources) {
String target = resource.getURI().toString();
int index = target.indexOf(prefix);

if(index != -1) {
String relativePath = target.substring(index);
File file = new File(destDir, relativePath);

if(!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
InputStream input = resource.getInputStream();

try {
log.info("Archiving {}", relativePath);
byte[] data = IOUtils.toByteArray(input);
FileUtils.writeByteArrayToFile(file, data);
}finally {
input.close();
}
}
}
}
}

private void extractClassPath(File destDir) throws Exception{
File file = generator.getConfigFilePath(project);
String content = FileUtils.readFileToString(file);
ClassPathConfigFile configFile = generator.parseConfig(project, content);
List<File> files = configFile.getFiles();

extractClassPath(destDir, files);
}

private void extractClassPath(final File destDir, final List<File> files) throws Exception {
int threads = Runtime.getRuntime().availableProcessors();
ExecutorService executor = Executors.newFixedThreadPool(threads);

for(File file : files) {
if(file.isFile() && file.getName().endsWith(".jar")) {
ArchiveJarExtractor extractor = new ArchiveJarExtractor(destDir, file);
executor.execute(extractor);
}
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.HOURS);
}

@AllArgsConstructor
private static class ArchiveJarExtractor implements Runnable {

private final File destDir;
private final File file;

@Override
public void run() {
try {
JarFile jarfile = new JarFile(file); // jar file path(here sqljdbc4.jar)
Enumeration<JarEntry> jarEntries = jarfile.entries();

while (jarEntries.hasMoreElements()) {
JarEntry jarEntry = jarEntries.nextElement();
String fileName = jarEntry.getName();
File outputPath = new File(destDir, fileName);

if (!outputPath.exists()) {
outputPath.getParentFile().mkdirs();
}
if (jarEntry.isDirectory()) {
continue;
}
InputStream sourceFile = jarfile.getInputStream(jarEntry);
FileOutputStream outputFile = new FileOutputStream(outputPath);

try {
log.info("Archiving {}", fileName);
IOUtils.copy(sourceFile, outputFile);
}finally {
outputFile.close();
sourceFile.close();
}
}
jarfile.close();
} catch(Exception e) {
log.info("Could not extract {}", file);
}
}
}

@AllArgsConstructor
private static class ArchivePath {

private final String prefix;
private final boolean classes;

public String getPattern(){
if(classes) {
return prefix + "/**/*.class";
}
return prefix;
}

public String getPath(){
return prefix;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
public class Project implements FileDirectory {

private final ConfigurationClassLoader classLoader;
private final ArchiveBuilder archiveBuilder;
private final FileSystem fileSystem;
private final ProjectContext context;
private final Workspace workspace;
Expand All @@ -33,13 +34,18 @@ public class Project implements FileDirectory {
public Project(ConfigurationReader reader, ConfigFileSource source, Workspace workspace, String projectDirectory, String projectName) {
this.context = new ProjectContext(reader, source, workspace, this);
this.classLoader = new ConfigurationClassLoader(this);
this.archiveBuilder = new ArchiveBuilder(this, context);
this.fileSystem = new FileSystem(this);
this.store = new ProjectStore();
this.projectDirectory = projectDirectory;
this.projectName = projectName;
this.workspace = workspace;
}

public File getExportedArchive(String mainScript) {
return archiveBuilder.exportArchive(mainScript);
}

public Workspace getWorkspace(){
return workspace;
}
Expand Down Expand Up @@ -99,7 +105,7 @@ public String getScriptPath(String resource) {
File path = getProjectPath();
return context.getLayout().getDownloadPath(path, resource);
}

public List<DependencyFile> getDependencies() {
return context.getDependencies();
}
Expand Down
Loading

0 comments on commit 114ffd5

Please sign in to comment.