99import java .io .IOException ;
1010import java .io .InputStream ;
1111import java .io .InputStreamReader ;
12+ import java .net .URI ;
13+ import java .net .URISyntaxException ;
14+ import java .net .URL ;
15+ import java .nio .file .FileSystem ;
16+ import java .nio .file .FileSystems ;
17+ import java .nio .file .Files ;
18+ import java .nio .file .Path ;
1219import java .nio .file .Paths ;
1320import java .sql .Connection ;
1421import java .sql .PreparedStatement ;
2431import java .util .regex .Matcher ;
2532import java .util .regex .Pattern ;
2633import java .util .stream .Collectors ;
34+ import java .util .stream .Stream ;
2735
2836import org .apache .commons .io .FilenameUtils ;
2937import org .slf4j .Logger ;
@@ -274,16 +282,37 @@ protected List<String> getDatabaseMigrationFilesDatedAfter(Date from) {
274282 * {@code new Date(0)} as argument instead.
275283 */
276284 protected Result <Throwable , List <String >> getDatabaseMigrationFiles () {
285+ LOGGER .trace ("Searching for migration files inside [{}]..." , this .getDatabaseMigrationsResourcePath ());
277286 List <String > filenames = new ArrayList <>();
278287
279- try (InputStream in = this .getResourceAsStream (this .getDatabaseMigrationsResourcePath ()); BufferedReader br = new BufferedReader (new InputStreamReader (in ))) {
280- String resource ;
288+ try {
289+ String resourcePath = this .getDatabaseMigrationsResourcePath ();
290+ URL resourceUrl = this .getResource (resourcePath );
291+
292+ if (resourceUrl == null ) {
293+ throw new IllegalArgumentException ("Resource not found: " + resourcePath );
294+ }
281295
282- while ((resource = br .readLine ()) != null ) {
283- filenames .add (resource );
296+ URI uri = resourceUrl .toURI ();
297+ Path myPath ;
298+
299+ if ("jar" .equals (uri .getScheme ())) {
300+ LOGGER .trace ("URI [{}] is inside a .jar file. Walking the file tree though appropriate FileSystem provider..." , uri );
301+ try (FileSystem fs = FileSystems .newFileSystem (uri , Collections .emptyMap ())) {
302+ myPath = fs .getPath (resourcePath );
303+ try (Stream <Path > walk = Files .walk (myPath , 1 )) {
304+ walk .filter (Files ::isRegularFile ).forEach (path -> filenames .add (path .getFileName ().toString ()));
305+ }
306+ }
307+ } else {
308+ LOGGER .trace ("URI [{}] is not inside a .jar file. Walking the file tree through normal means..." , uri );
309+ myPath = Paths .get (uri );
310+ try (Stream <Path > walk = Files .walk (myPath , 1 )) {
311+ walk .filter (Files ::isRegularFile ).forEach (path -> filenames .add (path .getFileName ().toString ()));
312+ }
284313 }
285- } catch (IOException e ) {
286- LOGGER .error ("There was an error while reading resource files from path [{}]" , getDatabaseMigrationsResourcePath ());
314+ } catch (IOException | URISyntaxException e ) {
315+ LOGGER .error ("There was an error while reading resource files from path [{}]" , this . getDatabaseMigrationsResourcePath ());
287316 return Result .failure (e );
288317 }
289318
@@ -306,15 +335,28 @@ private Optional<Date> getDateFromFilename(String filename) {
306335 }
307336
308337 /**
309- * Reads a specific {@code resource} from the classpath.
338+ * Returns the {@link InputStream} corresponding to the given {@code resource} from the classpath.
310339 *
311340 * @implNote It's up to this method's callers to properly close the resulting {@link InputStream}.
341+ *
342+ * @apiNote This method is <b>not</b> suitable for performing filesystem operations (like reading a file-tree) when {@code resource}
343+ * is bundled inside a {@code .jar} file. While individual files are perfectly accessible/readable, some other
344+ * operations (like listing files inside a folder) will return empty results <i>without</i> throwing any kind
345+ * of {@link Exception} whatsoever.
312346 */
313347 private InputStream getResourceAsStream (String resource ) {
314348 InputStream in = Thread .currentThread ().getContextClassLoader ().getResourceAsStream (resource );
315349 return in == null ? H2Database .class .getResourceAsStream (resource ) : in ;
316350 }
317351
352+ /**
353+ * Returns the {@link URL} corresponding to the given {@code resource} from the classpath.
354+ */
355+ private URL getResource (String resource ) {
356+ URL in = Thread .currentThread ().getContextClassLoader ().getResource (resource );
357+ return in == null ? H2Database .class .getResource (resource ) : in ;
358+ }
359+
318360 /**
319361 * Parses the provided {@code date} in {@code yyyyMMdd} format.
320362 */
0 commit comments