AbstractFreemarkerBuilder.java

  1. package fr.sii.ogham.template.freemarker.builder;

  2. import static freemarker.template.Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS;

  3. import java.util.ArrayList;
  4. import java.util.List;

  5. import org.slf4j.Logger;
  6. import org.slf4j.LoggerFactory;

  7. import fr.sii.ogham.core.builder.Builder;
  8. import fr.sii.ogham.core.builder.context.BuildContext;
  9. import fr.sii.ogham.core.builder.context.DefaultBuildContext;
  10. import fr.sii.ogham.core.builder.resolution.ClassPathResolutionBuilder;
  11. import fr.sii.ogham.core.builder.resolution.FileResolutionBuilder;
  12. import fr.sii.ogham.core.builder.resolution.ResourceResolutionBuilder;
  13. import fr.sii.ogham.core.builder.resolution.ResourceResolutionBuilderHelper;
  14. import fr.sii.ogham.core.builder.resolution.StringResolutionBuilder;
  15. import fr.sii.ogham.core.builder.template.DetectorBuilder;
  16. import fr.sii.ogham.core.fluent.AbstractParent;
  17. import fr.sii.ogham.core.resource.resolver.FirstSupportingResourceResolver;
  18. import fr.sii.ogham.core.resource.resolver.ResourceResolver;
  19. import fr.sii.ogham.core.template.detector.FixedEngineDetector;
  20. import fr.sii.ogham.core.template.detector.OrTemplateDetector;
  21. import fr.sii.ogham.core.template.detector.SimpleResourceEngineDetector;
  22. import fr.sii.ogham.core.template.detector.TemplateEngineDetector;
  23. import fr.sii.ogham.core.template.parser.TemplateParser;
  24. import fr.sii.ogham.template.freemarker.FreeMarkerFirstSupportingTemplateLoader;
  25. import fr.sii.ogham.template.freemarker.FreeMarkerParser;
  26. import fr.sii.ogham.template.freemarker.FreeMarkerTemplateDetector;
  27. import fr.sii.ogham.template.freemarker.SkipLocaleForStringContentTemplateLookupStrategy;
  28. import fr.sii.ogham.template.freemarker.TemplateLoaderOptions;
  29. import fr.sii.ogham.template.freemarker.adapter.ClassPathResolverAdapter;
  30. import fr.sii.ogham.template.freemarker.adapter.FileResolverAdapter;
  31. import fr.sii.ogham.template.freemarker.adapter.FirstSupportingResolverAdapter;
  32. import fr.sii.ogham.template.freemarker.adapter.StringResolverAdapter;
  33. import fr.sii.ogham.template.freemarker.adapter.TemplateLoaderAdapter;
  34. import freemarker.cache.TemplateLoader;
  35. import freemarker.template.Configuration;
  36. import freemarker.template.TemplateExceptionHandler;

  37. @SuppressWarnings("squid:S00119")
  38. public abstract class AbstractFreemarkerBuilder<MYSELF extends AbstractFreemarkerBuilder<MYSELF, P>, P> extends AbstractParent<P>
  39.         implements DetectorBuilder<MYSELF>, ResourceResolutionBuilder<MYSELF>, Builder<TemplateParser> {
  40.     private static final Logger LOG = LoggerFactory.getLogger(AbstractFreemarkerBuilder.class);

  41.     protected final MYSELF myself;
  42.     protected final BuildContext buildContext;
  43.     private TemplateEngineDetector detector;
  44.     private ResourceResolutionBuilderHelper<MYSELF> resourceResolutionBuilderHelper;
  45.     private Configuration configuration;
  46.     private List<TemplateLoaderAdapter> customAdapters;
  47.     private FreemarkerConfigurationBuilder<MYSELF> configurationBuilder;
  48.     private ClassLoader classLoader;

  49.     protected AbstractFreemarkerBuilder(Class<?> selfType) {
  50.         this(selfType, null, new DefaultBuildContext());
  51.     }

  52.     @SuppressWarnings("unchecked")
  53.     protected AbstractFreemarkerBuilder(Class<?> selfType, P parent, BuildContext buildContext) {
  54.         super(parent);
  55.         myself = (MYSELF) selfType.cast(this);
  56.         this.buildContext = buildContext;
  57.         customAdapters = new ArrayList<>();
  58.     }

  59.     @Override
  60.     public MYSELF detector(TemplateEngineDetector detector) {
  61.         this.detector = detector;
  62.         return myself;
  63.     }

  64.     @Override
  65.     public ClassPathResolutionBuilder<MYSELF> classpath() {
  66.         initResolutionBuilder();
  67.         return resourceResolutionBuilderHelper.classpath();
  68.     }

  69.     @Override
  70.     public FileResolutionBuilder<MYSELF> file() {
  71.         initResolutionBuilder();
  72.         return resourceResolutionBuilderHelper.file();
  73.     }

  74.     @Override
  75.     public StringResolutionBuilder<MYSELF> string() {
  76.         initResolutionBuilder();
  77.         return resourceResolutionBuilderHelper.string();
  78.     }

  79.     @Override
  80.     public MYSELF resolver(ResourceResolver resolver) {
  81.         initResolutionBuilder();
  82.         return resourceResolutionBuilderHelper.resolver(resolver);
  83.     }

  84.     /**
  85.      * Ogham provides a generic resource resolution mechanism
  86.      * ({@link ResourceResolver}). Freemarker uses its own template resolution
  87.      * mechanism ({@link TemplateLoader}). A resolver adapter
  88.      * ({@link TemplateLoaderAdapter}) is the way to transform a
  89.      * {@link ResourceResolver} into a {@link TemplateLoader}.
  90.      *
  91.      * <p>
  92.      * Ogham provides and registers default resolver adapters but you may need
  93.      * to use a custom {@link ResourceResolver}. So you also need to provide the
  94.      * corresponding {@link TemplateLoaderAdapter}.
  95.      *
  96.      * @param adapter
  97.      *            the resolver adapter
  98.      * @return this instance for fluent chaining
  99.      */
  100.     public MYSELF resolverAdapter(TemplateLoaderAdapter adapter) {
  101.         customAdapters.add(adapter);
  102.         return myself;
  103.     }

  104.     /**
  105.      * Fluent configurer for Freemarker configuration.
  106.      *
  107.      * @return the fluent builder for Freemarker configuration object
  108.      */
  109.     public FreemarkerConfigurationBuilder<MYSELF> configuration() {
  110.         if (configurationBuilder == null) {
  111.             configurationBuilder = new FreemarkerConfigurationBuilder<>(myself, buildContext);
  112.         }
  113.         return configurationBuilder;
  114.     }

  115.     /**
  116.      * Sets a Freemarker configuration.
  117.      *
  118.      * This value preempts any other value defined by calling
  119.      * {@link #configuration()} method. It means that the provided configuration
  120.      * is used as-is and any call to {@link #configuration()} builder methods
  121.      * has no effect on the provided configuration. You have to manually
  122.      * configure it.
  123.      *
  124.      * If this method is called several times, only the last provided
  125.      * configuration is used.
  126.      *
  127.      * @param configuration
  128.      *            the Freemarker configuration
  129.      * @return this instance for fluent chaining
  130.      */
  131.     public MYSELF configuration(Configuration configuration) {
  132.         this.configuration = configuration;
  133.         return myself;
  134.     }

  135.     /**
  136.      * Merge an existing Freemarker configuration with previously provided
  137.      * configuration.
  138.      *
  139.      * <p>
  140.      * The provided configuration is used and any call to
  141.      * {@link #configuration()} builder methods are applied to the provided
  142.      * configuration.
  143.      *
  144.      *
  145.      * @param configuration
  146.      *            The Freemarker configuration to apply
  147.      * @return this instance for fluent chaining
  148.      */
  149.     public MYSELF mergeConfiguration(Configuration configuration) {
  150.         configuration().base(configuration);
  151.         return myself;
  152.     }

  153.     /**
  154.      * Set the {@link ClassLoader} to use for loading classpath resources.
  155.      *
  156.      * <p>
  157.      * Loading resources from classpath requires a {@link ClassLoader}. Several
  158.      * class loaders may be defined in an application to isolate parts of the
  159.      * application. FreeMarker requires you to provide a {@link ClassLoader} for
  160.      * finding resources in the classpath. This is done for security reasons.
  161.      *
  162.      * <p>
  163.      * By default, Ogham uses the current thread class loader.
  164.      *
  165.      * @param classLoader
  166.      *            the class loader to use
  167.      * @return this instance for fluent chaining
  168.      */
  169.     public MYSELF classLoader(ClassLoader classLoader) {
  170.         this.classLoader = classLoader;
  171.         return myself;
  172.     }

  173.     @Override
  174.     public TemplateParser build() {
  175.         LOG.info("Freemarker parser is registered");
  176.         return buildContext.register(new FreeMarkerParser(buildConfiguration(), buildResolver()));
  177.     }

  178.     @Override
  179.     public TemplateEngineDetector buildDetector() {
  180.         return detector == null ? buildDefaultDetector() : detector;
  181.     }

  182.     private TemplateEngineDetector buildDefaultDetector() {
  183.         FirstSupportingResourceResolver resolver = buildResolver();
  184.         OrTemplateDetector or = buildContext.register(new OrTemplateDetector());
  185.         or.addDetector(buildContext.register(new FreeMarkerTemplateDetector(resolver, ".ftl", ".ftlh")));
  186.         or.addDetector(buildContext.register(new SimpleResourceEngineDetector(resolver, buildContext.register(new FixedEngineDetector(true)))));
  187.         return or;
  188.     }

  189.     /**
  190.      * Builds the resolver used by Freemarker to resolve resources
  191.      *
  192.      * @return the resource resolver
  193.      */
  194.     public FirstSupportingResourceResolver buildResolver() {
  195.         return buildContext.register(new FirstSupportingResourceResolver(buildResolvers()));
  196.     }

  197.     private Configuration buildConfiguration() {
  198.         Configuration builtConfiguration;
  199.         if (this.configuration != null) {
  200.             builtConfiguration = this.configuration;
  201.         } else if (configurationBuilder != null) {
  202.             builtConfiguration = configurationBuilder.build();
  203.         } else {
  204.             builtConfiguration = new Configuration(DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
  205.             builtConfiguration.setDefaultEncoding("UTF-8");
  206.             builtConfiguration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
  207.         }
  208.         FirstSupportingResourceResolver builtResolver = buildResolver();
  209.         FirstSupportingResolverAdapter builtAdapter = buildAdapter();
  210.         builtConfiguration.setTemplateLoader(buildContext.register(new FreeMarkerFirstSupportingTemplateLoader(builtResolver, builtAdapter)));
  211.         builtConfiguration.setTemplateLookupStrategy(buildContext.register(new SkipLocaleForStringContentTemplateLookupStrategy(builtConfiguration.getTemplateLookupStrategy(), builtResolver, builtAdapter)));
  212.         return builtConfiguration;
  213.     }

  214.     protected List<ResourceResolver> buildResolvers() {
  215.         initResolutionBuilder();
  216.         return resourceResolutionBuilderHelper.buildResolvers();
  217.     }

  218.     protected FirstSupportingResolverAdapter buildAdapter() {
  219.         FirstSupportingResolverAdapter adapter = new FirstSupportingResolverAdapter();
  220.         for (TemplateLoaderAdapter custom : customAdapters) {
  221.             adapter.addAdapter(custom);
  222.         }
  223.         adapter.addAdapter(buildContext.register(new ClassPathResolverAdapter(classLoader)));
  224.         adapter.addAdapter(buildContext.register(new FileResolverAdapter()));
  225.         adapter.addAdapter(buildContext.register(new StringResolverAdapter()));
  226.         adapter.setOptions(buildContext.register(new TemplateLoaderOptions()));
  227.         return adapter;
  228.     }

  229.     private void initResolutionBuilder() {
  230.         if (resourceResolutionBuilderHelper == null) {
  231.             resourceResolutionBuilderHelper = new ResourceResolutionBuilderHelper<>(myself, buildContext);
  232.         }
  233.     }
  234. }