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}