Commit 31bd2fe9 by Jan Hrabal

WIP

parent 39d253af
...@@ -5,13 +5,13 @@ insert into APP_USER (ID, EMAIL, FIRST_NAME, LAST_NAME, PASSWORD, PASSWORD_SALT, ...@@ -5,13 +5,13 @@ insert into APP_USER (ID, EMAIL, FIRST_NAME, LAST_NAME, PASSWORD, PASSWORD_SALT,
insert into APP_USER_ROLE (USER_ID, ROLE_ID) values (1, 1); insert into APP_USER_ROLE (USER_ID, ROLE_ID) values (1, 1);
-- default templates -- default templates
insert into TEMPLATE (ID, CODE, TEMPLATE) values (1, 'email.layout', '{{content}}'); insert into SIMPLE_TEMPLATE (ID, CODE, TEMPLATE) values (1, 'email.layout', '{{content}}');
insert into TEMPLATE (ID, CODE, TEMPLATE) values (2, 'email.signup.subject', 'Welcome to ...'); insert into SIMPLE_TEMPLATE (ID, CODE, TEMPLATE) values (2, 'email.signup.subject', 'Welcome to ...');
insert into TEMPLATE (ID, CODE, TEMPLATE) values (3, 'email.signup.text', 'Welcome to ...'); insert into SIMPLE_TEMPLATE (ID, CODE, TEMPLATE) values (3, 'email.signup.text', 'Welcome to ...');
insert into TEMPLATE (ID, CODE, TEMPLATE) values (4, 'email.signup.html', 'Welcome to ...'); insert into SIMPLE_TEMPLATE (ID, CODE, TEMPLATE) values (4, 'email.signup.html', 'Welcome to ...');
insert into TEMPLATE (ID, CODE, TEMPLATE) values (5, 'email.password.subject', 'Password subject'); insert into SIMPLE_TEMPLATE (ID, CODE, TEMPLATE) values (5, 'email.password.subject', 'Password subject');
insert into TEMPLATE (ID, CODE, TEMPLATE) values (6, 'email.password.text', 'Password text {{token}}'); insert into SIMPLE_TEMPLATE (ID, CODE, TEMPLATE) values (6, 'email.password.text', 'Password text {{token}}');
insert into TEMPLATE (ID, CODE, TEMPLATE) values (7, 'email.password.html', 'Password HTML'); insert into SIMPLE_TEMPLATE (ID, CODE, TEMPLATE) values (7, 'email.password.html', 'Password HTML');
-- legal documents -- legal documents
insert into LEGAL_DOCUMENT (ID, CODE, LOCALE, CONTENT) values (1, 'AUTH.TERMS', 'en', '<p>By using the Inodio service you are insert into LEGAL_DOCUMENT (ID, CODE, LOCALE, CONTENT) values (1, 'AUTH.TERMS', 'en', '<p>By using the Inodio service you are
......
...@@ -179,39 +179,6 @@ CREATE TABLE EMAIL_ATTACHMENT ( ...@@ -179,39 +179,6 @@ CREATE TABLE EMAIL_ATTACHMENT (
CREATE INDEX EMAIL_ATTACHMENT_IDX ON EMAIL_ATTACHMENT(EMAIL_ID); CREATE INDEX EMAIL_ATTACHMENT_IDX ON EMAIL_ATTACHMENT(EMAIL_ID);
-- TEMPLATES
CREATE TABLE TEMPLATE (
ID INT8 NOT NULL,
CODE VARCHAR(250),
NAME VARCHAR(100),
TEMPLATE TEXT,
CONSTRAINT PK_TEMPLATE PRIMARY KEY(ID)
);
CREATE INDEX TEMPLATE_CODE_IDX ON 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 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);
-- LEGAL DOCUMENTS -- LEGAL DOCUMENTS
CREATE TABLE LEGAL_DOCUMENT ( CREATE TABLE LEGAL_DOCUMENT (
ID INT8 NOT NULL, ID INT8 NOT NULL,
...@@ -245,4 +212,21 @@ CREATE TABLE ATTACHMENT ( ...@@ -245,4 +212,21 @@ CREATE TABLE ATTACHMENT (
CREATE INDEX ATTACHMENT_IDX ON ATTACHMENT(UNIT_ID, OBJECT_TYPE, OBJECT_ID); CREATE INDEX ATTACHMENT_IDX ON ATTACHMENT(UNIT_ID, OBJECT_TYPE, OBJECT_ID);
-- SIMPLE TEMPLATES
CREATE TABLE SIMPLE_TEMPLATE (
ID INT8 NOT NULL,
CODE VARCHAR(250),
NAME VARCHAR(100),
LOCALE VARCHAR(50),
LAYOUT_CODE VARCHAR(250),
TEMPLATE TEXT,
CONSTRAINT PK_SIMPLE_TEMPLATE PRIMARY KEY(ID)
);
CREATE INDEX SIMPLE_TEMPLATE_CODE_IDX ON SIMPLE_TEMPLATE(CODE);
CREATE INDEX SIMPLE_TEMPLATE_CODE_LOCALE_IDX ON SIMPLE_TEMPLATE(CODE, LOCALE);
-- TODO -- TODO
\ No newline at end of file
package com.jh.boot; package com.jh.boot;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
...@@ -29,11 +30,13 @@ public class JhBootApplication { ...@@ -29,11 +30,13 @@ public class JhBootApplication {
} }
@Bean @Bean
@ConditionalOnMissingBean(TemplateEngine.class)
public TemplateEngine templateEngine() { public TemplateEngine templateEngine() {
return new HandlebarsTemplateEngine(); return new HandlebarsTemplateEngine();
} }
@Bean @Bean
@ConditionalOnMissingBean(TemplateService.class)
public TemplateService templateService() { public TemplateService templateService() {
return new CustomizableTemplateService(); return new CustomizableTemplateService();
} }
......
package com.jh.boot.template.customizable;
import java.util.Collection;
import java.util.List;
import javax.persistence.Query;
import org.springframework.stereotype.Repository;
import com.jh.boot.jpa.AbstractHibernateRepository;
import com.jh.boot.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<TemplateLabel> findCustomizedTemplateLabels(String templateCode, Long unitId) {
Query q = entityManager.createQuery("select tl from TemplateLabel tl left join tl.template t where t.code = :templateCode and tl.unitId = :unitId order by tl.position asc");
q.setParameter("templateCode", templateCode);
q.setParameter("unitId", unitId);
return q.getResultList();
}
@SuppressWarnings("unchecked")
public List<TemplateLabel> findDefaultTemplateLabels(String templateCode) {
Query q = entityManager.createQuery("select tl from TemplateLabel tl left join tl.template t where t.code = :templateCode and tl.unitId is null");
q.setParameter("templateCode", templateCode);
return q.getResultList();
}
public void saveCustomizedLabels(Collection<TemplateLabel> labels) {
if (labels == null) {
return;
}
labels.stream().forEach(this::save);
}
}
package com.jh.boot.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;
@Service
public class CustomizableLabelsService {
private CustomizableLabelsRepository repo;
private Map<String, CustomizableTemplateLabels> templateLabelsCache = new HashMap<>();
private boolean useCache;
@Transactional
public Map<String, TemplateLabel> getCustomizationTemplateLabels(String templateCode, Long unitId, String defaultLanguage) {
TemplateLabels templateLabels = getTemplateDefaultLabels(templateCode);
List<TemplateLabel> labels = repo.findCustomizedTemplateLabels(templateCode, unitId);
Map<String, TemplateLabel> defaultMap = templateLabels.getLanguages().get(defaultLanguage);
if (defaultMap == null) {
defaultMap = templateLabels.getFallbackLabels();
}
if (defaultMap == null) {
return null;
}
Map<String, TemplateLabel> result = new HashMap<>();
for (String code : templateLabels.getCodes()) {
TemplateLabel label = defaultMap.get(code);
TemplateLabel newLabel = new TemplateLabel();
newLabel.setCode(code);
newLabel.setLabel(label.getLabel());
newLabel.setDefaultLabel(label.getLabel());
newLabel.setPosition(label.getPosition());
result.put(code, newLabel);
}
for (TemplateLabel label : labels) {
TemplateLabel defaultLabel = result.get(label.getCode());
if (defaultLabel != null) {
label.setDefaultLabel(defaultLabel.getLabel());
}
result.put(defaultLanguage, label);
}
return result;
}
@Transactional
public Map<String, String> getTranslatedTemplateLabels(String templateCode, Long unitId, String defaultLanguage) {
TemplateLabels templateLabels = getTemplateDefaultLabels(templateCode);
List<TemplateLabel> labels = repo.findCustomizedTemplateLabels(templateCode, unitId);
Map<String, TemplateLabel> 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()) {
TemplateLabel label = defaultMap.get(code);
if (label != null) {
result.put(code, label.getLabel());
}
}
for (TemplateLabel label : labels) {
result.put(label.getCode(), label.getLabel());
}
return result;
}
@Transactional
public List<TemplateLabel> fetchLabels(String templateCode, Long unitId) {
return repo.findCustomizedTemplateLabels(templateCode, unitId);
}
@Transactional
public TemplateLabels getTemplateDefaultLabels(String templateCode) {
CustomizableTemplateLabels labels = useCache ? templateLabelsCache.get(templateCode) : null;
if (labels != null) {
return labels;
}
Set<String> codes = new LinkedHashSet<>();
Map<String, Map<String, TemplateLabel>> maps = new HashMap<>();
Map<String, TemplateLabel> fallbackMap = new HashMap<>();
List<TemplateLabel> defaultLabels = repo.findDefaultTemplateLabels(templateCode);
//keep order
for (TemplateLabel ctl : defaultLabels) {
codes.add(ctl.getCode());
}
//map labels without order
for (TemplateLabel ctl : defaultLabels) {
Map<String, TemplateLabel> 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, TemplateLabel>> entry : maps.entrySet()) {
Map<String, TemplateLabel> langMap = labels.languages.get(entry.getKey());
for (String code : codes) {
TemplateLabel ctl = entry.getValue().get(code);
if (ctl == null) {
ctl = fallbackMap.get(code);
}
if (ctl == null) {
continue;
}
langMap.put(code, ctl);
}
}
if (useCache) {
templateLabelsCache.put(templateCode, labels);
}
return labels;
}
@Transactional
public void saveCustomizedLabels(Collection<TemplateLabel> labels) {
repo.saveCustomizedLabels(labels);
}
//
// @Transactional
// public void saveCustomizedLabels(Long unitId, String templateCode, Map<String, String> labels) {
// Map<String, String> map = new HashMap<>(labels);
// List<CustomizableTemplateLabel> list = repo.findCustomizedTemplateLabels(templateCode, 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(templateCode));
// 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, TemplateLabel>> languages = new HashMap<>();
Map<String, TemplateLabel> fallbackLabels = new HashMap<>();
@Override
public Set<String> getCodes() {
return codes;
}
@Override
public Map<String, Map<String, TemplateLabel>> getLanguages() {
return languages;
}
@Override
public Map<String, TemplateLabel> getFallbackLabels() {
return fallbackLabels;
}
}
}
package com.jh.boot.template.customizable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import com.jh.boot.template.Template;
import com.jh.boot.template.TemplateLoader;
public class CustomizableTemplateLoader implements TemplateLoader {
@Autowired
private CustomizableLabelsRepository repository;
@Override
@Transactional
public Template loadTemplate(String code) {
return repository.fetchTemplate(code);
}
}
package com.jh.boot.template.customizable;
import java.io.StringWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import com.jh.boot.template.TemplateEngine;
import com.jh.boot.template.TemplateService;
public class CustomizableTemplateService implements TemplateService {
@Autowired
private TemplateEngine engine;
@Autowired
private CustomizableLabelsService labelsService;
@Override
public String evaluate(String templateCode, Map<String, Object> data, Locale locale) {
if (data == null) {
data = Collections.emptyMap();
}
Map<String, String> labels = labelsService.getTranslatedTemplateLabels(templateCode, null, locale.toString());
data = new HashMap<String, Object>(data);
data.put("labels", labels);
StringWriter writer = new StringWriter();
engine.evaluate(writer, templateCode, data);
return writer.toString();
}
}
package com.jh.boot.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.boot.jpa.AbstractIdEntity;
import com.jh.boot.template.Template;
@Entity
@Table(name = "CUSTOMIZABLE_TEMPLATE_LABEL")
public class TemplateLabel 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.boot.template.customizable;
import java.util.Map;
import java.util.Set;
public interface TemplateLabels {
Set<String> getCodes();
Map<String, Map<String, TemplateLabel>> getLanguages();
Map<String, TemplateLabel> getFallbackLabels();
}
\ No newline at end of file
package com.jh.boot.template.customizable;
import javax.persistence.Column;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import com.jh.boot.jpa.AbstractIdEntity;
import com.jh.boot.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.boot.template.customizable;
import javax.persistence.Column;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import com.jh.boot.jpa.AbstractIdEntity;
import com.jh.boot.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.boot.template.customizable;
import javax.persistence.Column;
import com.jh.boot.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);
}
}
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