001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.core.util.resource.locator; 018 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collections; 022import java.util.List; 023import java.util.Locale; 024 025import org.apache.wicket.Application; 026import org.apache.wicket.core.util.file.WebApplicationPath; 027import org.apache.wicket.util.file.IResourceFinder; 028import org.apache.wicket.util.file.Path; 029import org.apache.wicket.util.resource.IResourceStream; 030import org.apache.wicket.util.resource.ResourceUtils; 031import org.apache.wicket.util.resource.ResourceUtils.PathLocale; 032import org.apache.wicket.util.string.Strings; 033import org.slf4j.Logger; 034import org.slf4j.LoggerFactory; 035 036 037/** 038 * Locates Wicket resources. 039 * 040 * <p> 041 * Contains the logic to locate a resource based on a path, a variation, a style (see 042 * {@link org.apache.wicket.Session}), a locale and an extension string. The full filename will be 043 * built like: <path>_<variation>_<style>_<locale>.<extension>. 044 * <p> 045 * Resource matches will be attempted in the following order: 046 * <ol> 047 * <li><path>_<variation>_<style>_<locale>.<extension></li> 048 * <li><path>_<style>_<locale>.<extension></li> 049 * <li><path>_<locale>.<extension></li> 050 * <li><path>_<style>.<extension></li> 051 * <li><path>.<extension></li> 052 * </ol> 053 * <p> 054 * Locales may contain a language, a country and a region or variant. Combinations of these 055 * components will be attempted in the following order: 056 * <ol> 057 * <li>locale.toString() see javadoc for Locale for more details</li> 058 * <li><language>_<country></li> 059 * <li><language></li> 060 * </ol> 061 * <p> 062 * Resources will be actually loaded by the {@link IResourceFinder}s defined in the resource 063 * settings. By default there are finders that look in the classpath and in the classpath in 064 * META-INF/resources. You can add more by adding {@link WebApplicationPath}s or {@link Path}s to 065 * {@link org.apache.wicket.settings.ResourceSettings#getResourceFinders()}. 066 * 067 * @author Juergen Donnerstag 068 * @author Jonathan Locke 069 */ 070public class ResourceStreamLocator implements IResourceStreamLocator 071{ 072 /** Logging */ 073 private static final Logger log = LoggerFactory.getLogger(ResourceStreamLocator.class); 074 075 private static final Iterable<String> NO_EXTENSIONS = new ArrayList<>(0); 076 077 /** If null, the application registered finder will be used */ 078 private List<IResourceFinder> finders; 079 080 /** 081 * Constructor 082 */ 083 public ResourceStreamLocator() 084 { 085 this((List<IResourceFinder>)null); 086 } 087 088 /** 089 * Constructor 090 * 091 * @param finders 092 * resource finders. These will be tried in the given order. 093 */ 094 public ResourceStreamLocator(final IResourceFinder... finders) 095 { 096 this(Arrays.asList(finders)); 097 } 098 099 /** 100 * Constructor 101 * 102 * @param finders 103 * resource finders. These will be tried in the given order. 104 */ 105 public ResourceStreamLocator(final List<IResourceFinder> finders) 106 { 107 this.finders = finders; 108 } 109 110 /** 111 * 112 * @see org.apache.wicket.core.util.resource.locator.IResourceStreamLocator#locate(java.lang.Class, 113 * java.lang.String) 114 */ 115 @Override 116 public IResourceStream locate(final Class<?> clazz, final String path) 117 { 118 // First try with the resource finder registered with the application 119 // (allows for markup reloading) 120 if (finders == null) 121 { 122 finders = Application.get().getResourceSettings().getResourceFinders(); 123 } 124 125 IResourceStream result; 126 for (IResourceFinder finder : finders) 127 { 128 log.debug("Attempting to locate resource '{}' using finder'{}'", path, finder); 129 result = finder.find(clazz, path); 130 if (result != null) 131 { 132 return result; 133 } 134 } 135 return null; 136 } 137 138 /** 139 * 140 * @see org.apache.wicket.core.util.resource.locator.IResourceStreamLocator#locate(java.lang.Class, 141 * java.lang.String, java.lang.String, java.lang.String, java.util.Locale, 142 * java.lang.String, boolean) 143 */ 144 @Override 145 public IResourceStream locate(final Class<?> clazz, String path, final String style, 146 final String variation, Locale locale, final String extension, final boolean strict) 147 { 148 // If path contains a locale, then it'll replace the locale provided to this method 149 PathLocale data = ResourceUtils.getLocaleFromFilename(path); 150 if ((data != null) && (data.locale != null)) 151 { 152 path = data.path; 153 locale = data.locale; 154 } 155 156 // Try the various combinations of style, locale and extension to find the resource. 157 IResourceNameIterator iter = newResourceNameIterator(path, locale, style, variation, 158 extension, strict); 159 while (iter.hasNext()) 160 { 161 String newPath = iter.next(); 162 163 IResourceStream stream = locate(clazz, newPath); 164 if (stream != null) 165 { 166 stream.setLocale(iter.getLocale()); 167 stream.setStyle(iter.getStyle()); 168 stream.setVariation(iter.getVariation()); 169 return stream; 170 } 171 } 172 173 return null; 174 } 175 176 /** 177 * 178 * @see org.apache.wicket.core.util.resource.locator.IResourceStreamLocator#newResourceNameIterator(java.lang.String, 179 * java.util.Locale, java.lang.String, java.lang.String, java.lang.String, boolean) 180 */ 181 @Override 182 public IResourceNameIterator newResourceNameIterator(final String path, final Locale locale, 183 final String style, final String variation, final String extension, final boolean strict) 184 { 185 final Iterable<String> extensions; 186 187 final String realPath; 188 189 if ((extension == null) && (path != null) && (path.indexOf('.') != -1)) 190 { 191 // extract the path and extension 192 realPath = Strings.beforeLast(path, '.'); 193 String realExtension = Strings.afterLast(path, '.'); 194 if (realExtension.indexOf(',') > -1) 195 { 196 // multiple extensions are not allowed in the path parameter 197 // it could be an attack, so ignore it and pretend there are no resources 198 return new EmptyResourceNameIterator(); 199 } 200 201 // add a minimized file to the resource lookup if necessary 202 if (Application.exists() && 203 Application.get().getResourceSettings().getUseMinifiedResources()) 204 { 205 extensions = Arrays.asList("min." + realExtension, realExtension); 206 } 207 else 208 { 209 extensions = Collections.singleton(realExtension); 210 } 211 } 212 else 213 { 214 realPath = path; 215 if (extension == null) 216 { 217 extensions = NO_EXTENSIONS; 218 } 219 else 220 { 221 String[] commaSeparated = Strings.split(extension, ','); 222 List<String> nonMinifiedExtensions = Arrays.asList(commaSeparated); 223 224 // add a minimized file to the resource lookup if necessary 225 if (Application.exists() && 226 Application.get().getResourceSettings().getUseMinifiedResources()) 227 { 228 ArrayList<String> minifiedExtensions = new ArrayList<>(); 229 for (String nonMinifiedExtension : nonMinifiedExtensions) 230 { 231 minifiedExtensions.add("min." + nonMinifiedExtension); 232 minifiedExtensions.add(nonMinifiedExtension); 233 } 234 extensions = minifiedExtensions; 235 } 236 else 237 { 238 extensions = nonMinifiedExtensions; 239 } 240 } 241 } 242 243 return newResourceNameIterator(realPath, locale, style, variation, extensions, strict); 244 } 245 246 // TODO Wicket 7 Add this method to IResourceStreamLocator interface. 247 public IResourceNameIterator newResourceNameIterator(final String path, final Locale locale, 248 final String style, final String variation, final Iterable<String> extensions, final boolean strict) 249 { 250 return new ResourceNameIterator(path, style, variation, locale, extensions, strict); 251 } 252}