/* ================================================================== * PatternMatchingSetupResourceProvider.java - 23/09/2016 9:27:09 AM * * Copyright 2007-2016 SolarNetwork.net Dev Team * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA * ================================================================== */ package net.solarnetwork.node.setup; import static net.solarnetwork.node.setup.SetupResourceUtils.baseFilenameForPath; import static net.solarnetwork.node.setup.SetupResourceUtils.localeScore; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.io.Resource; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.util.StringUtils; /** * Resolve resources based on pattern matching a set of base names. * * This can be easier to configure a set of localized resources than using * {@link SimpleSetupResourceProvider}. * * @author matt * @version 1.1 */ public class PatternMatchingSetupResourceProvider implements SetupResourceProvider, ApplicationContextAware { /** * The content type assigned to resolved resources that are of an unknown * type. */ public static final String UNKNOWN_CONTENT_TYPE = "application/octet-stream"; private Locale defaultLocale = Locale.US; private Set<String> consumerTypes = SetupResource.WEB_CONSUMER_TYPES; private Set<String> roles = SetupResource.USER_ROLES; private SetupResourceScope scope = SetupResourceScope.Default; private String[] basenames; private ResourcePatternResolver resourcePatternResolver; private int cacheSeconds = 86400; private Map<String, String> fileExtensionContentTypeMapping = net.solarnetwork.node.setup.SetupResourceUtils.DEFAULT_FILENAME_EXTENSION_CONTENT_TYPES; private static final Logger LOG = LoggerFactory .getLogger(PatternMatchingSetupResourceProvider.class); @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { if ( resourcePatternResolver == null ) { resourcePatternResolver = applicationContext; } } @Override public SetupResource getSetupResource(String resourceUID, Locale locale) { int bestScore = -1; SetupResource bestMatch = null; for ( String basename : basenames ) { List<SetupResource> resources = resolveSetupResourcesForBasename(basename); for ( SetupResource rsrc : resources ) { if ( resourceUID.equals(rsrc.getResourceUID()) ) { int score = localeScore(rsrc, locale, defaultLocale); if ( score == Integer.MAX_VALUE ) { return rsrc; } if ( bestMatch == null || score > bestScore ) { bestScore = score; bestMatch = rsrc; } } } } return bestMatch; } @Override public Collection<SetupResource> getSetupResourcesForConsumer(String consumerType, Locale locale) { if ( !consumerType.equals(consumerType) ) { return Collections.emptyList(); } List<SetupResource> results = new ArrayList<SetupResource>(basenames.length); for ( String basename : basenames ) { Map<String, SetupResource> bestMatches = new HashMap<String, SetupResource>(); List<SetupResource> resources = resolveSetupResourcesForBasename(basename); for ( SetupResource rsrc : resources ) { Set<String> supported = rsrc.getSupportedConsumerTypes(); if ( supported == null || supported.contains(consumerType) ) { SetupResource currMatch = bestMatches.get(rsrc.getResourceUID()); if ( localeScore(currMatch, locale, defaultLocale) < localeScore(rsrc, locale, defaultLocale) ) { bestMatches.put(rsrc.getResourceUID(), rsrc); } } } results.addAll(bestMatches.values()); } return results; } private List<SetupResource> resolveSetupResourcesForBasename(String basename) { final String pattern = basename + "*.*"; List<SetupResource> result = null; try { Resource[] matches = resourcePatternResolver.getResources(pattern); if ( matches != null ) { for ( Resource r : matches ) { if ( result == null ) { result = new ArrayList<SetupResource>(8); } String filename = r.getFilename(); String contentType = fileExtensionContentTypeMapping .get(StringUtils.getFilenameExtension(filename)); if ( contentType == null ) { contentType = UNKNOWN_CONTENT_TYPE; } result.add(new ResourceSetupResource(r, baseFilenameForPath(filename), contentType, cacheSeconds, consumerTypes, roles, scope)); } } } catch ( IOException e ) { LOG.error("Error resolving basename [{}]: {}", e); } if ( result == null ) { result = Collections.emptyList(); } return result; } /** * Set the consumer types assigned to all resolved resources. Defaults to * {@link SetupResource#WEB_CONSUMER_TYPES}. * * @param consumerTypes * The consumer types. */ public void setConsumerTypes(Set<String> consumerTypes) { this.consumerTypes = consumerTypes; } /** * Set the base names supported by this factory. * * @param basenames * The list of base names (file paths without extensions) to use. */ public void setBasenames(String[] basenames) { this.basenames = basenames; } /** * A pattern resolver to search for resources with. * * @param resourcePatternResolver * The pattern resolver to use. */ public void setResourcePatternResolver(ResourcePatternResolver resourcePatternResolver) { this.resourcePatternResolver = resourcePatternResolver; } /** * Set the cache value to use for resolved resources, in seconds. * * @param cacheSeconds * The cache maximum seconds. */ public void setCacheSeconds(int cacheSeconds) { this.cacheSeconds = cacheSeconds; } /** * The required roles to assign to resolved resources. Defaults to * {@link SetupResource#USER_ROLES}. * * @param roles * The required roles to use. */ public void setRoles(Set<String> roles) { this.roles = roles; } /** * Set the filename to content type mapping. Defaults to * {@link SetupResourceUtils#DEFAULT_FILENAME_EXTENSION_CONTENT_TYPES}. * * @param fileExtensionContentTypeMapping * The filename to content type mapping to use. */ public void setFileExtensionContentTypeMapping(Map<String, String> fileExtensionContentTypeMapping) { this.fileExtensionContentTypeMapping = fileExtensionContentTypeMapping; } /** * Set the locale to use for resources that have no locale specified in * their filename. * * @param defaultLocale * The default locale. */ public void setDefaultLocale(Locale defaultLocale) { this.defaultLocale = defaultLocale; } /** * Set a scope to use for all resolved resources. * * @param scope * the scope to set * @since 1.1 */ public void setScope(SetupResourceScope scope) { this.scope = scope; } }