question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

ClasspathFileSource does not work with spring boot fat jar

See original GitHub issue

The case is that I need to fire up spring boot app with some external mocks using wiremock (duh). The ClasspathFileSource finds the path in the jar but it fails to load the stub mappings.

return FluentIterable.from(toIterable(zipFile.entries())).filter(new Predicate<ZipEntry>() {
            public boolean apply(ZipEntry jarEntry) {
                return !jarEntry.isDirectory() && jarEntry.getName().startsWith(path);
            }
        }).transform(new Function<ZipEntry, TextFile>() {
            public TextFile apply(ZipEntry jarEntry) {
                return new TextFile(getUriFor(jarEntry));
            }
        }).toList();

I suspect that this line: return !jarEntry.isDirectory() && jarEntry.getName().startsWith(path); is problematic since spring boot fat JAR entries do not start with the $path they do start with BOOT-INF/classes/$path/.... - so in this case ClasspathFileSource returns empty list.

Issue Analytics

  • State:open
  • Created 6 years ago
  • Reactions:6
  • Comments:10

github_iconTop GitHub Comments

7reactions
x418commented, Mar 13, 2019

We have a similar issue wherein our wiremock mappings are packaged in a nested jar (under WEB-INF/lib) in the Spring Boot war. The following is how we solved it


import com.github.tomakehurst.wiremock.common.BinaryFile;
import com.github.tomakehurst.wiremock.common.FileSource;
import com.github.tomakehurst.wiremock.common.TextFile;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;

import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked;
import static com.google.common.collect.Lists.newArrayList;
import static java.util.Arrays.asList;

@Slf4j
class JarReadingClasspathFileSource implements FileSource {

  private final String path;
  private URI pathUri;
  private ZipFile warFile;
  private String jarFileName;
  private File rootDirectory;

  JarReadingClasspathFileSource(String path) {
    this.path = path;
    try {
      pathUri = new ClassPathResource(path).getURI();
      if (asList("jar", "war", "ear", "zip").contains(pathUri.getScheme())) {
        log.info("Path URI is : [{}]", pathUri);
        String[] pathElements = pathUri.getSchemeSpecificPart().split("!", 3);
        String warFileUri = pathElements[0];
        String warFilePath = warFileUri.replace("file:", "");
        warFile = new ZipFile(new File(warFilePath));
        jarFileName = pathElements[1].substring(1);
      } else if (pathUri.getScheme().equals("file")) {
        rootDirectory = new File(pathUri);
      } else {
        throw new RuntimeException("ClasspathFileSource can't handle paths of type " + pathUri.getScheme());
      }
    } catch (Exception e) {
      throwUnchecked(e);
    }
  }

  @Override
  public BinaryFile getBinaryFileNamed(final String name) {
    if (isFileSystem()) {
      return new BinaryFile(new File(rootDirectory, name).toURI());
    }
    return new BinaryFile(getUri(name));
  }

  private boolean isFileSystem() {
    return rootDirectory != null;
  }


  @Override
  public TextFile getTextFileNamed(String name) {
    if (isFileSystem()) {
      return new TextFile(new File(rootDirectory, name).toURI());
    }
    return new TextFile(getUri(name));
  }

  private URI getUri(final String name) {
    try {
      return new ClassPathResource(path + "/" + name).getURI();
    } catch (IOException e) {
      return throwUnchecked(e, URI.class);
    }
  }

  @Override
  public void createIfNecessary() {
  }

  @Override
  public FileSource child(String subDirectoryName) {
    return new JarReadingClasspathFileSource(path + "/" + subDirectoryName);
  }

  @Override
  public String getPath() {
    return path;
  }

  @Override
  public URI getUri() {
    return pathUri;
  }

  @Override
  public List<TextFile> listFilesRecursively() {
    if (isFileSystem()) {
      assertExistsAndIsDirectory();
      List<File> fileList = newArrayList();
      recursivelyAddFilesToList(rootDirectory, fileList);
      return toTextFileList(fileList);
    }
    List<TextFile> files = new ArrayList<>();
    ZipEntry zipEntry = warFile.getEntry(jarFileName);
    try (final InputStream zipFileStream = warFile.getInputStream(zipEntry)) {
      final ZipInputStream zipInputStream = new ZipInputStream(zipFileStream);
      ZipEntry nestedZipEntry;
      while ((nestedZipEntry = zipInputStream.getNextEntry()) != null) {
        if (!nestedZipEntry.isDirectory() && nestedZipEntry.getName().startsWith(path)) {
          files.add(new TextFile(new ClassPathResource(nestedZipEntry.getName()).getURI()));
        }
      }
    } catch (IOException e) {
      log.error("Unable to read war file", e);
    }

    return files;
  }

  @Override
  public void writeTextFile(String name, String contents) {
  }

  @Override
  public void writeBinaryFile(String name, byte[] contents) {
  }

  @Override
  public boolean exists() {
    return !isFileSystem() || rootDirectory.exists();
  }

  @Override
  public void deleteFile(String name) {
  }

  private List<TextFile> toTextFileList(List<File> fileList) {
    return fileList.stream()
        .map(input -> new TextFile(input.toURI()))
        .collect(Collectors.toList());
  }


  private void recursivelyAddFilesToList(File root, List<File> fileList) {
    File[] files = root.listFiles();
    for (File file : files) {
      if (file.isDirectory()) {
        recursivelyAddFilesToList(file, fileList);
      } else {
        fileList.add(file);
      }
    }
  }


  private void assertExistsAndIsDirectory() {
    if (rootDirectory.exists() && !rootDirectory.isDirectory()) {
      throw new RuntimeException(rootDirectory + " is not a directory");
    } else if (!rootDirectory.exists()) {
      throw new RuntimeException(rootDirectory + " does not exist");
    }
  }
}
5reactions
birnbuazncommented, Jan 22, 2019

I had a similar problem because my resource files were located within a nested jar within the Spring Boot 2 UberJar.

I wrote a simple FileSource implementation, which uses Spring to load the resources, which works perfectly, because Spring nows how to load resources from within nested jars:

package my.example;

import com.github.tomakehurst.wiremock.common.BinaryFile;
import com.github.tomakehurst.wiremock.common.FileSource;
import com.github.tomakehurst.wiremock.common.TextFile;
import org.springframework.core.io.ClassPathResource;

import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;

class SpringClasspathResourceFileSource implements FileSource {


    private final String path;

    SpringClasspathResourceFileSource(final String path) {
        this.path = path;
    }

    @Override
    public URI getUri() {
        return getURI(path);
    }

    @Override
    public FileSource child(final String subDirectoryName) {
        return new SpringClasspathResourceFileSource(appendToPath(subDirectoryName));
    }

    @Override
    public BinaryFile getBinaryFileNamed(final String name) {
        return new BinaryFile(getURI(appendToPath(name)));
    }

    @Override
    public TextFile getTextFileNamed(final String name) {
        return new TextFile(getURI(appendToPath(name)));
    }

    private URI getURI(final String name) {
        try {
            return new ClassPathResource(name).getURI();
        } catch (IOException root) {
            throw new RuntimeException(root);
        }
    }

    private String appendToPath(final String name) {
        return path + "/" + name;
    }

    @Override
    public void createIfNecessary() {
    }

    @Override
    public String getPath() {
        return path;
    }

    @Override
    public List<TextFile> listFilesRecursively() {
        return new ArrayList<>();
    }

    @Override
    public void writeTextFile(final String name, final String contents) {
    }

    @Override
    public void writeBinaryFile(final String name, final byte[] contents) {
    }

    @Override
    public boolean exists() {
        return true;
    }

    @Override
    public void deleteFile(final String name) {
    }
}

Just use this FileSource when configuring WireMock and you are good to go:

return new WireMockServer(
    options()
        .port(port)
        .fileSource(new SpringClasspathResourceFileSource("classpath/to/your/resources"))
);
Read more comments on GitHub >

github_iconTop Results From Across the Web

ClasspathFileSource does not work with spring boot fat jar #725
The ClasspathFileSource finds the path in the jar but it fails to load the stub mappings. return FluentIterable.from(toIterable(zipFile.entries() ...
Read more >
Reading file inside Spring boot fat jar - Stack Overflow
I have a file I put in the resource file, let's call it path and I was not able to read it. @madoke...
Read more >
The Executable Jar Format - Spring
Spring Boot takes a different approach and lets you actually nest jars directly. 1.1. The Executable Jar File Structure. Spring Boot Loader-compatible jar...
Read more >
Running a Spring Boot App with Maven vs an Executable Jar
In this tutorial, we'll explore the differences between starting a Spring Boot web application via the mvn spring-boot:run command and ...
Read more >
Fat jar unable to find classpath file inside /resources package
I created a fat jar using Spring boot. ... be resolved to absolute file path because it does not reside in the file...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found