add templates module
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
2021-08-28 18:18:49 +03:00
parent 3ed90496cd
commit 94394cc6e6
17 changed files with 539 additions and 4 deletions

View File

@@ -1,7 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
@@ -24,7 +22,8 @@
<module>stbf-test</module>
<module>stbf-common</module>
<module>stbf-rubenlagus</module>
</modules>
<module>stbf-templates</module>
</modules>
<dependencies>
<dependency>

80
stbf-templates/pom.xml Normal file
View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>stbf-parent</artifactId>
<groupId>ru.penkrat.stbf</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>stbf-templates</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.jar.plugin.version>3.0.2</maven.jar.plugin.version>
<jacoco.maven.plugin.version>0.8.0</jacoco.maven.plugin.version>
<junit.version>4.12</junit.version>
<assertj.version>3.9.0</assertj.version>
<mockito.version>2.13.0</mockito.version>
<jmustache.version>1.14</jmustache.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven.jar.plugin.version}</version>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<artifactId>stbf-api</artifactId>
<groupId>ru.penkrat.stbf</groupId>
<version>${project.version}</version>
</dependency>
<dependency>
<artifactId>stbf-common</artifactId>
<groupId>ru.penkrat.stbf</groupId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.11.1</version>
</dependency>
<dependency>
<groupId>com.samskivert</groupId>
<artifactId>jmustache</artifactId>
<version>${jmustache.version}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<artifactId>stbf-pengrad</artifactId>
<groupId>ru.penkrat.stbf</groupId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,9 @@
package ru.penkrat.stbf.templates;
import ru.penkrat.stbf.api.Keyboard;
public interface KeyboardProvider {
Keyboard getKeyboard();
}

View File

@@ -0,0 +1,11 @@
package ru.penkrat.stbf.templates;
import ru.penkrat.stbf.api.Screen;
public interface ScreenResolver {
Screen getScreen(String name);
Screen getScreen(String name, Object context);
}

View File

@@ -0,0 +1,7 @@
package ru.penkrat.stbf.templates;
public interface TemplateRenderer {
String render(String template, Object context);
}

View File

@@ -0,0 +1,13 @@
package ru.penkrat.stbf.templates.impl;
import com.samskivert.mustache.Mustache;
import ru.penkrat.stbf.templates.TemplateRenderer;
public class MustacheRenderer implements TemplateRenderer {
@Override
public String render(String template, Object context) {
return Mustache.compiler().compile(template).execute(context);
}
}

View File

@@ -0,0 +1,38 @@
package ru.penkrat.stbf.templates.utils;
import lombok.experimental.UtilityClass;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@UtilityClass
public class ReflectionUtils {
public <T> T getMethodResult(Object context, String methodName, Class<T> clazz) {
try {
Method method = context.getClass().getMethod(methodName);
Object result = method.invoke(context);
if (clazz.isAssignableFrom(result.getClass())) {
return (T) result;
}
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}

View File

@@ -0,0 +1,27 @@
package ru.penkrat.stbf.templates.xml;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@JacksonXmlRootElement(localName = "button")
class Button {
@JacksonXmlText
private String text;
@JacksonXmlProperty(isAttribute = true, localName = "if")
private String ifCondition;
public Button(String text) {
this.text = text;
}
}

View File

@@ -0,0 +1,20 @@
package ru.penkrat.stbf.templates.xml;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
class ButtonsRow {
@JsonProperty("button")
@JacksonXmlElementWrapper(useWrapping = false)
private List<Button> buttons = new ArrayList<>();
}

View File

@@ -0,0 +1,29 @@
package ru.penkrat.stbf.templates.xml;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
class KeyboardWrapper {
@JsonProperty("row")
@JacksonXmlElementWrapper(useWrapping = false)
private List<ButtonsRow> rows = new ArrayList<>();
@JacksonXmlProperty(isAttribute = true)
private String factoryMethod;
@JsonIgnore
public boolean isNotEmpty() {
return !rows.isEmpty();
}
}

View File

@@ -0,0 +1,12 @@
package ru.penkrat.stbf.templates.xml;
import ru.penkrat.stbf.templates.TemplateRenderer;
class NoopTemplateRenderer implements TemplateRenderer {
@Override
public String render(String template, Object context) {
return template;
}
}

View File

@@ -0,0 +1,22 @@
package ru.penkrat.stbf.templates.xml;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
class ScreenItem {
@JacksonXmlProperty(isAttribute = true)
private String id;
@JacksonXmlProperty(isAttribute = true)
private String name;
private String text;
private KeyboardWrapper keyboard = new KeyboardWrapper();
}

View File

@@ -0,0 +1,20 @@
package ru.penkrat.stbf.templates.xml;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.Getter;
@JacksonXmlRootElement(localName = "screens")
class Screens {
@Getter
@JacksonXmlElementWrapper(useWrapping = false, localName = "screen")
@JsonProperty("screen")
private List<ScreenItem> screens = new ArrayList<>();
}

View File

@@ -0,0 +1,111 @@
package ru.penkrat.stbf.templates.xml;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import ru.penkrat.stbf.api.Action;
import ru.penkrat.stbf.api.Keyboard;
import ru.penkrat.stbf.api.KeyboardBuilder;
import ru.penkrat.stbf.api.Screen;
import ru.penkrat.stbf.common.screen.TextScreen;
import ru.penkrat.stbf.templates.ScreenResolver;
import ru.penkrat.stbf.templates.TemplateRenderer;
import ru.penkrat.stbf.templates.utils.ReflectionUtils;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
public class XmlScreenResolver implements ScreenResolver {
private final XmlMapper mapper = new XmlMapper();
private final Map<String, List<ScreenItem>> byId;
private final Map<String, List<ScreenItem>> byName;
@Setter
private TemplateRenderer templateRenderer = new NoopTemplateRenderer();
public XmlScreenResolver(InputStream is) throws IOException {
Screens screens = mapper.readValue(is, Screens.class);
byId = screens.getScreens().stream()
.collect(Collectors.groupingBy(ScreenItem::getId));
byName = screens.getScreens().stream()
.collect(Collectors.groupingBy(ScreenItem::getName));
}
@Override
public Screen getScreen(String name) {
ScreenItem item = get(name);
return new TextScreen(item.getText(), buildKeyboard(item.getKeyboard()));
}
@Override
public Screen getScreen(String name, Object context) {
ScreenItem item = get(name);
return new TextScreen(templateRenderer.render(item.getText(), context),
resolveKeyboard(item.getKeyboard(), context));
}
private Keyboard resolveKeyboard(KeyboardWrapper wrapper, Object context) {
if (wrapper == null)
return null;
if (wrapper.getFactoryMethod() != null && !wrapper.getFactoryMethod().isEmpty() && context != null) {
val keyboard = ReflectionUtils.getMethodResult(context, wrapper.getFactoryMethod(), Keyboard.class);
if (keyboard == null) {
log.warn("Method '{}' returns NULL value!", wrapper.getFactoryMethod());
}
return keyboard;
}
return buildKeyboard(wrapper);
}
private Keyboard buildKeyboard(KeyboardWrapper wrapper) {
Keyboard keyboard = null;
if (wrapper.isNotEmpty()) {
KeyboardBuilder builder = KeyboardBuilder.newKeyboard();
for (ButtonsRow row : wrapper.getRows()) {
List<Action> buttons = row.getButtons().stream()
.map(btn -> Action.builder()
.text(btn.getText())
.build())
.collect(Collectors.toList());
builder.row(buttons.toArray(new Action[buttons.size()]));
}
keyboard = builder.build();
}
return keyboard;
}
private ScreenItem get(String key) {
List<ScreenItem> list = byId.get(key);
if (list != null) {
if (list.size() > 1) {
throw new IllegalStateException("Non unique 'id' " + key);
}
return list.get(0);
}
list = byName.get(key);
if (list != null) {
if (list.size() > 1) {
throw new IllegalStateException("Non unique 'name' " + key);
}
return list.get(0);
}
throw new IllegalStateException("Screen not found by 'id' or 'name' " + key);
}
}

View File

@@ -0,0 +1,59 @@
package ru.penkrat.stbf.templates.xml;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.InputStream;
import org.junit.Before;
import org.junit.Test;
import ru.penkrat.stbf.api.Keyboard;
import ru.penkrat.stbf.api.KeyboardBuilder;
import ru.penkrat.stbf.api.Screen;
import ru.penkrat.stbf.templates.impl.MustacheRenderer;
public class XmlScreenResolverTest {
@Before
public void setUp() throws Exception {
}
@Test
public void testRead() throws Exception {
InputStream xmlUnderTest = XmlScreenResolverTest.class.getResourceAsStream("screens.xml");
XmlScreenResolver resolver = new XmlScreenResolver(xmlUnderTest);
Screen screen1 = resolver.getScreen("screen-1");
assertThat(screen1).isNotNull();
assertThat(screen1.getText()).isEqualTo("Test text");
assertThat(screen1.getKeyboard()).isNotNull();
}
@Test
public void testReadWithTemplates() throws Exception {
InputStream xmlUnderTest = XmlScreenResolverTest.class.getResourceAsStream("screens.xml");
XmlScreenResolver resolver = new XmlScreenResolver(xmlUnderTest);
resolver.setTemplateRenderer(new MustacheRenderer());
Keyboard keyboard = KeyboardBuilder.newKeyboard().build();
Screen screen2 = resolver.getScreen("screen-2", new Object() {
@SuppressWarnings("unused")
public String name = "Tester";
@SuppressWarnings("unused")
public Keyboard getKeyboard() {
return keyboard;
}
});
assertThat(screen2).isNotNull();
assertThat(screen2.getText()).isEqualTo("Hello, Tester");
assertThat(screen2.getKeyboard()).isEqualTo(keyboard);
}
}

View File

@@ -0,0 +1,59 @@
package ru.penkrat.stbf.templates.xml;
import org.junit.Before;
import org.junit.Test;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import ru.penkrat.stbf.templates.xml.Button;
import ru.penkrat.stbf.templates.xml.ButtonsRow;
import ru.penkrat.stbf.templates.xml.ScreenItem;
import ru.penkrat.stbf.templates.xml.Screens;
public class XmlWriterTest {
private final XmlMapper mapper = new XmlMapper();
@Before
public void setUp() throws Exception {
mapper.enable(SerializationFeature.INDENT_OUTPUT);
}
@Test
public void testWrite() throws Exception {
Screens root = new Screens();
ScreenItem item1 = new ScreenItem();
item1.setId("1");
item1.setName("screen-1");
item1.setText("Test text");
Button btn1 = new Button("Btn1");
btn1.setIfCondition("false");
Button btn2 = new Button("Btn2");
ButtonsRow row1 = new ButtonsRow();
row1.getButtons().add(btn1);
row1.getButtons().add(btn2);
ButtonsRow row2 = new ButtonsRow();
row2.getButtons().add(btn1);
row2.getButtons().add(btn2);
item1.getKeyboard().getRows().add(row1);
item1.getKeyboard().getRows().add(row2);
ScreenItem item2 = new ScreenItem();
item2.setId("2");
item2.setName("screen-2");
item2.setText("Hello, {{ name }}");
item2.getKeyboard().setFactoryMethod("getKeyboard");
root.getScreens().add(item1);
root.getScreens().add(item2);
String xml = mapper.writeValueAsString(root);
System.out.println(xml);
}
}

View File

@@ -0,0 +1,19 @@
<screens>
<screen id="1" name="screen-1">
<text>Test text</text>
<keyboard>
<row>
<button if="false">Btn1</button>
<button>Btn2</button>
</row>
<row>
<button>Btn1</button>
<button>Btn2</button>
</row>
</keyboard>
</screen>
<screen id="2" name="screen-2">
<text>Hello, {{ name }}</text>
<keyboard factoryMethod="getKeyboard"/>
</screen>
</screens>