Загрузка шаблонов FreeMarker из базы данных
Я хотел бы хранить свои шаблоны FreeMarker в таблице базы данных, которая выглядит примерно так:
template_name | template_content
---------------------------------
hello |Hello ${user}
goodbye |So long ${user}
При получении запроса на шаблон с конкретным именем это должно привести к выполнению запроса, который загружает соответствующий контент шаблона. Содержимое этого шаблона вместе с моделью данных (значение переменной 'user' в приведенных выше примерах) затем следует передать FreeMarker.
Однако FreeMarker API, похоже, предполагает, что каждое имя шаблона соответствует файлу с тем же именем в определенном каталоге файловой системы. Можно ли как-нибудь легко загрузить свои шаблоны из БД вместо файловой системы?
ОБНОВЛЕНИЕ: Я должен был упомянуть, что я хотел бы иметь возможность добавлять шаблоны в базу данных во время работы приложения, поэтому я не могу просто загрузить все шаблоны при запуске в новый StringTemplateLoader (как предложено ниже).
Ответы
Ответ 1
Мы используем StringTemplateLoader для загрузки наших tempates, которые мы получили из db (как предположил Дэн Винтон)
Вот пример:
StringTemplateLoader stringLoader = new StringTemplateLoader();
String firstTemplate = "firstTemplate";
stringLoader.putTemplate(firstTemplate, freemarkerTemplate);
// It 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);
Edit
Вам не нужно загружать все шаблоны при запуске. Всякий раз, когда мы будем обращаться к шаблону, мы выберем его из БД и загрузим его через StringLoader, и, вызвав template.process(), мы генерируем (в нашем случае) вывод XML.
Ответ 2
Несколько способов:
-
Создайте новую реализацию TemplateLoader, чтобы загрузить шаблоны непосредственно из базы данных и передать ее в Configuration с использованием setTemplateLoader()
перед загрузкой любых шаблонов.
-
Используйте StringTemplateLoader, который вы настраиваете из своей базы данных при запуске приложения. Добавьте его в конфигурацию, как указано выше.
Изменить в свете редактирования вопросника, ваша собственная реализация TemplateLoader выглядит как способ. Посмотрите здесь Javadoc это простой маленький интерфейс с четырьмя методами, и его поведение хорошо документировано.
Ответ 3
Начиная с версии 2.3.20 вы можете просто построить Template
с помощью строки:
public Template(String name,
String sourceCode,
Configuration cfg)
throws IOException
который является конструктором удобства для Template(name, new StringReader(sourceCode), cfg)
.
Ответ 4
Для тех, кто ищет какой-то код, вот он. Взгляните на комментарии в коде для лучшего понимания.
DBTemplate:
@Entity
public class DBTemplate implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private long templateId;
private String content; // Here 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 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 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 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) и работает как прелесть.
Бест,
Ответ 5
Старый вопрос, но для тех, у кого возникла та же проблема, я нашел простое решение, не требуя специального загрузчика шаблонов или загрузки шаблона при запуске.
Предположим, в вашей базе данных есть динамический шаблон:
База данных:
<p>Hello <b>${params.user}</b>!</p>
Вы можете просто создать файл Freemarker (ftlh), который интерпретирует полученную строку (content
) и генерирует из нее шаблон, используя интерпретировать:
dynamic.ftlh:
<#assign inlineTemplate = content?interpret>
<@inlineTemplate />
Затем в вашем Java-коде вам нужно всего лишь получить строку из вашей базы данных (точно так же, как получить любые другие данные из базы данных) и использовать файл с interpret
для генерации шаблона:
Java:
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).
Ответ 6
Реализация конфигурации.
Пример:
@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);