001/* 002 * Copyright 2012-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.autoconfigure.template; 018 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.LinkedHashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.concurrent.ConcurrentHashMap; 025 026import org.springframework.context.ApplicationContext; 027import org.springframework.core.env.Environment; 028import org.springframework.core.io.ResourceLoader; 029import org.springframework.core.io.support.SpringFactoriesLoader; 030import org.springframework.util.Assert; 031 032/** 033 * Collection of {@link TemplateAvailabilityProvider} beans that can be used to check 034 * which (if any) templating engine supports a given view. Caches responses unless the 035 * {@code spring.template.provider.cache} property is set to {@code false}. 036 * 037 * @author Phillip Webb 038 * @author Madhura Bhave 039 * @since 1.4.0 040 */ 041public class TemplateAvailabilityProviders { 042 043 private final List<TemplateAvailabilityProvider> providers; 044 045 private static final int CACHE_LIMIT = 1024; 046 047 private static final TemplateAvailabilityProvider NONE = new NoTemplateAvailabilityProvider(); 048 049 /** 050 * Resolved template views, returning already cached instances without a global lock. 051 */ 052 private final Map<String, TemplateAvailabilityProvider> resolved = new ConcurrentHashMap<>( 053 CACHE_LIMIT); 054 055 /** 056 * Map from view name resolve template view, synchronized when accessed. 057 */ 058 @SuppressWarnings("serial") 059 private final Map<String, TemplateAvailabilityProvider> cache = new LinkedHashMap<String, TemplateAvailabilityProvider>( 060 CACHE_LIMIT, 0.75f, true) { 061 062 @Override 063 protected boolean removeEldestEntry( 064 Map.Entry<String, TemplateAvailabilityProvider> eldest) { 065 if (size() > CACHE_LIMIT) { 066 TemplateAvailabilityProviders.this.resolved.remove(eldest.getKey()); 067 return true; 068 } 069 return false; 070 } 071 072 }; 073 074 /** 075 * Create a new {@link TemplateAvailabilityProviders} instance. 076 * @param applicationContext the source application context 077 */ 078 public TemplateAvailabilityProviders(ApplicationContext applicationContext) { 079 this((applicationContext != null) ? applicationContext.getClassLoader() : null); 080 } 081 082 /** 083 * Create a new {@link TemplateAvailabilityProviders} instance. 084 * @param classLoader the source class loader 085 */ 086 public TemplateAvailabilityProviders(ClassLoader classLoader) { 087 Assert.notNull(classLoader, "ClassLoader must not be null"); 088 this.providers = SpringFactoriesLoader 089 .loadFactories(TemplateAvailabilityProvider.class, classLoader); 090 } 091 092 /** 093 * Create a new {@link TemplateAvailabilityProviders} instance. 094 * @param providers the underlying providers 095 */ 096 protected TemplateAvailabilityProviders( 097 Collection<? extends TemplateAvailabilityProvider> providers) { 098 Assert.notNull(providers, "Providers must not be null"); 099 this.providers = new ArrayList<>(providers); 100 } 101 102 /** 103 * Return the underlying providers being used. 104 * @return the providers being used 105 */ 106 public List<TemplateAvailabilityProvider> getProviders() { 107 return this.providers; 108 } 109 110 /** 111 * Get the provider that can be used to render the given view. 112 * @param view the view to render 113 * @param applicationContext the application context 114 * @return a {@link TemplateAvailabilityProvider} or null 115 */ 116 public TemplateAvailabilityProvider getProvider(String view, 117 ApplicationContext applicationContext) { 118 Assert.notNull(applicationContext, "ApplicationContext must not be null"); 119 return getProvider(view, applicationContext.getEnvironment(), 120 applicationContext.getClassLoader(), applicationContext); 121 } 122 123 /** 124 * Get the provider that can be used to render the given view. 125 * @param view the view to render 126 * @param environment the environment 127 * @param classLoader the class loader 128 * @param resourceLoader the resource loader 129 * @return a {@link TemplateAvailabilityProvider} or null 130 */ 131 public TemplateAvailabilityProvider getProvider(String view, Environment environment, 132 ClassLoader classLoader, ResourceLoader resourceLoader) { 133 Assert.notNull(view, "View must not be null"); 134 Assert.notNull(environment, "Environment must not be null"); 135 Assert.notNull(classLoader, "ClassLoader must not be null"); 136 Assert.notNull(resourceLoader, "ResourceLoader must not be null"); 137 Boolean useCache = environment.getProperty("spring.template.provider.cache", 138 Boolean.class, true); 139 if (!useCache) { 140 return findProvider(view, environment, classLoader, resourceLoader); 141 } 142 TemplateAvailabilityProvider provider = this.resolved.get(view); 143 if (provider == null) { 144 synchronized (this.cache) { 145 provider = findProvider(view, environment, classLoader, resourceLoader); 146 provider = (provider != null) ? provider : NONE; 147 this.resolved.put(view, provider); 148 this.cache.put(view, provider); 149 } 150 } 151 return (provider != NONE) ? provider : null; 152 } 153 154 private TemplateAvailabilityProvider findProvider(String view, 155 Environment environment, ClassLoader classLoader, 156 ResourceLoader resourceLoader) { 157 for (TemplateAvailabilityProvider candidate : this.providers) { 158 if (candidate.isTemplateAvailable(view, environment, classLoader, 159 resourceLoader)) { 160 return candidate; 161 } 162 } 163 return null; 164 } 165 166 private static class NoTemplateAvailabilityProvider 167 implements TemplateAvailabilityProvider { 168 169 @Override 170 public boolean isTemplateAvailable(String view, Environment environment, 171 ClassLoader classLoader, ResourceLoader resourceLoader) { 172 return false; 173 } 174 175 } 176 177}