Use Spring to inject text file directly to String
Asked Answered
M

4

22

So I have this

@Value("classpath:choice-test.html")
private Resource sampleHtml;
private String sampleHtmlData;

@Before
public void readFile() throws IOException {
    sampleHtmlData = IOUtils.toString(sampleHtml.getInputStream());
}

What I'd like to know is if it's possible to not have the readFile() method and have sampleHtmlData be injected with the contents of the file. If not I'll just have to live with this but it would be a nice shortcut.

Maggie answered 6/12, 2012 at 8:42 Comment(0)
A
42

Technically you can do this with XML and an awkward combination of factory beans and methods. But why bother when you can use Java configuration?

@Configuration
public class Spring {

    @Value("classpath:choice-test.html")
    private Resource sampleHtml;

    @Bean
    public String sampleHtmlData() {
        try(InputStream is = sampleHtml.getInputStream()) {
            return IOUtils.toString(is, StandardCharsets.UTF_8);
        }
    }
}

Notice that I also close the stream returned from sampleHtml.getInputStream() by using try-with-resources idiom. Otherwise you'll get memory leak.

Autrey answered 6/12, 2012 at 8:54 Comment(3)
I think I'll give this a try, thanks, but unfortunately I'm compiling to Java 6 so I can't use the try/with :(Maggie
Thanks. Instead of using IOUtils, I use new String(Files.readAllBytes(sampleHtml.getFile().toPath())); .Antichlor
Instead of using IOUtils I suggest using StreamUtils.copyToString(in);: This is bundled with spring so you have this method anyway and in contrast to @EvanHu 's proposal I think that this is more compatible if your ressources are not in the filesystem.Incentive
M
2

There is no built-in functionality for this to my knowledge but you can do it yourself e.g. like this:

<bean id="fileContentHolder">
  <property name="content">
    <bean class="CustomFileReader" factory-method="readContent">
      <property name="filePath" value="path/to/my_file"/>
    </bean>
   </property>
</bean>

Where readContent() returns a String which is read from the file on path/to/my_file.

Malvern answered 6/12, 2012 at 8:54 Comment(0)
L
1

If you want it reduced to one line per injection you can add annotation and conditional converter. This will also preserve ctrl-click navigation and autocomplete in IntelliJ.

@SpringBootApplication
public class DemoApplication {

    @Value("classpath:application.properties")
    @FromResource
    String someFileContents;

    @PostConstruct
    void init() {
        if (someFileContents.startsWith("classpath:"))
            throw new RuntimeException("injection failed");
    }

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    DemoApplication(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
        // doing it in constructor ensures it executes before @Value injection in application
        // if you don't inject values into application class, you can extract that to separate configuration
        environment.getConversionService().addConverter(new ConditionalGenericConverter() {
            @Override
            public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
                return targetType.hasAnnotation(FromResource.class);
            }

            @Override
            public Set<GenericConverter.ConvertiblePair> getConvertibleTypes() {
                return Set.of(new GenericConverter.ConvertiblePair(String.class, String.class));
            }

            @Override
            public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
                try (final var stream = resourceLoader.getResource(Objects.toString(source)).getInputStream()) {
                    return StreamUtils.copyToString(stream, StandardCharsets.UTF_8);
                } catch (IOException e) {
                    throw new IllegalArgumentException(e);
                }
            }
        });
    }
}

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface FromResource {
}
Latinalatinate answered 22/7, 2021 at 2:9 Comment(0)
D
0

Adopting to @Marcin Wisnicki it is possible to define an annotation say 'StringResource' with default value empty string and define utility class say 'StringResouceReader', a modified convertor such that:

  1. If StringResource is applied on a field as @StringResource @Value(classpath:path) then contents of resource on path is auto injected as string
  2. If StringResource is applied on a class as @StringResource(qualifier) and apply @Value(classpath:path) on a field and if name of the field or member starts with the qualifier then contents of resource on path is auto injected as string

The advantage of this approach is to make code more cleaner when there are multiple resources to be to be auto injected at different fields in same class. @StringResource(qualifier) can be specified on class and use @Value on as usual.

StringResource Class

import java.lang.annotation.*;


@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface StringResource {
    String value() default  "";
}

StringResourceRender

@Service
public class StringResourceReader{

    @Cacheable
    public String getString(Resource resource){
        return readAsString(resource);
    }

    private static String readAsString(Resource resource){
        try {
            return StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    private static boolean isJavaIdentifier(String name){
        if(name.isEmpty()){
            return false;
        }
        char[] chars = name.toCharArray();
        for(int i = 0;i<chars.length;i++){
            if(i==0&&!Character.isJavaIdentifierStart(chars[i])){
                return false;
            }else if(!Character.isJavaIdentifierStart(chars[i])){
                return false;
            }
        }
        return true;
    }
    public static void registerConvertor(ConfigurableEnvironment environment, ResourceLoader resourceLoader){
        environment.getConversionService().addConverter(new ConditionalGenericConverter() {
            @Override
            public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
                if(targetType.hasAnnotation(Value.class)){
                    if(targetType.hasAnnotation(StringResource.class)){
                        return true;
                    }else{
                        Object source = targetType.getResolvableType().getSource();
                        if(source instanceof Member) {
                            Member member = (Member) source;
                            StringResource stringResource = AnnotationUtils.findAnnotation(member.getDeclaringClass(), StringResource.class);
                            if (stringResource != null) {
                                String qualifier = stringResource.value().trim();
                                if(qualifier.length()==0){
                                    throw new IllegalStateException("Annotation StringResource must specify argument qualifier when used on a class");
                                }else if(!isJavaIdentifier(qualifier)){
                                    throw new IllegalStateException("Qualifier must be java identifier");
                                }else{
                                    return member.getName().startsWith(qualifier);
                                }
                            } else {
                                return false;
                            }
                        }else {
                            return false;
                        }
                    }
                }else{
                    return false;
                }
            }

            @Override
            public Set<ConvertiblePair> getConvertibleTypes() {
                return new HashSet(Arrays.asList(new GenericConverter.ConvertiblePair(String.class, String.class)));
            }

            @Override
            public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
                return readAsString(resourceLoader.getResource(source.toString()));
            }
        });
    }
}

Then register convertor in SpringApplication constructor

@SpringBootApplication
public class SomeApplicationApplication {

    public SomeApplicationApplication(ConfigurableEnvironment environment, ResourceLoader resourceLoader){
        StringResourceReader.registerConvertor(environment, resourceLoader);
    }
    public static void main(String[] args) {
        SpringApplication.run(SomeApplicationApplication.class, args);
    }

}
Dragon answered 16/2, 2023 at 7:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.