added a first version of java doc for the templating module

This commit is contained in:
Sheldan
2020-05-11 20:46:14 +02:00
parent e89c3b8796
commit 70428e6e03
19 changed files with 283 additions and 10 deletions

View File

@@ -12,6 +12,9 @@ import org.springframework.ui.freemarker.FreeMarkerConfigurationFactory;
import java.io.IOException;
import java.util.Locale;
/**
* Configuration bean used to provide the {@link Configuration} bean to the spring context.
*/
@org.springframework.context.annotation.Configuration
public class FreemarkerConfiguration {
@@ -24,6 +27,12 @@ public class FreemarkerConfiguration {
@Autowired
private InstantMethod instantMethod;
/**
* Creates a {@link Configuration} bean with the appropriate configuration which includes:
* The correct compatibility version and the provided formatter methods to be used in the templates.
* The encoding of the templates is set to UTF-8.
* @return A configured {@link Configuration} bean according to the configuration
*/
@Bean
public Configuration freeMarkerConfiguration() throws IOException, TemplateException {
FreeMarkerConfigurationFactory factory = new FreeMarkerConfigurationFactory();

View File

@@ -16,6 +16,10 @@ import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;
/**
* Loads the available templates from the class path and uploads them to the database, overriding existing templates in the process.
* This will load all *.ftl files at any level within a folder named 'templates' in the resources folder.
*/
@Component
@Slf4j
public class TemplateSeedDataLoader {
@@ -26,6 +30,10 @@ public class TemplateSeedDataLoader {
@Autowired
private TemplateManagementService service;
/**
* Is executed when the spring context is started, this will load all templates from the class path and
* store them in the database overriding the existing ones in the process.
*/
@EventListener
public void handleContextRefreshEvent(ContextRefreshedEvent ctxStartEvt) {
log.info("Updating templates.");

View File

@@ -11,6 +11,10 @@ import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
/**
* Loads the the template from the database to be used by Freemarker. This bean is also used when the templates within
* templates are used.
*/
@Component
public class DatabaseTemplateLoader implements TemplateLoader {
@@ -20,6 +24,11 @@ public class DatabaseTemplateLoader implements TemplateLoader {
@Autowired
private TemplateManagementService templateManagementService;
/**
* Loads the content of the template object
* @param s The key of the template to load
* @return The template loaded from the database
*/
@Override
public Object findTemplateSource(String s) throws IOException {
return templateManagementService.getTemplateByKey(s);
@@ -36,6 +45,12 @@ public class DatabaseTemplateLoader implements TemplateLoader {
}
}
/**
* Retrieves the content of the template from the retrieved {@link Template} object
* @param o The retrieved {@link Template} object from the database
* @param s The encoding of the object
* @return The content of the template as a String reader
*/
@Override
public Reader getReader(Object o, String s) throws IOException {
return new StringReader(((Template) o).getContent());

View File

@@ -12,12 +12,23 @@ import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.List;
/**
* Method used to format the {@link Duration} object, as its not natively supported by Freemarker.
* This method only accepts a single {@link Duration} object as the first parameter.
*/
@Component
public class DurationMethod implements TemplateMethodModelEx {
@Autowired
private TemplateService service;
/**
* This method expects a single Duration object in the list of arguments. It will throw a {@link TemplateModelException}
* otherwise
* @param arguments The parameters passed to this method, should be only a single duration.
* @return The string representation of the {@link Duration} object
* @throws TemplateModelException
*/
@Override
public Object exec(List arguments) throws TemplateModelException {
if (arguments.size() != 1) {
@@ -30,6 +41,7 @@ public class DurationMethod implements TemplateMethodModelEx {
Duration duration = (Duration) wrappedObject;
StringBuilder stringBuilder = new StringBuilder();
// upgrading to java 9 makes this nicer
// todo refactor to use a single template, instead of multiple ones
long days = duration.toDays();
if(days > 0) {
stringBuilder.append(service.renderTemplate("day", getParam(days)));
@@ -51,6 +63,11 @@ public class DurationMethod implements TemplateMethodModelEx {
return stringBuilder.toString();
}
/**
* Creates the parameter passed to the template for rendering the single time unit template.
* @param value
* @return
*/
private HashMap<String, Object> getParam(Long value) {
HashMap<String, Object> params = new HashMap<>();
params.put("amount", value);

View File

@@ -11,15 +11,23 @@ import org.springframework.stereotype.Component;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
/**
* Formats the passed {@link Instant} object with the given Formatter. The format will be directly passed to {@link DateTimeFormatter}.
*/
@Component
public class InstantMethod implements TemplateMethodModelEx {
@Autowired
private TemplateService service;
/**
* Renders the given {@link Instant} object with the given String. Internally {@link DateTimeFormatter} will be used.
* @param arguments The list of arguments, first element must be an {@link Instant} and the second one must be a {@link String}.
* @return The formatted {@link Instant} as a string.
* @throws TemplateModelException If there are less or more arguments in the list and if the first element is not a {@link Instant}.
*/
@Override
public Object exec(List arguments) throws TemplateModelException {
if (arguments.size() != 2) {

View File

@@ -4,9 +4,22 @@ package dev.sheldan.abstracto.templating.model;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
/**
* The container class used to deserialize the embed configuration for the author in {@link net.dv8tion.jda.api.entities.MessageEmbed}
*/
@Getter
@Setter
public class EmbedAuthor {
/**
* The name used in the {@link net.dv8tion.jda.api.entities.MessageEmbed} author
*/
private String name;
/**
* The URL used in the {@link net.dv8tion.jda.api.entities.MessageEmbed} author
*/
private String url;
/**
* The picture used as the avatar of the author in {@link net.dv8tion.jda.api.entities.MessageEmbed}
*/
private String avatar;
}

View File

@@ -3,9 +3,22 @@ package dev.sheldan.abstracto.templating.model;
import lombok.Getter;
import lombok.Setter;
@Setter @Getter
/**
* The container class used to deserialize the embed configuration for the color in {@link net.dv8tion.jda.api.entities.MessageEmbed}
*/
@Setter
@Getter
public class EmbedColor {
/**
* The red part of RGB
*/
private Integer r;
/**
* The green part of RGB
*/
private Integer g;
/**
* The blue part of RGB
*/
private Integer b;
}

View File

@@ -6,16 +6,51 @@ import lombok.Setter;
import java.time.OffsetDateTime;
import java.util.List;
@Getter @Setter
/**
* The whole container object used to deserialize the whole embed configuration
* https://raw.githubusercontent.com/DV8FromTheWorld/JDA/assets/assets/docs/embeds/07-addField.png
*/
@Getter
@Setter
public class EmbedConfiguration {
/**
* The {@link EmbedAuthor} object holding the configuration for the author of the {@link net.dv8tion.jda.api.entities.MessageEmbed}
*/
private EmbedAuthor author;
/**
* The {@link EmbedTitle} object holding the configuration for the title in the {@link net.dv8tion.jda.api.entities.MessageEmbed}
*/
private EmbedTitle title;
/**
* The {@link EmbedColor} object holding the configuration for the color in the {@link net.dv8tion.jda.api.entities.MessageEmbed}
*/
private EmbedColor color;
/**
* The description which is going to be used in the {@link net.dv8tion.jda.api.entities.MessageEmbed}
*/
private String description;
/**
* The link to the image used as a thumbnail in the {@link net.dv8tion.jda.api.entities.MessageEmbed}
*/
private String thumbnail;
/**
* The link to the image used as the image in the {@link net.dv8tion.jda.api.entities.MessageEmbed}
*/
private String imageUrl;
/**
* The collection containing all the objects containing the configuration for the fields in the {@link net.dv8tion.jda.api.entities.MessageEmbed}
*/
private List<EmbedField> fields;
/**
* The {@link EmbedFooter} object holding the configuration for the footer in {@link net.dv8tion.jda.api.entities.MessageEmbed}
*/
private EmbedFooter footer;
/**
* Thhe {@link OffsetDateTime} object used as the time stamp in the {@link net.dv8tion.jda.api.entities.MessageEmbed}
*/
private OffsetDateTime timeStamp;
/**
* The message which is posted along the {@link net.dv8tion.jda.api.entities.MessageEmbed} as a normal message.
*/
private String additionalMessage;
}

View File

@@ -3,9 +3,23 @@ package dev.sheldan.abstracto.templating.model;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
/**
* The container class used to deserialize the embed configuration for a field in {@link net.dv8tion.jda.api.entities.MessageEmbed}
*/
@Getter
@Setter
public class EmbedField {
/**
* The name of the field to be set, must not be null or empty
*/
private String name;
/**
* The value of the field to be set, must not be null or empty
*/
private String value;
/**
* Whether or not the field should be rendered inline within the {@link net.dv8tion.jda.api.entities.MessageEmbed}.
* This means, if multiple fields can be put on the same height in the {@link net.dv8tion.jda.api.entities.MessageEmbed} this will be done by discord.
*/
private Boolean inline;
}

View File

@@ -3,8 +3,18 @@ package dev.sheldan.abstracto.templating.model;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
/**
* The container object used to deserialize the embed configuration for the footer in the {@link net.dv8tion.jda.api.entities.MessageEmbed}
*/
@Getter
@Setter
public class EmbedFooter {
/**
* The text which is going to be used as the footer text
*/
private String text;
/**
* The link to the image which is going to be used as the icon of the footer
*/
private String icon;
}

View File

@@ -3,8 +3,18 @@ package dev.sheldan.abstracto.templating.model;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
/**
* The container class used to deserialize the embed configuration used in the title in {@link net.dv8tion.jda.api.entities.MessageEmbed}
*/
@Getter
@Setter
public class EmbedTitle {
/**
* The text which is going to be used as the title of the embed
*/
private String title;
/**
* The link which is used when clicking on the title of the embed
*/
private String url;
}

View File

@@ -1,9 +1,12 @@
package dev.sheldan.abstracto.templating.loading;
package dev.sheldan.abstracto.templating.repository;
import dev.sheldan.abstracto.templating.model.database.Template;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
* Repository used to load the templates from the database.
*/
@Repository
public interface TemplateRepository extends JpaRepository<Template, String> {
}

View File

@@ -19,6 +19,9 @@ import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
/**
* Bean used to render a template, identified by a key, with the passed model.
*/
@Slf4j
@Component
public class TemplateServiceBean implements TemplateService {
@@ -30,12 +33,29 @@ public class TemplateServiceBean implements TemplateService {
private Gson gson;
/**
* Formats the passed passed count with the embed used for formatting pages.
* @param count The index of the page you want formated.
* @return The rendered template as a string object
*/
private String getPageString(Integer count) {
HashMap<String, Object> params = new HashMap<>();
params.put("count", count);
return renderTemplateWithMap("embed_page_count", params);
}
/**
* Retrieves the key which gets suffixed with '_embed' and this retrives the embed configuration. This configuration is then rendered
* and deserialized with GSON into a {@link EmbedConfiguration} object. This object is then rendered into a {@link MessageToSend} and returned.
* If the individual element do not fit in an embed, for example, if the field count is to high, another embed will be created in the {@link MessageToSend} object.
* If multiple embeds are necessary to provide what the {@link EmbedConfiguration} wanted, this method will automatically set the footer of the additional {@link MessageEmbed}
* with a formatted page count.
* This method will to try its best to provided a message which can be handled by discord without rejecting it. Besides that, the content from the rendered template, will be passed
* into the {@link EmbedBuilder} directly.
* @param key The key of the embed template to be used for rendering.
* @param model The model providing the properties to be used for rendering
* @return The {@link MessageToSend} object which is properly split up in order to be send to discord.
*/
@Override
public MessageToSend renderEmbedTemplate(String key, Object model) {
String embedConfig = this.renderTemplate(key + "_embed", model);
@@ -100,6 +120,12 @@ public class TemplateServiceBean implements TemplateService {
.build();
}
/**
* Enlarges the passed list of builders, if the passed index is not yet available within the list.
* When a new builder is needed, this will automatically set the footer with a page indicator.
* @param builders The current list of {@link EmbedBuilder} builders used.
* @param neededIndex The desired index in the list which should be available for using.
*/
private void extendIfNecessary(List<EmbedBuilder> builders, double neededIndex) {
if(neededIndex > builders.size() - 1) {
for (int i = builders.size(); i < neededIndex + 1; i++) {
@@ -110,6 +136,12 @@ public class TemplateServiceBean implements TemplateService {
}
}
/**
* Renders the template identified by the key with the passed {@link HashMap}
* @param key The key of the template to be rendered.
* @param parameters The {@link HashMap} to be used as the parameters for the template
* @return The rendered template as a string
*/
@Override
public String renderTemplateWithMap(String key, HashMap<String, Object> parameters) {
try {
@@ -120,6 +152,12 @@ public class TemplateServiceBean implements TemplateService {
}
}
/**
* Renders the template identified by the key with the passed object as model
* @param key The key of the template to be rendered
* @param model The object containing the model to be used in the template
* @return The rendered template as a string
*/
@Override
public String renderTemplate(String key, Object model) {
try {

View File

@@ -1,6 +1,6 @@
package dev.sheldan.abstracto.templating.service.management;
import dev.sheldan.abstracto.templating.loading.TemplateRepository;
import dev.sheldan.abstracto.templating.repository.TemplateRepository;
import dev.sheldan.abstracto.templating.model.database.Template;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -8,6 +8,10 @@ import org.springframework.stereotype.Component;
import java.time.Instant;
/**
* ManagementService bean used to retrieve the templates by key from the database.
* This class uses the {@link TemplateRepository} bean to laod the {@link Template} objects.
*/
@Component
@Slf4j
public class TemplateManagementServiceBean implements TemplateManagementService {
@@ -22,7 +26,7 @@ public class TemplateManagementServiceBean implements TemplateManagementService
@Override
public boolean templateExists(String key) {
return getTemplateByKey(key) != null;
return repository.existsById(key);
}
@Override

View File

@@ -1,6 +1,19 @@
package dev.sheldan.abstracto.templating;
/**
* An interface to be used on objects, which should be able to be processable by the template engine.
* This contains a template key and the model which is used when rendering this template.
*/
public interface Templatable {
/**
* The template key to be used to render this object.
* @return The template key as string
*/
String getTemplateName();
/**
* The model to be used to render this template.
* @return The model containing the attributes to be used for rendering.
*/
Object getTemplateModel();
}

View File

@@ -7,10 +7,19 @@ import net.dv8tion.jda.api.entities.MessageEmbed;
import java.util.List;
/**
* A full message which is ready to be send. This message can contain an arbitrary amount of embeds and a string message.
*/
@Getter
@Setter
@Builder
public class MessageToSend {
/**
* The collections of embeds to be send. The first embed is in the same message as the string message.
*/
private List<MessageEmbed> embeds;
/**
* The string content to be used in the first message.
*/
private String message;
}

View File

@@ -10,6 +10,9 @@ import javax.persistence.*;
import java.time.Instant;
import java.util.Objects;
/**
* Represents the template stored in the database.
*/
@Builder
@Entity
@NoArgsConstructor
@@ -19,10 +22,16 @@ import java.util.Objects;
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Template {
/**
* The globally unique key of the template
*/
@Id
@Getter
private String key;
/**
* The content of the template
*/
@Getter
@Column(length = 4000)
private String content;

View File

@@ -4,8 +4,32 @@ import dev.sheldan.abstracto.templating.model.MessageToSend;
import java.util.HashMap;
/**
* Provides methods to render templates with the appropriate model.
*/
public interface TemplateService {
/**
* The template containing a embed definition which should be rendered. The key must refer to an existing template and the supplied model will be used when rendering.
* This creates a {@link MessageToSend} object containing the rendered template and might result in multiple embeds.
* @param key The key of the embed template to be used for rendering.
* @param model The model providing the properties to be used for rendering
* @return A fully rendered message containing the content of the template and might contain multiple embeds.
*/
MessageToSend renderEmbedTemplate(String key, Object model);
/**
* Renders the template identified by the key with the given {@link HashMap} used as model and returns the value as a string
* @param key The key of the template to be rendered.
* @param parameters The {@link HashMap} to be used as the parameters for the template
* @return The template rendered as string.
*/
String renderTemplateWithMap(String key, HashMap<String, Object> parameters);
/**
* Renders the template identified by the key with the given model and returns the value as a string
* @param key The key of the template to be rendered
* @param model The object containing the model to be used in the template
* @return The template rendered as string.
*/
String renderTemplate(String key, Object model);
}

View File

@@ -2,8 +2,29 @@ package dev.sheldan.abstracto.templating.service.management;
import dev.sheldan.abstracto.templating.model.database.Template;
/**
* Provides methods to access the stored templates.
*/
public interface TemplateManagementService {
/**
* Retrieves the template identified by the key.
* @param key They template key to search for
* @return The {@link Template} identified by the key, if it exists.
*/
Template getTemplateByKey(String key);
/**
* Checks whether or not the template exists in the database.
* @param key They key of the template to search for
* @return true, if the template exists and false otherwise
*/
boolean templateExists(String key);
/**
* Creates a template identified by the key and with the provided content.
* @param key They key of the template to create.
* @param content The content the template should have
* @return The created template in the database.
*/
Template createTemplate(String key, String content);
}