Commit 13ec164f by jhrabal

work in progress

parent e7571522
...@@ -82,6 +82,18 @@ ...@@ -82,6 +82,18 @@
<artifactId>spring-boot-starter-mail</artifactId> <artifactId>spring-boot-starter-mail</artifactId>
</dependency> </dependency>
<!-- handlebars -->
<dependency>
<groupId>com.github.jknack</groupId>
<artifactId>handlebars</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>com.github.jknack</groupId>
<artifactId>handlebars-helpers</artifactId>
<version>4.1.2</version>
</dependency>
<dependency> <dependency>
<groupId>commons-httpclient</groupId> <groupId>commons-httpclient</groupId>
......
...@@ -107,6 +107,7 @@ CREATE TABLE RESET_PASSWORD_TOKEN ( ...@@ -107,6 +107,7 @@ CREATE TABLE RESET_PASSWORD_TOKEN (
); );
CREATE INDEX RESET_PASSWORD_TOKEN_LOGIN_IDX ON RESET_PASSWORD_TOKEN(LOGIN); CREATE INDEX RESET_PASSWORD_TOKEN_LOGIN_IDX ON RESET_PASSWORD_TOKEN(LOGIN);
-- EMAILS
CREATE TABLE EMAIL ( CREATE TABLE EMAIL (
ID INT8 NOT NULL, ID INT8 NOT NULL,
...@@ -167,3 +168,39 @@ CREATE TABLE EMAIL_ATTACHMENT ( ...@@ -167,3 +168,39 @@ CREATE TABLE EMAIL_ATTACHMENT (
); );
CREATE INDEX EMAIL_ATTACHMENT_IDX ON EMAIL_ATTACHMENT(EMAIL_ID); CREATE INDEX EMAIL_ATTACHMENT_IDX ON EMAIL_ATTACHMENT(EMAIL_ID);
-- CUSTOMIZABLE TEMPLATES
CREATE TABLE CUSTOMIZABLE_TEMPLATE (
ID INT8 NOT NULL,
CODE VARCHAR(250),
NAME VARCHAR(100),
TEMPLATE TEXT,
CONSTRAINT PK_CUSTOMIZABLE_TEMPLATE PRIMARY KEY(ID)
);
CREATE INDEX CUSTOMIZABLE_TEMPLATE_CODE_IDX ON CUSTOMIZABLE_TEMPLATE(CODE);
CREATE TABLE CUSTOMIZABLE_TEMPLATE_LABEL (
ID INT8 NOT NULL,
TEMPLATE_ID INT8 NOT NULL,
UNIT_ID INT8,
CODE VARCHAR(1000) NOT NULL,
LANG VARCHAR(10),
LABEL VARCHAR,
POSITION NUMERIC(19,0),
CONSTRAINT PK_CUSTOMIZABLE_TEMPLATE_LABEL PRIMARY KEY(ID),
CONSTRAINT FK_CUSTOMIZABLE_TEMPLATE_LABEL_TEMPLATE FOREIGN KEY (TEMPLATE_ID) REFERENCES CUSTOMIZABLE_TEMPLATE(ID)
);
CREATE INDEX CUSTOMIZABLE_TEMPLATE_LABEL_TEMPLATE_IDX ON CUSTOMIZABLE_TEMPLATE_LABEL(TEMPLATE_ID);
CREATE INDEX CUSTOMIZABLE_TEMPLATE_LABEL_UNIT_IDX ON CUSTOMIZABLE_TEMPLATE_LABEL(UNIT_ID);
-- TODO
\ No newline at end of file
...@@ -22,10 +22,6 @@ public class Email extends AbstractIdEntity { ...@@ -22,10 +22,6 @@ public class Email extends AbstractIdEntity {
@Column(name = "LOCKED") @Column(name = "LOCKED")
private Boolean locked; private Boolean locked;
/** The unit id. */
@Column(name = "UNIT_ID")
private Long unitId;
/** The email from. */ /** The email from. */
@Column(name = "EMAIL_FROM") @Column(name = "EMAIL_FROM")
private String emailFrom; private String emailFrom;
...@@ -85,24 +81,6 @@ public class Email extends AbstractIdEntity { ...@@ -85,24 +81,6 @@ public class Email extends AbstractIdEntity {
} }
/** /**
* Gets the unit id.
*
* @return the unit id
*/
public Long getUnitId() {
return unitId;
}
/**
* Sets the unit id.
*
* @param unitId the new unit id
*/
public void setUnitId(Long unitId) {
this.unitId = unitId;
}
/**
* Gets the from. * Gets the from.
* *
* @return the from * @return the from
......
package com.jh.common.email;
import org.springframework.context.annotation.Bean;
//TODO
public class JhMailConfig {
@Bean
public EmailService emailService() {
return new LocalEmailService();
}
}
...@@ -147,7 +147,6 @@ public class LocalEmailService implements EmailService { ...@@ -147,7 +147,6 @@ public class LocalEmailService implements EmailService {
helper.setText(email.getHtml(), true); helper.setText(email.getHtml(), true);
} }
if (email.getAttachments() != null) { if (email.getAttachments() != null) {
for (EmailAttachment a : email.getAttachments()) { for (EmailAttachment a : email.getAttachments()) {
if (a.getData() != null) { if (a.getData() != null) {
...@@ -164,7 +163,6 @@ public class LocalEmailService implements EmailService { ...@@ -164,7 +163,6 @@ public class LocalEmailService implements EmailService {
mailSender.send(msg); mailSender.send(msg);
email.setStatus("SENT"); email.setStatus("SENT");
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
email.setStatus("FAILED"); email.setStatus("FAILED");
......
package com.jh.common.i18n;
/**
* The Interface I18nContext.
*/
public interface I18nContext {
/**
* Gets the date format.
*
* @return the date format
*/
public String getDateFormat();
/**
* Gets the currency format.
*
* @return the currency format
*/
public String getCurrencyFormat();
/**
* Gets the thousand delimiter.
*
* @return the thousand delimiter
*/
public String getThousandDelimiter();
/**
* Gets the decimal delimiter.
*
* @return the decimal delimiter
*/
public String getDecimalDelimiter();
/**
* Gets the currency.
*
* @return the currency
*/
public String getCurrency();
/**
* Gets the currency symbol.
*
* @return the currency symbol
*/
public String getCurrencySymbol();
}
package com.jh.common.i18n;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* TODO comment.
*
* @author jh
*/
public class I18nUtils {
/** The date formats. */
private Map<String, SimpleDateFormat> dateFormats;
/** The date formatters. */
private Map<String, DateTimeFormatter> dateFormatters;
/** The number formats. */
private Map<NumberFormatKey, NumberFormat> numberFormats;
/** The instance. */
private static I18nUtils instance = new I18nUtils();
private I18nContext defaultI18n = new I18nContext() {
@Override
public String getThousandDelimiter() {
return ",";
}
@Override
public String getDecimalDelimiter() {
return ".";
}
@Override
public String getDateFormat() {
return "MM/DD/YYYY";
}
@Override
public String getCurrencySymbol() {
return "$";
}
@Override
public String getCurrencyFormat() {
return "left";
}
@Override
public String getCurrency() {
return "USD";
}
};
/**
* Instantiates a new i 18 n utils.
*/
private I18nUtils() {
dateFormats = new HashMap<>();
numberFormats = new HashMap<>();
dateFormatters = new HashMap<>();
}
/**
* Format number.
*
* @param number the number
* @param i18n the i 18 n
* @return the string
*/
public String formatNumber(Number number, I18nContext i18n) {
return formatNumber(number, 3, i18n);
}
/**
* Format number.
*
* @param number the number
* @param fixed the fixed
* @param i18n the i 18 n
* @return the string
*/
public String formatNumber(Number number, int fixed, I18nContext i18n) {
if (number == null) {
return "";
}
if (i18n == null) {
i18n = defaultI18n;
}
NumberFormatKey nfk = new NumberFormatKey(i18n.getDecimalDelimiter(), i18n.getThousandDelimiter(), fixed);
NumberFormat nf = numberFormats.get(nfk);
if (nf == null) {
DecimalFormatSymbols otherSymbols = new DecimalFormatSymbols();
char ds = i18n.getDecimalDelimiter() == null || i18n.getDecimalDelimiter().isEmpty() ? '.' : i18n.getDecimalDelimiter().charAt(0);
char gs = i18n.getThousandDelimiter() == null || i18n.getThousandDelimiter().isEmpty() ? '.' : i18n.getThousandDelimiter().charAt(0);
otherSymbols.setDecimalSeparator(ds);
otherSymbols.setGroupingSeparator(gs);
DecimalFormat df = new DecimalFormat("#", otherSymbols);
df.setGroupingUsed(true);
df.setGroupingSize(3);
df.setMinimumIntegerDigits(1);
df.setMinimumFractionDigits(fixed);
df.setMaximumFractionDigits(fixed);
nf = df;
numberFormats.put(nfk, df);
}
return nf.format(number);
}
/**
* Format money.
*
* @param number the number
* @param currency the currency
* @param i18n the i 18 n
* @param fixed the fixed
* @return the string
*/
public String formatMoney(Number number, String currency, I18nContext i18n, int fixed) {
if (number == null) {
return "";
}
if (i18n == null) {
i18n = defaultI18n;
}
if (currency == null) {
currency = "";
}
if (!"right".equals(i18n.getCurrencyFormat())) {
StringBuilder sb = new StringBuilder(currency);
sb.append(" ").append(formatNumber(number, fixed, i18n));
return sb.toString();
}
StringBuilder sb = new StringBuilder(formatNumber(number, fixed, i18n));
sb.append(" ").append(currency);
return sb.toString();
}
/**
* Format money.
*
* @param number the number
* @param currency the currency
* @param i18n the i 18 n
* @return the string
*/
public String formatMoney(Number number, String currency, I18nContext i18n) {
return formatMoney(number, currency, i18n, 2);
}
/**
* Format percent.
*
* @param number the number
* @param i18n the i 18 n
* @return the string
*/
public String formatPercent(Number number, I18nContext i18n) {
if (number == null) {
return "";
}
StringBuilder sb = new StringBuilder(formatNumber(number, i18n));
sb.append(" %");
return sb.toString();
}
/**
* Format date.
*
* @param date the date
* @param i18n the i 18 n
* @return the string
*/
public String formatDate(Date date, I18nContext i18n) {
if (date == null) {
return "";
}
if (i18n == null) {
i18n = defaultI18n;
}
String format = Objects.toString(i18n.getDateFormat(), "MM/dd/yyyy");
SimpleDateFormat sdf = dateFormats.get(format);
if (sdf == null) {
dateFormats.put(format, sdf = new SimpleDateFormat(format));
}
return sdf.format(date);
}
/**
* Format date.
*
* @param date the date
* @param i18n the i 18 n
* @return the string
*/
public String formatDate(LocalDate date, I18nContext i18n) {
if (date == null) {
return "";
}
if (i18n == null) {
i18n = defaultI18n;
}
String format = Objects.toString(i18n.getDateFormat(), "MM/dd/yyyy");
DateTimeFormatter dtf = dateFormatters.get(format);
if (dtf == null) {
dateFormatters.put(format, dtf = DateTimeFormatter.ofPattern(convertJSFormat(format)));
}
return date.format(dtf);
}
/**
* Convert JS format.
*
* @param format the format
* @return the string
*/
private String convertJSFormat(String format) {
return format.replace("D", "d").replace("m", "M").replace("Y","y");
}
/**
* Gets the single instance of I18nUtils.
*
* @return single instance of I18nUtils
*/
public static I18nUtils getInstance() {
return instance;
}
}
package com.jh.common.template;
import org.springframework.context.annotation.Bean;
public class JhTemplateConfig {
@Bean
public TemplateEngine templateEngine() {
return null;
}
@Bean
public TemplateService templateService() {
return null;
}
}
package com.jh.common.template;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import com.jh.common.jpa.AbstractIdEntity;
@Entity
@Table(name = "CUSTOMIZABLE_TEMPLATE")
public class Template extends AbstractIdEntity {
@Column(name = "CODE")
private String code;
@Column(name = "NAME")
private String name;
@Column(name = "TEMPLATE")
private String template;
@Column(name = "TRANSLATE_NAME")
private Boolean translateName;
public Template() {
super();
}
public Template(Long templateId) {
super();
setId(templateId);
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTemplate() {
return template;
}
public void setTemplate(String template) {
this.template = template;
}
public Boolean getTranslateName() {
return translateName;
}
public void setTranslateName(Boolean translateName) {
this.translateName = translateName;
}
}
package com.jh.common.template;
import java.io.Writer;
import java.util.Locale;
import java.util.Map;
public interface TemplateEngine {
/**
* TODO.
*
* @param templateName the template name
* @param clazz the clazz
* @param data the data
* @param locale the locale
* @param writer the writer
*/
void evaluateTemplate(String templateName, Class<?> clazz, Map<String, Object> data, Locale locale, Writer writer);
/**
* TODO.
*
* @param template the template
* @param data the data
* @param locale the locale
* @param writer the writer
*/
void evaluateTemplate(String template, Map<String, Object> data, Locale locale, Writer writer);
}
\ No newline at end of file
package com.jh.common.template;
public interface TemplateLoader {
}
...@@ -6,25 +6,8 @@ import java.util.Map; ...@@ -6,25 +6,8 @@ import java.util.Map;
public interface TemplateService { public interface TemplateService {
/** void evaluate(Writer writer, String templateCode, Map<String, Object> data, Locale locale);
* TODO.
*
* @param templateName the template name
* @param clazz the clazz
* @param data the data
* @param locale the locale
* @param writer the writer
*/
void evaluateTemplate(String templateName, Class<?> clazz, Map<String, Object> data, Locale locale, Writer writer);
/** void evaluateInline(Writer writer, String template, Map<String, Object> data, Locale locale);
* TODO.
*
* @param template the template
* @param data the data
* @param locale the locale
* @param writer the writer
*/
void evaluateTemplate(String template, Map<String, Object> data, Locale locale, Writer writer);
} }
package com.jh.common.template.customizable;
import java.util.Collection;
import java.util.List;
import javax.persistence.Query;
import org.springframework.stereotype.Repository;
import com.jh.common.jpa.AbstractHibernateRepository;
import com.jh.common.template.Template;
@Repository
public class CustomizableLabelsRepository extends AbstractHibernateRepository {
@SuppressWarnings("unchecked")
public Template fetchTemplate(String code) {
Query q = entityManager.createQuery("select t from Template t where code = :code");
q.setParameter("code", code);
List<Template> list = q.getResultList();
return list.isEmpty() ? null : list.get(0);
}
@SuppressWarnings("unchecked")
public List<CustomizableTemplateLabel> findCustomizedTemplateLabels(Long templateId, Long unitId) {
Query q = entityManager.createQuery("select tl from TemplateLabel tl left join tl.template t where t.id = :templateId and tl.unitId = :unitId order by tl.position asc");
q.setParameter("template.id", templateId);
q.setParameter("unitId", unitId);
return q.getResultList();
}
@SuppressWarnings("unchecked")
public List<CustomizableTemplateLabel> findDefaultTemplateLabels(Long templateId) {
Query q = entityManager.createQuery("select tl from TemplateLabel tl left join tl.template t where t.id = :templateId and tl.unitId is null");
q.setParameter("template.id", templateId);
return q.getResultList();
}
public void saveCustomizedLabels(Collection<CustomizableTemplateLabel> labels) {
if (labels == null) {
return;
}
labels.stream().forEach(this::save);
}
}
package com.jh.common.template.customizable;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.jh.common.template.Template;
@Service
public class CustomizableLabelsService {
private CustomizableLabelsRepository repo;
private Map<Long, CustomizableTemplateLabels> templateLabelsCache = new HashMap<>();
private boolean useCache;
@Transactional
public Template getTemplateContent(String code) {
return repo.fetchTemplate(code);
}
@Transactional
public Map<String, CustomizableTemplateLabel> getCustomizationTemplateLabels(Long templateId, Long unitId, String defaultLanguage) {
TemplateLabels templateLabels = getTemplateDefaultLabels(templateId);
List<CustomizableTemplateLabel> labels = repo.findCustomizedTemplateLabels(templateId, unitId);
Map<String, CustomizableTemplateLabel> defaultMap = templateLabels.getLanguages().get(defaultLanguage);
if (defaultMap == null) {
defaultMap = templateLabels.getFallbackLabels();
}
if (defaultMap == null) {
return null;
}
Map<String, CustomizableTemplateLabel> result = new HashMap<>();
for (String code : templateLabels.getCodes()) {
CustomizableTemplateLabel label = defaultMap.get(code);
CustomizableTemplateLabel newLabel = new CustomizableTemplateLabel();
newLabel.setCode(code);
newLabel.setLabel(label.getLabel());
newLabel.setDefaultLabel(label.getLabel());
newLabel.setPosition(label.getPosition());
result.put(code, newLabel);
}
for (CustomizableTemplateLabel label : labels) {
CustomizableTemplateLabel defaultLabel = result.get(label.getCode());
if (defaultLabel != null) {
label.setDefaultLabel(defaultLabel.getLabel());
}
result.put(defaultLanguage, label);
}
return result;
}
@Transactional
public Map<String, String> getTranslatedTemplateLabels(Long templateId, Long unitId, String defaultLanguage) {
TemplateLabels templateLabels = getTemplateDefaultLabels(templateId);
List<CustomizableTemplateLabel> labels = repo.findCustomizedTemplateLabels(templateId, unitId);
Map<String, CustomizableTemplateLabel> defaultMap = templateLabels.getLanguages().get(defaultLanguage);
if (defaultMap == null) {
defaultMap = templateLabels.getFallbackLabels();
}
if (defaultMap == null) {
return null;
}
Map<String, String> result = new HashMap<>();
for (String code : templateLabels.getCodes()) {
CustomizableTemplateLabel label = defaultMap.get(code);
if (label != null) {
result.put(code, label.getLabel());
}
}
for (CustomizableTemplateLabel label : labels) {
result.put(label.getCode(), label.getLabel());
}
return result;
}
@Transactional
public List<CustomizableTemplateLabel> fetchLabels(Long templateId, Long unitId) {
return repo.findCustomizedTemplateLabels(templateId, unitId);
}
@Transactional
public TemplateLabels getTemplateDefaultLabels(Long templateId) {
CustomizableTemplateLabels labels = useCache ? templateLabelsCache.get(templateId) : null;
if (labels != null) {
return labels;
}
Set<String> codes = new LinkedHashSet<>();
Map<String, Map<String, CustomizableTemplateLabel>> maps = new HashMap<>();
Map<String, CustomizableTemplateLabel> fallbackMap = new HashMap<>();
List<CustomizableTemplateLabel> defaultLabels = repo.findDefaultTemplateLabels(templateId);
//keep order
for (CustomizableTemplateLabel ctl : defaultLabels) {
codes.add(ctl.getCode());
}
//map labels without order
for (CustomizableTemplateLabel ctl : defaultLabels) {
Map<String, CustomizableTemplateLabel> map = maps.get(ctl.getLanguageCode());
if (map == null) {
maps.put(ctl.getLanguageCode(), map = new HashMap<>());
}
map.put(ctl.getCode(), ctl);
if ("en".equalsIgnoreCase(ctl.getLanguageCode()) || !fallbackMap.containsKey(ctl.getCode())) {
fallbackMap.put(ctl.getCode(), ctl);
}
}
labels = new CustomizableTemplateLabels();
labels.codes = codes;
labels.languages = new HashMap<>();
for (String lang : maps.keySet()) {
labels.languages.put(lang, new HashMap<>());
}
// align maps
for (Entry<String, Map<String, CustomizableTemplateLabel>> entry : maps.entrySet()) {
Map<String, CustomizableTemplateLabel> langMap = labels.languages.get(entry.getKey());
for (String code : codes) {
CustomizableTemplateLabel ctl = entry.getValue().get(code);
if (ctl == null) {
ctl = fallbackMap.get(code);
}
if (ctl == null) {
continue;
}
langMap.put(code, ctl);
}
}
if (useCache) {
templateLabelsCache.put(templateId, labels);
}
return labels;
}
@Transactional
public void saveCustomizedLabels(Collection<CustomizableTemplateLabel> labels) {
repo.saveCustomizedLabels(labels);
}
@Transactional
public void saveCustomizedLabels(Long unitId, Long templateId, Map<String, String> labels) {
Map<String, String> map = new HashMap<>(labels);
List<CustomizableTemplateLabel> list = repo.findCustomizedTemplateLabels(templateId, unitId);
for (CustomizableTemplateLabel label : list) {
String s = map.get(label.getCode());
if (s == null) {
repo.delete(label);
} else {
label.setLabel(s);
repo.save(label);
map.remove(label.getCode());
}
}
for (Entry<String, String> e : map.entrySet()) {
CustomizableTemplateLabel label = new CustomizableTemplateLabel();
label.setUnitId(unitId);
label.setTemplate(new Template(templateId));
label.setCode(e.getKey());
label.setLabel(e.getValue());
repo.save(label);
}
}
@Autowired(required = false)
public void setRepo(CustomizableLabelsRepository repo) {
this.repo = repo;
}
@Value("${global.caching:true}")
public void setUseCache(boolean useCache) {
this.useCache = useCache;
}
protected class CustomizableTemplateLabels implements TemplateLabels {
Set<String> codes = new LinkedHashSet<>();
Map<String, Map<String, CustomizableTemplateLabel>> languages = new HashMap<>();
Map<String, CustomizableTemplateLabel> fallbackLabels = new HashMap<>();
@Override
public Set<String> getCodes() {
return codes;
}
@Override
public Map<String, Map<String, CustomizableTemplateLabel>> getLanguages() {
return languages;
}
@Override
public Map<String, CustomizableTemplateLabel> getFallbackLabels() {
return fallbackLabels;
}
}
}
package com.jh.common.template.customizable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Transient;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.jh.common.jpa.AbstractIdEntity;
import com.jh.common.template.Template;
@Entity
@Table(name = "CUSTOMIZABLE_TEMPLATE_LABEL")
public class CustomizableTemplateLabel extends AbstractIdEntity {
@ManyToOne
@JoinColumn(name="TEMPLATE_ID", nullable=false, updatable=false)
@JsonIgnore
private Template template;
@Column(name = "UNIT_ID")
@JsonIgnore
private Long unitId;
@Column(name = "CODE")
private String code;
@JsonIgnore
@Column(name = "POSITION")
private Long position;
@Column(name = "LANG")
@JsonIgnore
private String languageCode;
@Column(name = "LABEL")
private String label;
@Transient
private String defaultLabel;
@Transient
private String defaultLanguageCode;
public Template getTemplate() {
return template;
}
public void setTemplate(Template template) {
this.template = template;
}
public Long getUnitId() {
return unitId;
}
public void setUnitId(Long unitId) {
this.unitId = unitId;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Long getPosition() {
return position;
}
public void setPosition(Long position) {
this.position = position;
}
public String getLanguageCode() {
return languageCode;
}
public void setLanguageCode(String languageCode) {
this.languageCode = languageCode;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getDefaultLabel() {
return defaultLabel;
}
public void setDefaultLabel(String defaultLabel) {
this.defaultLabel = defaultLabel;
}
public String getDefaultLanguageCode() {
return defaultLanguageCode;
}
public void setDefaultLanguageCode(String defaultLanguageCode) {
this.defaultLanguageCode = defaultLanguageCode;
}
@Override
public String toString() {
return "unitId=" + unitId + ", code=" + code + ", languageCode=" + languageCode + ", label=" + label;
}
}
package com.jh.common.template.customizable;
import java.util.Map;
import java.util.Set;
public interface TemplateLabels {
Set<String> getCodes();
Map<String, Map<String, CustomizableTemplateLabel>> getLanguages();
Map<String, CustomizableTemplateLabel> getFallbackLabels();
}
\ No newline at end of file
package com.jh.common.template.customizable;
import javax.persistence.Column;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import com.jh.common.jpa.AbstractIdEntity;
import com.jh.common.template.Template;
//@Entity
//@Table(name = "CUSTOMIZABLE_TEMPLATE_PARAM")
public class TemplateParam extends AbstractIdEntity {
@Column(name = "CODE")
private String code;
@Column(name = "NAME")
private String name;
@Column(name = "LABEL")
private String label;
@ManyToOne
@JoinColumn(name = "TEMPLATE_ID")
private Template template;
@Column(name = "DEFAULT_VALUE")
private String defaultValue;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public Template getTemplate() {
return template;
}
public void setTemplate(Template template) {
this.template = template;
}
public String getDefaultValue() {
return defaultValue;
}
public void setDefaultValue(String defaultValue) {
this.defaultValue = defaultValue;
}
}
package com.jh.common.template.customizable;
import javax.persistence.Column;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import com.jh.common.jpa.AbstractIdEntity;
import com.jh.common.template.Template;
//@Entity
//@Table(name = "CUSTOMIZABLE_TEMPLATE_PARAM")
public class TemplateParamValue extends AbstractIdEntity {
private Long unitId;
@ManyToOne
@JoinColumn(name = "TEMPLATE_PARAM_ID")
private Template template;
@Column(name = "VALUE")
private String value;
}
package com.jh.common.template.customizable;
import javax.persistence.Column;
import com.jh.common.jpa.AbstractIdEntity;
//@Entity
//@Table(name = "CUSTOMIZABLE_TEMPLATE_SETTINGS")
public class TemplateSettings extends AbstractIdEntity {
@Column(name = "UNIT_ID")
private Long unitId;
@Column(name = "TEMPLATE_ID")
private Long templateId;
@Column(name = "DEFAULT_LANG")
private String defaultLanguage;
public TemplateSettings() {
super();
}
public TemplateSettings(Long templateId) {
super();
setId(templateId);
}
}
package com.jh.common.template.handlebars;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.text.MessageFormat;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import com.github.jknack.handlebars.Context;
import com.github.jknack.handlebars.Handlebars;
import com.github.jknack.handlebars.Helper;
import com.github.jknack.handlebars.Options;
import com.github.jknack.handlebars.Template;
import com.github.jknack.handlebars.context.JavaBeanValueResolver;
import com.github.jknack.handlebars.context.MapValueResolver;
import com.jh.common.i18n.I18nContext;
import com.jh.common.i18n.I18nUtils;
import com.jh.common.template.TemplateService;
/**
*
*
* {{#if author}}
* <h1>{{firstName}} {{lastName}}</h1>
* {{else}}
* <h1>Unknown Author</h1>
* {{/if}}
*
*
* {{#each array}}
{{@index}}: {{this}}
{{/each}}
*
*
*{{#each array as |value key|}}
{{#each child as |childValue childKey|}}
{{key}} - {{childKey}}. {{childValue}}
{{/each}}
{{/each}}
*
* {{#with author}}
<p>{{name}}</p>
{{else}}
<p class="empty">No content</p>
{{/with}}
{{#with author as |myAuthor|}}
<h2>By {{myAuthor.firstName}} {{myAuthor.lastName}}</h2>
{{/with}}
*
* Nested handlebars paths can also include ../ segments, which evaluate their paths against a parent context.
*
*
* @author Jan Hrabal
*/
public class HandlebarsTemplateService implements TemplateService {
/** The use cache. */
private boolean useCache = false;
/** The template cache. */
private Map<String, Template> templateCache;
private Handlebars handlebars;
/**
* Initializes instance.
*/
@PostConstruct
public void init() {
templateCache = new HashMap<>();
handlebars = new Handlebars();
handlebars.setParentScopeResolution(true);
handlebars.setPreEvaluatePartialBlocks(false);
//conditional helpers
handlebars.registerHelper("eq", new Helper<Object>() {
public CharSequence apply(Object a, Options options) throws IOException {
Object b = options.param(0, null);
boolean result = Objects.equals(a, b);
if (!result) {
result = Objects.equals(Objects.toString(a, null), Objects.toString(b, null));
}
return result ? options.fn() : options.inverse();
}
});
I18nUtils i18nUtils = I18nUtils.getInstance();
handlebars.registerHelper("formatNumber", new Helper<Number>() {
public CharSequence apply(Number num, Options options) throws IOException {
I18nContext i18nCtx = (I18nContext) options.param(0);
return i18nUtils.formatNumber(num, i18nCtx);
}
});
handlebars.registerHelper("formatPercent", new Helper<Number>() {
public CharSequence apply(Number num, Options options) throws IOException {
I18nContext i18nCtx = (I18nContext) options.param(0);
return i18nUtils.formatPercent(num, i18nCtx);
}
});
handlebars.registerHelper("formatMoney", new Helper<Number>() {
public CharSequence apply(Number num, Options options) throws IOException {
String currency = (String) options.param(0);
I18nContext i18nCtx = (I18nContext) options.param(1);
return i18nUtils.formatMoney(num, currency, i18nCtx);
}
});
handlebars.registerHelper("formatDate", new Helper<LocalDate>() {
public CharSequence apply(LocalDate date, Options options) throws IOException {
I18nContext i18nCtx = (I18nContext) options.param(0);
return i18nUtils.formatDate(date, i18nCtx);
}
});
handlebars.registerHelper("formatLabel", new Helper<String>() {
public CharSequence apply(String key, Options options) throws IOException {
if (options.params == null || options.params.length < 1) {
return "Wrong formatLabel syntax for key " + key + ". It should be formatLabel key labels ...params";
}
Map<String, Object> labels = (Map<String, Object>) options.params[0];
if (labels == null) {
return "!!" + key;
}
String label = (String) labels.get(key);
if (label == null) {
return "!!" + key;
}
Object[] messageParams = options.params.length > 1 ? Arrays.copyOfRange(options.params, 1, options.params.length) : new Object[0];
return MessageFormat.format(label, messageParams);
}
});
}
@Override
public void evaluate(Writer writer, String templateCode, Map<String, Object> data, Locale locale) {
String name = "template://" + templateCode;
Template template = useCache ? templateCache.get(name) : null;
//TODO read template
if (template == null) {
template = handlebars.compileInline(s);
if (useCache) {
templateCache.put(name, template);
}
}
evaluateTemplate(template, data, locale, writer);
}
@Override
public void evaluateInline(Writer writer, String template, Map<String, Object> data, Locale locale) {
if (locale == null) {
locale = Locale.ENGLISH;
}
String name = "inline://" + template;
Template t = useCache ? templateCache.get(name) : null;
if (t == null) {
try {
t = handlebars.compileInline(template);
} catch (Exception e) {
throw new RuntimeException(String.format("Inline template '%s' cannot be read", template), e);
}
if (useCache) {
templateCache.put(name, t);
}
}
evaluateTemplate(t, data, locale, writer);
}
protected void evaluateTemplate(Template t, Map<String, Object> data, Locale locale, Writer writer) {
Context context = Context
.newBuilder(data)
.combine(data)
.resolver(JavaBeanValueResolver.INSTANCE, MapValueResolver.INSTANCE)
.build();
try {
t.apply(context, writer);
} catch (IOException e) {
throw new RuntimeException("Cannot evaluate template", e);
}
}
/**
* Sets the use cache.
*
* @param useCache the new use cache
*/
@Value("${global.caching:true}")
public void setUseCache(boolean useCache) {
this.useCache = useCache;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment