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.caching; 018 019import java.util.Locale; 020import java.util.concurrent.ConcurrentHashMap; 021import java.util.concurrent.ConcurrentMap; 022 023import org.apache.wicket.core.util.resource.UrlResourceStream; 024import org.apache.wicket.core.util.resource.locator.IResourceNameIterator; 025import org.apache.wicket.core.util.resource.locator.IResourceStreamLocator; 026import org.apache.wicket.request.resource.ResourceReference.Key; 027import org.apache.wicket.util.lang.Args; 028import org.apache.wicket.util.resource.FileResourceStream; 029import org.apache.wicket.util.resource.IResourceStream; 030 031 032/** 033 * Locating resources can take a significant amount of time, especially since there are often 034 * several CSS, JavaScript and image resources on any given page. To facilitate localization and 035 * styling, Wicket will usually make several attempts at locating each resource (i.e. first with an 036 * "en_US" suffix, then "en", and so on); multiply these attempts by the number of resources on the 037 * page and this starts to add up. 038 * <p> 039 * This locator mitigates this problem by caching (indefinitely) references to 040 * {@link UrlResourceStream} and {@link FileResourceStream} objects as they are found, and 041 * {@link NullResourceStreamReference} for all which are missing so they are not looked up again and 042 * again. 043 */ 044public class CachingResourceStreamLocator implements IResourceStreamLocator 045{ 046 private final ConcurrentMap<CacheKey, IResourceStreamReference> cache; 047 048 private final IResourceStreamLocator delegate; 049 050 /** 051 * Construct. 052 * 053 * @param resourceStreamLocator 054 * the delegate 055 */ 056 public CachingResourceStreamLocator(final IResourceStreamLocator resourceStreamLocator) 057 { 058 Args.notNull(resourceStreamLocator, "resourceStreamLocator"); 059 060 delegate = resourceStreamLocator; 061 062 cache = new ConcurrentHashMap<>(); 063 } 064 065 /** 066 * {@inheritDoc} 067 * 068 * Checks for {@link IResourceStreamReference} in the cache and returns <code>null</code> if the 069 * result is {@link NullResourceStreamReference#INSTANCE}, or {@link FileResourceStream} / 070 * {@link UrlResourceStream} if there is an entry in the cache. Otherwise asks the delegate to 071 * find one and puts it in the cache. 072 */ 073 @Override 074 public IResourceStream locate(Class<?> clazz, String path) 075 { 076 CacheKey key = new CacheKey(clazz.getName(), path, null, null, null, null, true); 077 IResourceStreamReference resourceStreamReference = cache.get(key); 078 079 final IResourceStream result; 080 if (resourceStreamReference == null) 081 { 082 result = delegate.locate(clazz, path); 083 084 updateCache(key, result); 085 } 086 else 087 { 088 result = resourceStreamReference.getReference(); 089 } 090 091 return result; 092 } 093 094 private void updateCache(CacheKey key, IResourceStream stream) 095 { 096 if (null == stream) 097 { 098 cache.put(key, NullResourceStreamReference.INSTANCE); 099 } 100 else if (stream instanceof FileResourceStream) 101 { 102 FileResourceStream fileResourceStream = (FileResourceStream)stream; 103 cache.put(key, new FileResourceStreamReference(fileResourceStream)); 104 } 105 else if (stream instanceof UrlResourceStream) 106 { 107 UrlResourceStream urlResourceStream = (UrlResourceStream)stream; 108 cache.put(key, new UrlResourceStreamReference(urlResourceStream)); 109 } 110 } 111 112 @Override 113 public IResourceStream locate(Class<?> scope, String path, String style, String variation, 114 Locale locale, String extension, boolean strict) 115 { 116 CacheKey key = new CacheKey(scope.getName(), path, extension, locale, style, variation, strict); 117 IResourceStreamReference resourceStreamReference = cache.get(key); 118 119 final IResourceStream result; 120 if (resourceStreamReference == null) 121 { 122 result = delegate.locate(scope, path, style, variation, locale, extension, strict); 123 124 updateCache(key, result); 125 } 126 else 127 { 128 result = resourceStreamReference.getReference(); 129 } 130 131 return result; 132 } 133 134 @Override 135 public IResourceNameIterator newResourceNameIterator(String path, Locale locale, String style, 136 String variation, String extension, boolean strict) 137 { 138 return delegate.newResourceNameIterator(path, locale, style, variation, extension, strict); 139 } 140 141 /** 142 * Clears the resource cache. 143 * 144 * @since 6.16.0 145 */ 146 public void clearCache() 147 { 148 cache.clear(); 149 } 150 151 /** 152 * A specialization of {@link org.apache.wicket.request.resource.ResourceReference.Key} that 153 * additionally takes the file extension into account 154 */ 155 private static class CacheKey extends Key 156 { 157 private static final long serialVersionUID = 1L; 158 159 /** 160 * The file extension 161 */ 162 private final String extension; 163 164 /** Whether the key was looked up using a strict matching search */ 165 private final boolean strict; 166 167 private CacheKey(String scope, String name, String extension, Locale locale, String style, String variation, boolean strict) 168 { 169 super(scope, name, locale, style, variation); 170 171 this.extension = extension; 172 this.strict = strict; 173 } 174 175 @Override 176 public int hashCode() 177 { 178 final int prime = 31; 179 int result = super.hashCode(); 180 result = prime * result + ((extension == null) ? 0 : extension.hashCode()); 181 result = prime * result + (strict ? 1231 : 1237); 182 return result; 183 } 184 185 @Override 186 public boolean equals(Object obj) 187 { 188 if (this == obj) 189 return true; 190 if (!super.equals(obj)) 191 return false; 192 if (getClass() != obj.getClass()) 193 return false; 194 CacheKey other = (CacheKey)obj; 195 if (extension == null) 196 { 197 if (other.extension != null) 198 return false; 199 } 200 else if (!extension.equals(other.extension)) 201 return false; 202 if (strict != other.strict) 203 return false; 204 return true; 205 } 206 } 207}