1

I have a service that gets an URL from the user input, extracts the body content of this URL, apply CSS, and finally returns the result as a stream.

The tricky part is that I have different implementations depending on the URL, if the URL is not recognized, then a "default" implementation is used. To do so, I used a BeanFactory to choose the correct implementation on Runtime.

@Service
@Qualifier("defaultHtmlToHtmlService")
public class DefaultHtmlToHtmlImpl implements HtmlToHtmlService {

    @Override
    public InputStream htmlToHtml(String url) throws IOException {
        Document document = Jsoup.connect(url).get();
        Element content = document.body();

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        outputStream.write(content.outerHtml().getBytes());
        outputStream.close();

        return new ByteArrayInputStream(outputStream.toByteArray());
    }
}

@Component
public class BeanFactory {


private final HtmlToHtmlService defaultHtmlToHtmlService;

private final HtmlToHtmlService impl1;

private final HtmlToHtmlService impl2;

@Autowired
public BeanFactory(@Qualifier("defaultHtmlToHtmlImpl") HtmlToHtmlService defaultHtmlToHtmlService,
                   @Qualifier("impl1") HtmlToHtmlService impl1,
                   @Qualifier("impl2") HtmlToHtmlService impl2) {
    this.defaultHtmlToHtmlService = defaultHtmlToHtmlService;
    this.impl1 = impl1;
    this.impl2 = impl2;
}

public HtmlToHtmlService getHtmlToHtmlImpl(String url) {
    if (url.contains("some pattern")) {
        return impl1;
    } else if (url.contains("some other pattern")) {
        return impl2;
    } else {
        return defaultHtmlToHtmlService;
    }
}

This is working just fine. However, my issue is that I don't want my BeanFactory to share any information with the rest, but I do not know how to decouple it. Basically, I want to remove the @Qualifier annotation, and not to have to manually enter the new URL patterns in the Bean Factory in the future when I will get more implementations.

I saw that maybe @PostConstruct annotation or using static blocks could be the solution, but I am really not sure how to apply it in this case.

Zaid Aly
  • 163
  • 1
  • 17
LaChope
  • 183
  • 1
  • 2
  • 14

1 Answers1

2

Something like a strategy pattern can help in this case. If you define a List<HtmlToHtmlService> in the constructor of the component in question, Spring can populate it with all implementations available in the context (you may want to rename the class BeanFactory to avoid ambiguity with Spring's own BeanFactory):

@Component
public class HtmlToHtmlServiceFactory {

    private final List<HtmlToHtmlService> services;

    @Autowired
    public HtmlToHtmlServiceFactory(List<HtmlToHtmlService> services) {
        this.services = services
    }

    public HtmlToHtmlService getHtmlToHtmlImpl(String url) {
        return services.stream()
            .filter(service -> service.supportsUrl(url))
            .findFirst()
            .orElseThrow(() -> new IllegalArgumentException(String.format("No service found that supports [%s]", url)));
    }

}
M A
  • 71,713
  • 13
  • 134
  • 174
  • Thanks for your answer! However, I have to surround the lambda expression with try and catch, and therefore return a boolean... Any idea how to avoid that? – LaChope Feb 02 '21 at 07:37
  • Does this help: https://stackoverflow.com/questions/18198176/java-8-lambda-function-that-throws-exception ? – M A Feb 02 '21 at 08:18
  • The exception is not the issue, but the return type. I get 'Bad return type in lambda expression: InputStream cannot be converted to boolean' – LaChope Feb 02 '21 at 09:23
  • 1
    @LaChope The lambda that is specified in the `filter` method is supposed to return a boolean because the filter will test a condition. It should not return an InputStream. In the code i've shown, once the method `getHtmlToHtmlImpl` returns a `HtmlToHtmlService`, you can call on it the method that processes the url and returns InputStream. If this doesn't help, you may want to edit the question. – M A Feb 02 '21 at 09:28
  • 1
    Thank you very much for your answer. Indeed, I did not see it like this. Sorry about that, I have very little knowledge about lambdas... Your answer solved my issue, thank you. – LaChope Feb 02 '21 at 14:22