--- /dev/null
+/target/
\ No newline at end of file
--- /dev/null
+<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/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-parent</artifactId>
+ <version>1.3.5.RELEASE</version>
+ </parent>
+
+ <groupId>de.juplo</groupId>
+ <artifactId>thymeproxy</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <properties>
+
+ <!-- settings for Spring-Boot -->
+ <java.version>1.8</java.version>
+
+ <!-- other usefull settings -->
+ <encoding>UTF-8</encoding>
+ <maven.compiler.source>1.8</maven.compiler.source>
+ <maven.compiler.target>1.8</maven.compiler.target>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+
+ <!-- application-settings -->
+ <thymeproxy.name>${project.name}</thymeproxy.name>
+ <thymeproxy.origin>http://localhost:8080</thymeproxy.origin>
+ <thymeproxy.port>80</thymeproxy.port>
+ <thymeproxy.ttl>300000</thymeproxy.ttl><!-- 5 minutes -->
+
+ <!-- used versions (not defined in spring-boot) -->
+ <httpclient-spring-boot-starter.version>1.0-SNAPSHOT</httpclient-spring-boot-starter.version>
+
+ </properties>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-thymeleaf</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>de.juplo</groupId>
+ <artifactId>httpclient-spring-boot-starter</artifactId>
+ <version>${httpclient-spring-boot-starter.version}</version>
+ </dependency>
+
+ <!-- Httpclient -->
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ </dependency>
+
+ <!-- Needed to parse HTML5 with Thymeleaf -->
+ <dependency>
+ <groupId>net.sourceforge.nekohtml</groupId>
+ <artifactId>nekohtml</artifactId>
+ <version>${nekohtml.version}</version>
+ </dependency>
+
+ <!-- Testing -->
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-configuration-processor</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>jcl-over-slf4j</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ <!-- Needed, because otherwise, filtering of maven properties won't work -->
+ <configuration>
+ <addResources>false</addResources>
+ </configuration>
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>springloaded</artifactId>
+ <version>1.2.4.RELEASE</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+package de.juplo.autoconfigure;
+
+
+import de.juplo.autoconfigure.ThymeproxyProperties.Origin;
+import de.juplo.thymeproxy.ProxyTemplateResolver;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+
+/**
+ * Automatic configuration
+ *
+ * @author Kai Moritz
+ */
+@Configuration
+@EnableConfigurationProperties(ThymeproxyProperties.class)
+@ConditionalOnClass(value = ThymeleafAutoConfiguration.class)
+@AutoConfigureBefore(ThymeleafAutoConfiguration.class)
+public class ThymeproxyAutoConfiguration
+{
+ private static final Logger LOG =
+ LoggerFactory.getLogger(ThymeproxyAutoConfiguration.class);
+
+
+ @Bean
+ @ConditionalOnProperty("thymeproxy.origins[0].uri")
+ public ProxyTemplateResolver defaultTemplateResolver(
+ CloseableHttpClient client,
+ ThymeproxyProperties properties,
+ ConfigurableApplicationContext context
+ )
+ {
+ LOG.info("configuring {} proxies", properties.origins.size());
+
+ Origin origin = properties.origins.get(0);
+ String uri = origin.uri.toString();
+ ProxyTemplateResolver defaultResolver =
+ new ProxyTemplateResolver(
+ "0: " + origin.uri.getHost(),
+ 0,
+ client,
+ uri,
+ origin.ttl
+ );
+ LOG.info("registering defaultTemplateResolver for {}", uri);
+
+ for (int i=1; i<properties.origins.size(); i++)
+ {
+ origin = properties.origins.get(i);
+
+ String name = "proxy" + i;
+ uri = origin.uri.toString();
+ ProxyTemplateResolver resolver =
+ new ProxyTemplateResolver(
+ i + ": " + origin.uri.getHost(),
+ i,
+ client,
+ origin.uri.toString(),
+ origin.ttl
+ );
+
+ LOG.info("registering {} for {}", name, uri);
+ context.getBeanFactory().registerSingleton(name, resolver);
+ }
+
+ return defaultResolver;
+ }
+}
--- /dev/null
+package de.juplo.autoconfigure;
+
+
+import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+import org.hibernate.validator.constraints.NotEmpty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+
+
+/**
+ *
+ * @author Kai Moritz
+ */
+@ConfigurationProperties("thymeproxy")
+public class ThymeproxyProperties
+{
+ String name;
+ List<Origin> origins = new LinkedList<>();
+
+
+ public void setName(String name)
+ {
+ this.name = name;
+ }
+
+ public List<Origin> getOrigins()
+ {
+ return origins;
+ }
+
+ public void setOrigins(List<Origin> origins)
+ {
+ this.origins = origins;
+ }
+
+
+ public static class Origin
+ {
+ @NotEmpty
+ URI uri;
+ List<String> patterns = new LinkedList<>();
+ Long ttl;
+
+
+ public void setUri(URI uri)
+ {
+ this.uri = uri;
+ }
+
+ public List<String> getPatterns()
+ {
+ return this.patterns;
+ }
+
+ public void setPatterns(List<String> patterns)
+ {
+ this.patterns = patterns;
+ }
+
+ public void setTtl(Long ttl)
+ {
+ this.ttl = ttl;
+ }
+ }
+}
--- /dev/null
+package de.juplo.thymeproxy;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import org.apache.http.HttpEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.thymeleaf.TemplateProcessingParameters;
+import org.thymeleaf.resourceresolver.IResourceResolver;
+
+
+
+/**
+ *
+ * @author kai
+ */
+public class ProxyResourceResolver implements IResourceResolver
+{
+ private final static Logger LOG =
+ LoggerFactory.getLogger(ProxyResourceResolver.class);
+
+
+ private final String resource;
+
+ private final CloseableHttpResponse response;
+ private final HttpEntity entity;
+
+
+ public ProxyResourceResolver(
+ String resource,
+ CloseableHttpResponse response,
+ HttpEntity entity
+ )
+ {
+ this.resource = resource;
+ this.response = response;
+ this.entity = entity;
+ }
+
+
+ @Override
+ public String getName()
+ {
+ return resource;
+ }
+
+ @Override
+ public InputStream getResourceAsStream(TemplateProcessingParameters templateProcessingParameters, String resourceName)
+ {
+ InputStream is;
+ try
+ {
+ is = entity.getContent();
+ }
+ catch (IOException e)
+ {
+ LOG.error("unexpected error while retriving the response-body", e);
+ return null;
+ }
+
+ return new InputStream()
+ {
+ @Override
+ public boolean markSupported()
+ {
+ return is.markSupported();
+ }
+
+ @Override
+ public synchronized void reset() throws IOException
+ {
+ is.reset();
+ }
+
+ @Override
+ public synchronized void mark(int readlimit)
+ {
+ is.mark(readlimit);
+ }
+
+ @Override
+ public void close() throws IOException
+ {
+ is.close();
+ EntityUtils.consume(entity);
+ response.close();
+ }
+
+ @Override
+ public int available() throws IOException
+ {
+ return is.available();
+ }
+
+ @Override
+ public long skip(long n) throws IOException
+ {
+ return is.skip(n);
+ }
+
+ @Override
+ public int read() throws IOException
+ {
+ return is.read();
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException
+ {
+ return is.read(b, off, len);
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException
+ {
+ return is.read(b);
+ }
+ };
+ }
+}
--- /dev/null
+package de.juplo.thymeproxy;
+
+
+import java.io.IOException;
+import java.time.Clock;
+import java.time.Duration;
+import java.time.OffsetDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.regex.Pattern;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.http.Header;
+import org.apache.http.HeaderElement;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.entity.ContentType;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.thymeleaf.TemplateProcessingParameters;
+import org.thymeleaf.templatemode.StandardTemplateModeHandlers;
+import org.thymeleaf.templateresolver.ITemplateResolutionValidity;
+import org.thymeleaf.templateresolver.ITemplateResolver;
+import org.thymeleaf.templateresolver.NonCacheableTemplateResolutionValidity;
+import org.thymeleaf.templateresolver.TTLTemplateResolutionValidity;
+import org.thymeleaf.templateresolver.TemplateResolution;
+
+
+
+/**
+ *
+ * @author Kai Moritz
+ */
+public class ProxyTemplateResolver implements ITemplateResolver
+{
+ private final static Logger LOG =
+ LoggerFactory.getLogger(ProxyTemplateResolver.class);
+
+ private final static Pattern HTML =
+ Pattern.compile("text/html", Pattern.CASE_INSENSITIVE);
+ private final static Pattern XHTML =
+ Pattern.compile("application/xhtml+xml", Pattern.CASE_INSENSITIVE);
+ private final static Pattern XML =
+ Pattern.compile("(?:/|\\+)xml$", Pattern.CASE_INSENSITIVE);
+
+ public final static Long DEFAULT_TTL = 300000l; /** 5 minutes */
+
+ private final String name;
+ private final Integer order;
+
+ private final CloseableHttpClient client;
+ private final String origin;
+ private final Long ttl;
+ private final Clock clock;
+
+
+ public ProxyTemplateResolver(
+ String name,
+ Integer order,
+ CloseableHttpClient client,
+ String origin,
+ Long ttl,
+ Clock clock
+ )
+ {
+ super();
+ this.name = name;
+ this.order = order;
+ this.client = client;
+ this.origin = origin;
+ this.ttl = ttl;
+ this.clock = clock;
+ }
+
+ public ProxyTemplateResolver(
+ String name,
+ Integer order,
+ CloseableHttpClient client,
+ String origin,
+ Long ttl
+ )
+ {
+ this(name, order, client, origin, ttl, Clock.systemDefaultZone());
+ }
+
+ public ProxyTemplateResolver(
+ String name,
+ Integer order,
+ CloseableHttpClient client,
+ String origin
+ )
+ {
+ this(name, order, client, origin, DEFAULT_TTL, Clock.systemDefaultZone());
+ }
+
+
+ @Override
+ public String getName()
+ {
+ return name;
+ }
+
+ @Override
+ public Integer getOrder()
+ {
+ return order;
+ }
+
+ public String getOrigin()
+ {
+ return origin;
+ }
+
+ public Long getDefaultTTL()
+ {
+ return ttl;
+ }
+
+ @Override
+ public TemplateResolution resolveTemplate(TemplateProcessingParameters params)
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append(origin);
+ builder.append(params.getTemplateName());
+
+ String resource = builder.toString();
+
+ try
+ {
+ HttpGet request = new HttpGet(resource);
+ CloseableHttpResponse response = client.execute(request);
+
+ switch (response.getStatusLine().getStatusCode())
+ {
+ case HttpServletResponse.SC_FOUND:
+ case HttpServletResponse.SC_OK:
+ /** OK. Continue as normal... */
+ break;
+
+ case HttpServletResponse.SC_NOT_FOUND:
+ case HttpServletResponse.SC_GONE:
+ /** The resource can not be resolved through this origin */
+ return null;
+
+ case HttpServletResponse.SC_MOVED_PERMANENTLY:
+ // TODO
+
+ case HttpServletResponse.SC_SEE_OTHER:
+ // TODO
+
+ case HttpServletResponse.SC_TEMPORARY_REDIRECT:
+ // TODO
+
+ default:
+ LOG.error("{} -- {}", response.getStatusLine(), resource);
+ // TODO: throw sensible exceptions, to communicate resolving-errors
+ throw new RuntimeException(response.getStatusLine().toString() + " -- " + resource);
+ }
+
+ HttpEntity entity = response.getEntity();
+ ContentType content = ContentType.getOrDefault(entity);
+
+ return new TemplateResolution(
+ params.getTemplateName(),
+ resource,
+ new ProxyResourceResolver(resource, response, entity),
+ content.getCharset().displayName(),
+ ProxyTemplateResolver.computeTemplateMode(content),
+ computeValidity(response)
+ );
+ }
+ catch (IOException e)
+ {
+ LOG.error("unexpected error while resolving {}: {}", resource, e.getMessage());
+ // TODO: throw sensible exceptions, to communicate resolving-errors
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void initialize()
+ {
+ }
+
+
+ public static String computeTemplateMode(ContentType content)
+ {
+ String type = content.getMimeType();
+
+ if (HTML.matcher(type).matches())
+ return StandardTemplateModeHandlers.LEGACYHTML5.getTemplateModeName();
+
+ if (XML.matcher(type).find())
+ {
+ if (XHTML.matcher(type).matches())
+ return StandardTemplateModeHandlers.XHTML.getTemplateModeName();
+ else
+ return StandardTemplateModeHandlers.XML.getTemplateModeName();
+ }
+
+ throw new RuntimeException("Cannot handle mime-type " + type);
+ }
+
+ public ITemplateResolutionValidity computeValidity(HttpResponse response)
+ {
+ if (ttl == null)
+ /** Caching is disabled! */
+ return NonCacheableTemplateResolutionValidity.INSTANCE;
+
+ boolean cacheable = true;
+ Integer max_age = null;
+
+ for (Header header : response.getHeaders("Cache-Control"))
+ {
+ for (HeaderElement element : header.getElements())
+ {
+ switch (element.getName())
+ {
+ case "no-cache":
+ case "no-store":
+ case "must-revalidate":
+ cacheable = false;
+ break;
+
+ case "max-age":
+ try
+ {
+ max_age = Integer.parseInt(element.getValue());
+ }
+ catch (NumberFormatException e)
+ {
+ LOG.warn(
+ "invalid header \"Cache-Control: max-age={}\"",
+ element.getValue()
+ );
+ }
+ break;
+ }
+ }
+ }
+
+ if (max_age != null && max_age < 1)
+ cacheable = false;
+
+ if (!cacheable)
+ return NonCacheableTemplateResolutionValidity.INSTANCE;
+
+ if (max_age != null)
+ {
+ long millis = max_age;
+ if (millis >= Long.MAX_VALUE / 1000l )
+ millis = Long.MAX_VALUE;
+ else
+ millis = millis * 1000l;
+ return new TTLTemplateResolutionValidity(millis);
+ }
+
+ Header header = response.getLastHeader("Expires");
+ if (header == null)
+ /** No TTL specified in response-headers: use configured default */
+ return new TTLTemplateResolutionValidity(ttl);
+
+ try
+ {
+ OffsetDateTime expires =
+ OffsetDateTime.parse(
+ header.getValue(),
+ DateTimeFormatter.RFC_1123_DATE_TIME
+ );
+
+ Duration delta = Duration.between(OffsetDateTime.now(clock), expires);
+ if (delta.isNegative() || delta.isZero())
+ return NonCacheableTemplateResolutionValidity.INSTANCE;
+
+ long millis = delta.getSeconds();
+ if (millis >= Long.MAX_VALUE / 1000l)
+ millis = Long.MAX_VALUE;
+ else
+ {
+ millis = millis * 1000;
+ millis = millis + (long)(delta.getNano() / 1000000);
+ }
+ return new TTLTemplateResolutionValidity(millis);
+ }
+ catch (DateTimeParseException e)
+ {
+ LOG.warn("invalid header \"Expires: {}\"", header.getValue());
+ /**
+ * No TTL specified in response-headers: assume expired
+ * (see: https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21)
+ */
+ return NonCacheableTemplateResolutionValidity.INSTANCE;
+ }
+ }
+}
--- /dev/null
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=de.juplo.autoconfiguration.ThymeproxyAutoConfiguration
--- /dev/null
+thymeproxy.name=@thymeproxy.name@
+thymeproxy.origin=@thymeproxy.origin@
+server.port=@thymeproxy.port@
+thymeproxy.ttl=@thymeproxy.ttl@
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+
+ <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%p - %C{1}.%M(%L) | %m%n"/>
+ </layout>
+ </appender>
+
+ <logger name="de.juplo">
+ <level value="trace"/>
+ </logger>
+
+ <logger name="org.springframework">
+ <level value="debug" />
+ </logger>
+ <logger name="org.thymeleaf">
+ <level value="debug" />
+ </logger>
+
+ <root>
+ <level value="info"/>
+ <appender-ref ref="CONSOLE"/>
+ </root>
+
+</log4j:configuration>
--- /dev/null
+package de.juplo.autoconfigure;
+
+
+import de.juplo.thymeproxy.ProxyTemplateResolver;
+import java.net.URI;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import org.junit.Test;
+import org.springframework.context.annotation.Configuration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer;
+import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration;
+import org.springframework.boot.test.EnvironmentTestUtils;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.thymeleaf.templateresolver.ITemplateResolver;
+import org.thymeleaf.templateresolver.TemplateResolver;
+
+
+
+public class ThymeproxyAutoConfigurationTest
+{
+ private final Logger LOG =
+ LoggerFactory.getLogger(ThymeproxyAutoConfigurationTest.class);
+
+
+ @Test
+ public void propertyBinding() throws Exception
+ {
+ LOG.info("<-- Start Of New Test-Case!");
+
+ ConfigurableApplicationContext context;
+ ThymeproxyProperties properties;
+
+ context = load(
+ EmptyConfiguration.class,
+ "thymeproxy.name=Thymeproxy",
+ "thymeproxy.origins[0].uri=http://localhost:8080/test/",
+ "thymeproxy.origins[0].patterns[0]=^/css/",
+ "thymeproxy.origins[0].patterns[1]=^/img/",
+ "thymeproxy.origins[0].patterns[2]=*\\.xml$",
+ "thymeproxy.origins[1].uri=http://127.0.0.1:8081",
+ "thymeproxy.origins[1].ttl=30000"
+ );
+ properties = context.getBean(ThymeproxyProperties.class);
+ assertNotNull(properties);
+ assertEquals("Thymeproxy", properties.name);
+ assertNotNull(properties.origins);
+ assertEquals(2, properties.origins.size());
+ assertEquals(new URI("http://localhost:8080/test/"), properties.origins.get(0).uri);
+ assertNotNull(properties.origins.get(0).patterns);
+ assertEquals(3, properties.origins.get(0).patterns.size());
+ assertEquals("^/css/", properties.origins.get(0).patterns.get(0));
+ assertEquals("^/img/", properties.origins.get(0).patterns.get(1));
+ assertEquals("*\\.xml$", properties.origins.get(0).patterns.get(2));
+ assertNull(properties.origins.get(0).ttl);
+ assertEquals(new URI("http://127.0.0.1:8081"), properties.origins.get(1).uri);
+ assertNotNull(properties.origins.get(1).patterns);
+ assertEquals(0, properties.origins.get(1).patterns.size());
+ assertEquals(new Long(30000l), properties.origins.get(1).ttl);
+ context.close();
+ }
+
+ @Test
+ public void defaultConfiguration()
+ {
+ LOG.info("<-- Start Of New Test-Case!");
+
+ ConfigurableApplicationContext context = load(EmptyConfiguration.class);
+
+ ITemplateResolver resolver =
+ (ITemplateResolver)context.getBean("defaultTemplateResolver");
+ assertNotNull(resolver);
+ assertTrue(
+ "Expected an instance of type TemplateResolver",
+ resolver instanceof TemplateResolver
+ );
+ assertNotEquals(DefaultTemplateResolverConfiguration.RESOLVER, resolver);
+
+ context.close();
+ }
+
+ @Test
+ public void defaultTemplateResolverConfigured()
+ {
+ LOG.info("<-- Start Of New Test-Case!");
+
+ ConfigurableApplicationContext context =
+ load(DefaultTemplateResolverConfiguration.class);
+
+ ITemplateResolver resolver =
+ (ITemplateResolver)context.getBean("defaultTemplateResolver");
+ assertNotNull(resolver);
+ assertTrue(
+ "Expected an instance of type TemplateResolver",
+ resolver instanceof TemplateResolver
+ );
+ assertEquals(DefaultTemplateResolverConfiguration.RESOLVER, resolver);
+
+ context.close();
+ }
+
+ @Test
+ public void proxiesConfigured()
+ {
+ LOG.info("<-- Start Of New Test-Case!");
+
+ ConfigurableApplicationContext context = load(
+ EmptyConfiguration.class,
+ "thymeproxy.name=Thymeproxy",
+ "thymeproxy.origins[0].uri=http://localhost:8080/test/",
+ "thymeproxy.origins[0].patterns[0]=^/css/",
+ "thymeproxy.origins[0].patterns[1]=^/img/",
+ "thymeproxy.origins[0].patterns[2]=*\\.xml$",
+ "thymeproxy.origins[1].uri=http://127.0.0.1:8081",
+ "thymeproxy.origins[1].ttl=30000"
+ );
+
+ ITemplateResolver resolver;
+ ProxyTemplateResolver proxy;
+
+ resolver = (ITemplateResolver)context.getBean("defaultTemplateResolver");
+ assertNotNull(resolver);
+ assertTrue(
+ "Expected an instance of type ProxyTemplateResolver",
+ resolver instanceof ProxyTemplateResolver
+ );
+ proxy = (ProxyTemplateResolver)resolver;
+ assertEquals("0: localhost", proxy.getName());
+ assertEquals(new Integer(0), proxy.getOrder());
+ assertEquals("http://localhost:8080/test/", proxy.getOrigin());
+ assertNull(proxy.getDefaultTTL());
+
+ resolver = (ITemplateResolver)context.getBean("proxy1");
+ assertNotNull(resolver);
+ assertTrue(
+ "Expected an instance of type ProxyTemplateResolver",
+ resolver instanceof ProxyTemplateResolver
+ );
+ proxy = (ProxyTemplateResolver)resolver;
+ assertEquals("1: 127.0.0.1", proxy.getName());
+ assertEquals(new Integer(1), proxy.getOrder());
+ assertEquals("http://127.0.0.1:8081", proxy.getOrigin());
+ assertEquals(new Long(30000l), proxy.getDefaultTTL());
+
+ context.close();
+ }
+
+ @Test
+ public void proxiesAndDefaultTemplateResolverConfigured()
+ {
+ LOG.info("<-- Start Of New Test-Case!");
+
+ ConfigurableApplicationContext context = load(
+ DefaultTemplateResolverConfiguration.class,
+ "thymeproxy.name=Thymeproxy",
+ "thymeproxy.origins[0].uri=http://localhost:8080/test/",
+ "thymeproxy.origins[0].patterns[0]=^/css/",
+ "thymeproxy.origins[0].patterns[1]=^/img/",
+ "thymeproxy.origins[0].patterns[2]=*\\.xml$",
+ "thymeproxy.origins[1].uri=http://127.0.0.1:8081",
+ "thymeproxy.origins[1].ttl=30000"
+ );
+
+ ITemplateResolver resolver;
+ ProxyTemplateResolver proxy;
+
+ resolver = (ITemplateResolver)context.getBean("defaultTemplateResolver");
+ assertNotNull(resolver);
+ assertTrue(
+ "Expected an instance of type TemplateResolver",
+ resolver instanceof TemplateResolver
+ );
+ assertEquals(DefaultTemplateResolverConfiguration.RESOLVER, resolver);
+
+ try
+ {
+ resolver = (ITemplateResolver)context.getBean("proxy1");
+ fail("Found bean for name proxy1: " + resolver);
+ }
+ catch (BeansException e)
+ {
+ LOG.info(e.toString());
+ }
+
+ context.close();
+ }
+
+
+ @Configuration
+ static class EmptyConfiguration
+ {
+ }
+
+ @Configuration
+ static class DefaultTemplateResolverConfiguration
+ {
+ public static TemplateResolver RESOLVER = new TemplateResolver();
+
+ @Bean
+ public TemplateResolver defaultTemplateResolver()
+ {
+ return RESOLVER;
+ }
+ }
+
+
+ private ConfigurableApplicationContext load(Class<?> config, String... pairs)
+ {
+ AnnotationConfigApplicationContext ctx =
+ new AnnotationConfigApplicationContext();
+ EnvironmentTestUtils.addEnvironment(ctx, pairs);
+ ctx.register(HttpClientAutoConfiguration.class);
+ ctx.register(ThymeleafAutoConfiguration.class);
+ ctx.register(ThymeproxyAutoConfiguration.class);
+ ctx.register(config);
+ AutoConfigurationReportLoggingInitializer report =
+ new AutoConfigurationReportLoggingInitializer();
+ report.initialize(ctx);
+ ctx.refresh();
+ return ctx;
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<configuration>
+
+ <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder>
+ <pattern>%p - %c{0}.%M\(%L\) | %m%n</pattern>
+ </encoder>
+ </appender>
+
+ <logger name="de.juplo.facebook">
+ <level value="trace"/>
+ </logger>
+
+ <logger name="org.springframework.boot.autoconfigure.logging">
+ <level value="debug"/>
+ </logger>
+
+ <root>
+ <level value="info"/>
+ <appender-ref ref="CONSOLE"/>
+ </root>
+
+</configuration>