Skip to content

Instantly share code, notes, and snippets.

@bibarsov
Created January 23, 2020 11:21
Show Gist options
  • Save bibarsov/031f87df64d9058f2d75207fcb7e152f to your computer and use it in GitHub Desktop.
Save bibarsov/031f87df64d9058f2d75207fcb7e152f to your computer and use it in GitHub Desktop.
JTwig support for @path aliases
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jtwig.environment.EnvironmentConfiguration;
import org.jtwig.environment.EnvironmentConfigurationBuilder;
import org.jtwig.escape.config.DefaultEscapeEngineConfiguration;
import org.jtwig.functions.config.DefaultJtwigFunctionList;
import org.jtwig.parser.config.DefaultJtwigParserConfiguration;
import org.jtwig.property.configuration.DefaultPropertyResolverConfiguration;
import org.jtwig.render.config.DefaultRenderConfiguration;
import org.jtwig.render.expression.calculator.enumerated.config.DefaultEnumerationListStrategyList;
import org.jtwig.resource.config.DefaultResourceConfiguration;
import org.jtwig.resource.config.ResourceConfiguration;
import org.jtwig.resource.exceptions.ResourceException;
import org.jtwig.resource.loader.ClasspathResourceLoader;
import org.jtwig.resource.loader.FileResourceLoader;
import org.jtwig.resource.loader.StringResourceLoader;
import org.jtwig.resource.loader.TypedResourceLoader;
import org.jtwig.resource.reference.DefaultResourceReferenceExtractor;
import org.jtwig.resource.reference.PosixResourceReferenceExtractor;
import org.jtwig.resource.reference.ResourceReference;
import org.jtwig.resource.reference.UncResourceReferenceExtractor;
import org.jtwig.resource.reference.path.PathTypeSupplier;
import org.jtwig.resource.resolver.ReferenceRelativeResourceResolver;
import org.jtwig.resource.resolver.RelativeResourceResolver;
import org.jtwig.resource.resolver.path.RelativeFilePathResolver;
import org.jtwig.resource.resolver.path.RelativePathResolver;
import org.jtwig.spring.JtwigViewResolver;
import org.jtwig.spring.boot.config.JtwigViewResolverConfigurer;
import org.jtwig.value.config.DefaultValueConfiguration;
import org.jtwig.web.servlet.JtwigRenderer;
import org.springframework.context.annotation.Configuration;
//import *****.annotations.NonNull;
//import *****.annotations.Nullable;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.file.InvalidPathException;
import java.util.Collections;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.Collections.singleton;
@Configuration
public class CustomJtwigViewResolverConfigurer implements JtwigViewResolverConfigurer {
private static final Log LOG = LogFactory.getLog(CustomJtwigViewResolverConfigurer.class);
public static class JtwigIncludePaths {
@Nullable
private Map<String, String> includes;
@Nullable
public Map<String, String> getIncludes() {
return includes;
}
public void setIncludes(@Nullable Map<String, String> includes) {
this.includes = includes;
}
}
private static final String INCLUDE_ALIAS_PREFIX = "@";
private static final Pattern INCLUDE_ALIAS_PATH_PATTERN = Pattern.compile(INCLUDE_ALIAS_PREFIX + "([^/]*)/?(.*)");
private final Map<String, String> jtwigIncludeAliasToPathMap;
public CustomJtwigViewResolverConfigurer(JtwigIncludePaths jtwigIncludeAliasToPathMap) {
if (jtwigIncludeAliasToPathMap.getIncludes() == null) {
LOG.warn("No jtwig include paths mapping provided");
this.jtwigIncludeAliasToPathMap = Collections.emptyMap();
} else {
this.jtwigIncludeAliasToPathMap = ImmutableMap.copyOf(jtwigIncludeAliasToPathMap.getIncludes());
}
}
/**
* Overriding relative resolver's behaviour in order to support "@path" like aliases
*
* @param viewResolver given by jtwig-spring
* @see org.jtwig.resource.config.DefaultResourceConfiguration#DefaultResourceConfiguration() original configuration
*/
@Override
public void configure(JtwigViewResolver viewResolver) {
RelativeResourceResolver customRelativeResourceResolver = (parentReference, newPath) -> {
if (jtwigIncludeAliasToPathMap.isEmpty()) {
return Optional.absent();
}
String path = newPath.getPath();
if (path.startsWith(INCLUDE_ALIAS_PREFIX)) {
Matcher matcher = INCLUDE_ALIAS_PATH_PATTERN.matcher(path);
if (matcher.matches()) {
String includeAliasName = checkNotNull(
matcher.group(1),
"Failed to extract alias name from: " + path
);
String includePathRest = checkNotNull(
matcher.group(2),
"Failed to extract alias path from: " + path
);
String includeAliasReplacement = jtwigIncludeAliasToPathMap.get(includeAliasName);
//noinspection Guava
return Optional.of(new ResourceReference(
parentReference.getType(),//keep the same type, ok
resolvePath(
includeAliasReplacement,
includePathRest
)
));
} else {
throw new IllegalArgumentException("Couldn't resolve jtwig path for: " + path);
}
}
return Optional.absent();
};
//<copypaste> from org.jtwig.resource.config.DefaultResourceConfiguration>
EnvironmentConfiguration environmentConfiguration = new EnvironmentConfiguration(
new ResourceConfiguration(
ImmutableList.<RelativeResourceResolver>builder()
.add(customRelativeResourceResolver)//customized behaviour
.add(new ReferenceRelativeResourceResolver(singleton(ResourceReference.CLASSPATH), RelativePathResolver.instance()))
.add(new ReferenceRelativeResourceResolver(singleton(ResourceReference.FILE), RelativeFilePathResolver.instance()))
.build(),
ImmutableList.of(ResourceReference.STRING, ResourceReference.MEMORY),
ImmutableList.of(
new TypedResourceLoader(ResourceReference.FILE, FileResourceLoader.instance()),
new TypedResourceLoader(ResourceReference.CLASSPATH, new ClasspathResourceLoader(DefaultResourceConfiguration.class.getClassLoader())),
new TypedResourceLoader(ResourceReference.STRING, StringResourceLoader.instance())
),
new DefaultResourceReferenceExtractor(
PathTypeSupplier.pathTypeSupplier(),
new PosixResourceReferenceExtractor(),
new UncResourceReferenceExtractor()
),
Charset.defaultCharset()
),
new DefaultEnumerationListStrategyList(),
new DefaultJtwigParserConfiguration(),
new DefaultValueConfiguration(),
new DefaultRenderConfiguration(),
new DefaultEscapeEngineConfiguration(),
new DefaultPropertyResolverConfiguration(),
new DefaultJtwigFunctionList(),
Collections.emptyMap(),
Collections.emptyList(),
Collections.emptyList()
);
//</copypaste> from org.jtwig.resource.config.DefaultResourceConfiguration
viewResolver.setRenderer(new JtwigRenderer(new EnvironmentConfigurationBuilder(environmentConfiguration).build()));
}
@NonNull
private static String resolvePath(@NonNull String parent, @NonNull String child) {
try {
return new URI(String.format("%s/%s", parent, child)).normalize().toString();
} catch (InvalidPathException | URISyntaxException e) {
throw new ResourceException("Invalid path", e);
}
}
}
@bibarsov
Copy link
Author

Add bean declaration for properties.

    @Bean
    @ConfigurationProperties(prefix = "jtwig")
    public CustomJtwigViewResolverConfigurer.JtwigIncludePaths jtwigIncludePaths(){
        return new CustomJtwigViewResolverConfigurer.JtwigIncludePaths();
    }

Property should look like:
jtwig.includes.content=/templates/subpath/to/content

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment