Remove web concerns from template package and move out of -all

Co-authored-by: Phillip Webb <phil.webb@broadcom.com>
This commit is contained in:
Andy Wilkinson
2025-03-14 16:10:56 +00:00
committed by Phillip Webb
parent 4cdfa2eecc
commit 57006ee82e
14 changed files with 556 additions and 333 deletions

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.template;
import java.util.List;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.ClassUtils;
/**
* Abstract base class for {@link TemplateAvailabilityProvider} implementations that find
* templates from paths.
*
* @author Andy Wilkinson
* @author Phillip Webb
* @author Madhura Bhave
* @since 1.4.6
*/
public abstract class PathBasedTemplateAvailabilityProvider implements TemplateAvailabilityProvider {
private final String className;
private final Class<TemplateAvailabilityProperties> propertiesClass;
private final String propertyPrefix;
@SuppressWarnings("unchecked")
public PathBasedTemplateAvailabilityProvider(String className,
Class<? extends TemplateAvailabilityProperties> propertiesClass, String propertyPrefix) {
this.className = className;
this.propertiesClass = (Class<TemplateAvailabilityProperties>) propertiesClass;
this.propertyPrefix = propertyPrefix;
}
@Override
public boolean isTemplateAvailable(String view, Environment environment, ClassLoader classLoader,
ResourceLoader resourceLoader) {
if (ClassUtils.isPresent(this.className, classLoader)) {
Binder binder = Binder.get(environment);
TemplateAvailabilityProperties properties = binder.bindOrCreate(this.propertyPrefix, this.propertiesClass);
return isTemplateAvailable(view, resourceLoader, properties);
}
return false;
}
private boolean isTemplateAvailable(String view, ResourceLoader resourceLoader,
TemplateAvailabilityProperties properties) {
String location = properties.getPrefix() + view + properties.getSuffix();
for (String path : properties.getLoaderPath()) {
if (resourceLoader.getResource(path + location).exists()) {
return true;
}
}
return false;
}
protected abstract static class TemplateAvailabilityProperties {
private String prefix;
private String suffix;
protected TemplateAvailabilityProperties(String prefix, String suffix) {
this.prefix = prefix;
this.suffix = suffix;
}
protected abstract List<String> getLoaderPath();
public String getPrefix() {
return this.prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getSuffix() {
return this.suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.template;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
/**
* Indicates the availability of view templates for a particular templating engine such as
* FreeMarker or Thymeleaf.
*
* @author Andy Wilkinson
* @since 1.1.0
*/
@FunctionalInterface
public interface TemplateAvailabilityProvider {
/**
* Returns {@code true} if a template is available for the given {@code view}.
* @param view the view name
* @param environment the environment
* @param classLoader the class loader
* @param resourceLoader the resource loader
* @return if the template is available
*/
boolean isTemplateAvailable(String view, Environment environment, ClassLoader classLoader,
ResourceLoader resourceLoader);
}

View File

@@ -0,0 +1,166 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.template;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.Assert;
/**
* Collection of {@link TemplateAvailabilityProvider} beans that can be used to check
* which (if any) templating engine supports a given view. Caches responses unless the
* {@code spring.template.provider.cache} property is set to {@code false}.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 1.4.0
*/
public class TemplateAvailabilityProviders {
private final List<TemplateAvailabilityProvider> providers;
private static final int CACHE_LIMIT = 1024;
private static final TemplateAvailabilityProvider NONE = new NoTemplateAvailabilityProvider();
/**
* Resolved template views, returning already cached instances without a global lock.
*/
private final Map<String, TemplateAvailabilityProvider> resolved = new ConcurrentHashMap<>(CACHE_LIMIT);
/**
* Map from view name resolve template view, synchronized when accessed.
*/
private final Map<String, TemplateAvailabilityProvider> cache = new LinkedHashMap<>(CACHE_LIMIT, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<String, TemplateAvailabilityProvider> eldest) {
if (size() > CACHE_LIMIT) {
TemplateAvailabilityProviders.this.resolved.remove(eldest.getKey());
return true;
}
return false;
}
};
/**
* Create a new {@link TemplateAvailabilityProviders} instance.
* @param applicationContext the source application context
*/
public TemplateAvailabilityProviders(ApplicationContext applicationContext) {
this((applicationContext != null) ? applicationContext.getClassLoader() : null);
}
/**
* Create a new {@link TemplateAvailabilityProviders} instance.
* @param classLoader the source class loader
*/
public TemplateAvailabilityProviders(ClassLoader classLoader) {
Assert.notNull(classLoader, "'classLoader' must not be null");
this.providers = SpringFactoriesLoader.loadFactories(TemplateAvailabilityProvider.class, classLoader);
}
/**
* Create a new {@link TemplateAvailabilityProviders} instance.
* @param providers the underlying providers
*/
protected TemplateAvailabilityProviders(Collection<? extends TemplateAvailabilityProvider> providers) {
Assert.notNull(providers, "'providers' must not be null");
this.providers = new ArrayList<>(providers);
}
/**
* Return the underlying providers being used.
* @return the providers being used
*/
public List<TemplateAvailabilityProvider> getProviders() {
return this.providers;
}
/**
* Get the provider that can be used to render the given view.
* @param view the view to render
* @param applicationContext the application context
* @return a {@link TemplateAvailabilityProvider} or null
*/
public TemplateAvailabilityProvider getProvider(String view, ApplicationContext applicationContext) {
Assert.notNull(applicationContext, "'applicationContext' must not be null");
return getProvider(view, applicationContext.getEnvironment(), applicationContext.getClassLoader(),
applicationContext);
}
/**
* Get the provider that can be used to render the given view.
* @param view the view to render
* @param environment the environment
* @param classLoader the class loader
* @param resourceLoader the resource loader
* @return a {@link TemplateAvailabilityProvider} or null
*/
public TemplateAvailabilityProvider getProvider(String view, Environment environment, ClassLoader classLoader,
ResourceLoader resourceLoader) {
Assert.notNull(view, "'view' must not be null");
Assert.notNull(environment, "'environment' must not be null");
Assert.notNull(classLoader, "'classLoader' must not be null");
Assert.notNull(resourceLoader, "'resourceLoader' must not be null");
Boolean useCache = environment.getProperty("spring.template.provider.cache", Boolean.class, true);
if (!useCache) {
return findProvider(view, environment, classLoader, resourceLoader);
}
TemplateAvailabilityProvider provider = this.resolved.get(view);
if (provider == null) {
synchronized (this.cache) {
provider = findProvider(view, environment, classLoader, resourceLoader);
provider = (provider != null) ? provider : NONE;
this.resolved.put(view, provider);
this.cache.put(view, provider);
}
}
return (provider != NONE) ? provider : null;
}
private TemplateAvailabilityProvider findProvider(String view, Environment environment, ClassLoader classLoader,
ResourceLoader resourceLoader) {
for (TemplateAvailabilityProvider candidate : this.providers) {
if (candidate.isTemplateAvailable(view, environment, classLoader, resourceLoader)) {
return candidate;
}
}
return null;
}
private static final class NoTemplateAvailabilityProvider implements TemplateAvailabilityProvider {
@Override
public boolean isTemplateAvailable(String view, Environment environment, ClassLoader classLoader,
ResourceLoader resourceLoader) {
return false;
}
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.template;
import java.io.IOException;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.Assert;
/**
* Contains a location that templates can be loaded from.
*
* @author Phillip Webb
* @since 1.2.1
*/
public class TemplateLocation {
private final String path;
public TemplateLocation(String path) {
Assert.notNull(path, "'path' must not be null");
this.path = path;
}
/**
* Determine if this template location exists using the specified
* {@link ResourcePatternResolver}.
* @param resolver the resolver used to test if the location exists
* @return {@code true} if the location exists.
*/
public boolean exists(ResourcePatternResolver resolver) {
Assert.notNull(resolver, "'resolver' must not be null");
if (resolver.getResource(this.path).exists()) {
return true;
}
try {
return anyExists(resolver);
}
catch (IOException ex) {
return false;
}
}
private boolean anyExists(ResourcePatternResolver resolver) throws IOException {
String searchPath = this.path;
if (searchPath.startsWith(ResourceLoader.CLASSPATH_URL_PREFIX)) {
searchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ searchPath.substring(ResourceLoader.CLASSPATH_URL_PREFIX.length());
}
if (searchPath.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX)) {
Resource[] resources = resolver.getResources(searchPath);
for (Resource resource : resources) {
if (resource.exists()) {
return true;
}
}
}
return false;
}
@Override
public String toString() {
return this.path;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.template;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
/**
* {@link RuntimeHintsRegistrar} for default template location.
*
* @author Stephane Nicoll
*/
class TemplateRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.resources().registerPatternIfPresent(classLoader, "templates", (hint) -> hint.includes("templates/**"));
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Base classes for template Auto-configuration.
*/
package org.springframework.boot.autoconfigure.template;

View File

@@ -3,3 +3,6 @@ org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingP
org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer
org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.boot.autoconfigure.template.TemplateRuntimeHints