Загрузить шаблоны FreeMarker из базы данных

Я хотел бы хранить свои шаблоны FreeMarker в таблице базы данных, которая выглядит примерно так:

template_name | template_content
---------------------------------
hello         |Hello ${user}
goodbye       |So long ${user}

Когда получен запрос на шаблон с конкретным именем, это должно привести к выполнению запроса, который загружает соответствующий контент шаблона. Содержимое этого шаблона вместе с моделью данных (значение переменной 'user' в приведенных выше примерах) затем следует передать FreeMarker.

Однако API FreeMarker, похоже, предполагает, что каждое имя шаблона соответствует файлу с тем же именем в определенном каталоге файловой системы. Можно ли как-то легко загрузить свои шаблоны из БД вместо файловой системы?

РЕДАКТИРОВАТЬ: я должен был упомянуть, что я хотел бы иметь возможность добавлять шаблоны в базу данных во время работы приложения, поэтому я не могу просто загрузить все шаблоны при запуске в новый StringTemplateLoader (как предложено ниже).

10.12.2008 19:54:19
6 ОТВЕТОВ

Пара способов:

  • Создайте новую реализацию TemplateLoader для загрузки шаблонов непосредственно из базы данных и передайте ее в свой экземпляр конфигурации, используя setTemplateLoader()перед загрузкой любых шаблонов.

  • Используйте StringTemplateLoader, который вы настраиваете из своей базы данных при запуске приложения. Добавьте его в конфигурацию, как указано выше.

Редактируйте в свете редактирования спрашивающего, ваша собственная реализация TemplateLoader выглядит как путь. Проверьте Javadoc здесь , это простой маленький интерфейс только с четырьмя методами, и его поведение хорошо документировано.

18
11.12.2008 09:32:24

Мы используем StringTemplateLoader для загрузки наших шаблонов, которые мы получили из базы данных (как предложил Дэн Винтон)

Вот пример:

StringTemplateLoader stringLoader = new StringTemplateLoader();
String firstTemplate = "firstTemplate";
stringLoader.putTemplate(firstTemplate, freemarkerTemplate);
// It's possible to add more than one template (they might include each other)
// String secondTemplate = "<#include \"greetTemplate\"><@greet/> World!";
// stringLoader.putTemplate("greetTemplate", secondTemplate);
Configuration cfg = new Configuration();
cfg.setTemplateLoader(stringLoader);
Template template = cfg.getTemplate(firstTemplate);

Редактировать Вам не нужно загружать все шаблоны при запуске. Всякий раз, когда мы обращаемся к шаблону, мы извлекаем его из БД и загружаем его через StringLoader и, вызывая template.process (), генерируем (в нашем случае) вывод XML.

28
11.12.2008 09:56:15

Начиная с 2.3.20 вы можете просто создать Templateстроку :

public Template(String name,
                String sourceCode,
                Configuration cfg)
         throws IOException

это удобный конструктор для Template(name, new StringReader(sourceCode), cfg).

3
7.04.2016 08:11:15

Для тех, кто ищет какой-то код, вот он. Посмотрите на комментарии в коде для лучшего понимания.

DBTemplate:

@Entity
public class DBTemplate implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    private long templateId;

    private String content; // Here's where the we store the template

    private LocalDateTime modifiedOn;

}

Реализация TemplateLoader (EMF является экземпляром EntityManagerFactory):

public class TemplateLoaderImpl implements TemplateLoader {

    public TemplateLoaderImpl() { }

    /**
     * Retrieves the associated template for a given id.
     *
     * When Freemarker calls this function it appends a locale
     * trying to find a specific version of a file. For example,
     * if we need to retrieve the layout with id = 1, then freemarker
     * will first try to load layoutId = 1_en_US, followed by 1_en and
     * finally layoutId = 1.
     * That's the reason why we have to catch NumberFormatException
     * even if it is comes from a numeric field in the database.
     *
     * @param layoutId
     * @return a template instance or null if not found.
     * @throws IOException if a severe error happens, like not being
     * able to access the database.
     */
    @Override
    public Object findTemplateSource(String templateId) throws IOException {

        EntityManager em = null;

        try {
            long id = Long.parseLong(templateId);
            em = EMF.getInstance().getEntityManager();
            DBTemplateService service = new DBTemplateService(em);
            Optional<DBTemplate> result = service.find(id);
            if (result.isPresent()) {
                return result.get();
            } else {
                return null;
            }
        } catch (NumberFormatException e) {
            return null;
        } catch (Exception e) {
            throw new IOException(e);
        } finally {
            if (em != null && em.isOpen()) {
                em.close();
            }
        }
    }


    /**
     * Returns the last modification date of a given template.
     * If the item does not exist any more in the database, this
     * method will return Long's MAX_VALUE to avoid freemarker's
     * from recompiling the one in its cache.
     *
     * @param templateSource
     * @return
     */
    @Override
    public long getLastModified(Object templateSource) {
        EntityManager em = null;
        try {
            em = EMF.getInstance().getEntityManager();
            DBTemplateService service = new DBTemplateService(em);
            // Optimize to only retrieve the date
            Optional<DBTemplate> result = service.find(((DBTemplate) templateSource).getTemplateId());
            if (result.isPresent()) {
                return result.get().getModifiedOn().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
            } else {
                return Long.MAX_VALUE;
            }
        } finally {
            if (em != null && em.isOpen()) {
                em.close();
            }
        }
    }

    /**
     * Returns a Reader from a template living in Freemarker's cache.
     */
    @Override
    public Reader getReader(Object templateSource, String encoding) throws IOException {
        return new StringReader(((DBTemplate) templateSource).getContent());
    }

    @Override
    public void closeTemplateSource(Object templateSource) throws IOException {
        // Nothing to do here...
    }

}

Настройте класс конфигурации:

...
TemplateLoaderImpl loader = new TemplateLoaderImpl();

templateConfig = new Configuration(Configuration.VERSION_2_3_25);
templateConfig.setTemplateLoader(loader);
...

И наконец, используйте это:

...
long someId = 3L;
Template template = templateConfig.getTemplate("" + someId);
...

Это прекрасно работает и позволяет вам использовать все функции Freemarker, такие как импорт, включение и т. Д. Посмотрите на следующие примеры:

<#import "1" as layout> <!-- Use a template id. -->
<@layout.mainLayout>
...

Или в:

<#include "3"> <!-- Use a template id. -->
...

Я использую этот загрузчик на своей собственной CMS (CinnamonFramework) и работает как шарм.

Лучший,

3
29.04.2017 21:07:30
Я думаю, что вы можете установить Configuration.setLocalizedLookup (boolean), чтобы отключить локализованный поиск, чтобы вам не приходилось перехватывать NumberFormatException.
Artur 20.11.2018 08:06:39

Реализация конфигурации.

Пример :

@Configuraton
public class FreemarkerConfig {

@Autowired
TemplateRepository tempRepo;

@Autowired
TemplateUtils tempUtils;

@Primary
@Bean   
public FreeMarkerConfigurationFactoryBean getFreeMarkerConfiguration() {
    // Create new configuration bean
    FreeMarkerConfigurationFactoryBean bean = new FreeMarkerConfigurationFactoryBean();
    // Create template loader
    StringTemplateLoader sTempLoader = new StringTemplateLoader();
    // Find all templates
    Iterable<TemplateDb> ite = tempRepo.findAll();
    ite.forEach((template) -> {
        // Put them in loader
        sTempLoader.putTemplate(template.getFilename(), template.getContent()); 
    });
    // Set loader
    bean.setPreTemplateLoaders(sTempLoader);
    return bean;
}

}

Тогда вы можете использовать это так:

@Autowired
private Configuration freemarkerConfig;

  Template template = freemarkerConfig.getTemplate(templateFilePath);
  String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, mapTemplate);
0
24.10.2018 16:21:47

Старый вопрос, но для тех, у кого возникла та же проблема, я нашел простое решение без необходимости в загрузчике пользовательских шаблонов или загрузке шаблона при запуске.

Предположим, у вас есть в вашей базе данных динамический шаблон:

база данных:

<p>Hello <b>${params.user}</b>!</p>

Вы можете просто создать файл Freemarker (ftlh), который интерпретирует полученную строку ( content) и генерирует из нее шаблон, используя interpret :

dynamic.ftlh:

<#assign inlineTemplate = content?interpret>
<@inlineTemplate />

Затем в вашем Java-коде вам нужно только получить строку из вашей базы данных (точно так же, как получить любые другие данные из базы данных) и использовать файл, который должен interpretсгенерировать шаблон:

Ява:

String content = getFromDatabase(); 
Configuration cfg = getConfiguration(); 
String filePath = "dynamic.ftlh";

Map<String, Object> params = new HashMap<String, Object>();
params.put("user", "World");

Map<String, Object> root = new HashMap<>();
root.put("content", content);   
root.put("params", params);     

Template template = cfg.getTemplate(filePath);

try (Writer out = new StringWriter()) {
    template.process(root, out);
    String result = out.toString();
    System.out.println(result);
}

(Измените методы getFromDatabase()и getConfiguration()все, что вы хотите, чтобы получить динамический контент из базы данных и получить объект конфигурации Freemarker , соответственно)

Это должно напечатать:

<p>Hello <b>World</b>!</p>

Затем вы можете изменить свой динамический контент в базе данных или создать другие, добавить новые параметры и так далее, без необходимости создавать другие файлы Freemarker (ftlh).

1
15.08.2019 13:24:19