Search code examples
spring-bootannotations

value annotation with wildcards for file name starting with string in spring boot


I have below value in my application.properties file

input.file-path=c:\\temp\\data

In that path I have two files:

  • Students_Input_20221212.dat
  • Workers_Input_20221212.dat

is there a way using @Value annotation to get each of the files by using the start with string? Something like below:

@Value("${input.file-path}/Students_Input_*.dat")
private String inputFileStudents;

@Value("${input.file-path}/Workers_Input_*.dat")
private String inputFileWorkers;

So I have:

inputFileStudents = "c:\\temp\\data\\Students_Input_20221212.dat"
inputFileWorkers = "c:\\temp\\data\\Workers_Input_20221212.dat"

Solution

  • Finally found:

    [PathMatching]ResourcePatternResolver!

    Consider:

    • spring-boot:3 starter (web)
    • properties:
      # existing path (win):
      input.file=C:\\temp\\data
      # file filter (ant path matcher):
      input.students.filter=Students_Input_*.dat
      input.workers.filter=Workers_Input_*.dat
      

    Testing time:

    // ...
    import java.util.Arrays;
    // import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.core.io.support.ResourcePatternResolver; // !!
    // ...
    @Bean
    InitializingBean patternResolverShowCase( /* <- just a demo ... */
      /*@Autowired*/ ResourcePatternResolver resolver, /* <- autowire/@value "anywhere"(in spring beans)... */
      @Value("${input.file}") String folder, /* <- alternative for @Value: (type-safe) @ConfigurationProperties ;) */
      @Value("${input.students.filter}") String studentsFilter, /* < filter1 */
      @Value("${input.workers.filter}") String workersFilter /* < filter2 */
    ) {
      return () -> { // <- initializingBean - lambda..
        System.err.println( // <- just a demo...
          Arrays.toString( // ..do whatever you like with:
            resolver.getResources("file:" + folder + "/" + studentsFilter)
          )
        );
        System.err.println(
          Arrays.toString( // ... :
            resolver.getResources("file:" + folder + "/" + workersFilter)
          )
        );
      };
    }
    

    With SpEL

    Declaring (assigning name and implementation class, otherwise we get a "nameless default"):

    import org.springframework.core.io.support.PathMatchingResourcePatternResolver; // !
    
    // ...
    @Bean
    ResourcePatternResolver rssResolver() {
        return new PathMatchingResourcePatternResolver();
    }
    

    ..we can also just (SpEL):

    import org.springframework.core.io.Resource; // !
    // ...
    @Value("#{@rssResolver.getResources('file:'+'${input.file}'+'/'+'${input.students.filter}')}") Resource[] studentFiles;
    @Value("#{@rssResolver.getResources('file:'+'${input.file}'+'/'+'${input.workers.filter}')}") Resource[] workerFiles;
    // do whatever you like with'em (in spring) ...
    

    Explanations

    • above link...
    • properties, path separator handling and adjustment: to your needs/suits.
    • @Value("#{...}") SpEL expression (in a @Value ...).
    • @rssResolver refers to "rssResolver" bean.
    • plus:

    Original answer:

    Wildcards will be hard (from @Value), but this (wiring a directory into bean context) is easy-peasy:

    Properties:

    # existing path (win):
    myFolder=C:\\temp\\data
    # file filter (regex!):
    myFileFilter=^test.*\.txt$
    

    Test

    @Bean InitializingBean fileResourceShowCase(/* <- just a demo */
        @Value("${myFolder}") FileSystemResource rss1, /* <- org.springframework.core.io */
        @Value("#{T(java.nio.file.Path).of('${myFolder}')}") Path path, /* <- java.nio */
        @Value("#{new java.io.File('${myFolder}')}") File file, /* <- java.io */
        @Value("${myFileFilter}") String filter /* <- custom/extra */
    ) {
      return () -> { // 3 ways to access java.io.File:
        System.err.println(rss1.getFile());
        System.err.println(path.toFile());
        System.err.println(file);
        // apply the filter:
        File[] files = file.listFiles((f) -> f.getName().matches(filter));
        for (File f : files) {
          System.err.println(f);
        }
      };
    }
    

    Explanations:

    • @Value("${myFolder}") FileSystemResource rss1: access the folder as a (spring) FileSystemResource just by it's name (represented by ${myFolder} placeholder). [prefer!]
    • @Value("#{T(java.nio.file.Path).of('${myFolder}')}") Path path: SpEL expression for (static) Path.of(myFolder) (where ${myFolder} is resolved as above).
    • @Value("#{new java.io.File('${myFolder}')}") File file: SpEL expression for: new File(myFolder) ...

    Links:

    Don'ts

    Unfortunately we cannot do this with SpEL:

    @Value("""
      #{
        T(java.nio.file.Path).of('${myFolder}')
        .toFile()
        .listFiles(
          (f)->f.getName().matches('${myFileFilter}')
        )
      }
    """) File[] files; // -> EL1042E: Problem parsing right operand ;(;(
    

    see: Why doesn't this stream & lambda expression work with SpEL declaration?

    Neither this:

    @Value("${myFolder}/**") FileSystemResource[] rssArr // -> java.nio.file.InvalidPathException: Illegal char <*> at index ...
    

    ... the last (@Value("${myFolder}/**")) approach brought me to: https://www.google.com/search?q=spring+antpathmatcher+filesystem ;)