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; 018 019import java.util.ArrayList; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023 024import org.apache.wicket.css.ICssCompressor; 025import org.apache.wicket.javascript.IJavaScriptCompressor; 026import org.apache.wicket.markup.head.CssHeaderItem; 027import org.apache.wicket.markup.head.CssReferenceHeaderItem; 028import org.apache.wicket.markup.head.HeaderItem; 029import org.apache.wicket.markup.head.IReferenceHeaderItem; 030import org.apache.wicket.markup.head.JavaScriptHeaderItem; 031import org.apache.wicket.markup.head.JavaScriptReferenceHeaderItem; 032import org.apache.wicket.request.resource.CssResourceReference; 033import org.apache.wicket.request.resource.JavaScriptResourceReference; 034import org.apache.wicket.request.resource.ResourceReference; 035import org.apache.wicket.request.resource.ResourceReferenceRegistry; 036import org.apache.wicket.resource.bundles.ConcatResourceBundleReference; 037import org.apache.wicket.util.lang.Args; 038 039/** 040 * Contains all resource bundles that are registered in the application. Resource bundles provide a 041 * way to combine multiple resources into one, reducing the number of requests needed to load a 042 * page. The code using the resources does not need to know about the registered resources, making 043 * it possible to create resource bundles for 3rd party libraries. When a single resource from a 044 * resource bundle is requested, the bundle is rendered instead. All other resources from the bundle 045 * are marked as rendered. A specific resource can only be part of one bundle. 046 * 047 * @author papegaaij 048 */ 049public class ResourceBundles 050{ 051 private final ResourceReferenceRegistry registry; 052 053 private final Map<HeaderItem, HeaderItem> providedResourcesToBundles; 054 055 /** 056 * Construct. 057 * 058 * @param registry 059 * the registry that keeps all referenced resources 060 */ 061 public ResourceBundles(final ResourceReferenceRegistry registry) 062 { 063 this.registry = Args.notNull(registry, "registry"); 064 this.providedResourcesToBundles = new HashMap<HeaderItem, HeaderItem>(); 065 } 066 067 /** 068 * Adds a javascript bundle that is automatically generated by concatenating the given package 069 * resources. If the given resources depend on each other, you should make sure that the 070 * resources are provided in the order they need to be concatenated. If the resources depend on 071 * other resources, that are not part of the bundle, the bundle will inherit these dependencies. 072 * 073 * This method is equivalent to {@link #addBundle(HeaderItem)} with a 074 * {@link JavaScriptHeaderItem} for a {@link ConcatResourceBundleReference}. 075 * 076 * @param scope 077 * The {@linkplain ResourceReference#getScope() scope} of the bundle 078 * @param name 079 * The name of the resource. This will show up as the filename in the markup. 080 * @param references 081 * The resources this bundle will consist of. 082 * @return the newly created bundle 083 */ 084 public JavaScriptReferenceHeaderItem addJavaScriptBundle(Class<?> scope, String name, 085 JavaScriptResourceReference... references) 086 { 087 return addJavaScriptBundle(scope, name, false, references); 088 } 089 090 /** 091 * Adds a javascript bundle that is automatically generated by concatenating the given package 092 * resources. If the given resources depend on each other, you should make sure that the 093 * resources are provided in the order they need to be concatenated. If the resources depend on 094 * other resources, that are not part of the bundle, the bundle will inherit these dependencies. 095 * 096 * This method is equivalent to {@link #addBundle(HeaderItem)} with a 097 * {@link JavaScriptHeaderItem} for a {@link ConcatResourceBundleReference}. 098 * 099 * @param scope 100 * The {@linkplain ResourceReference#getScope() scope} of the bundle 101 * @param defer 102 * specifies that the execution of a script should be deferred (delayed) until after 103 * the page has been loaded. 104 * @param name 105 * The name of the resource. This will show up as the filename in the markup. 106 * @param references 107 * The resources this bundle will consist of. 108 * @return the newly created bundle 109 */ 110 public JavaScriptReferenceHeaderItem addJavaScriptBundle(Class<?> scope, String name, boolean defer, 111 JavaScriptResourceReference... references) 112 { 113 List<JavaScriptReferenceHeaderItem> items = new ArrayList<>(); 114 115 for (JavaScriptResourceReference curReference : references) 116 { 117 items.add(JavaScriptHeaderItem.forReference(curReference)); 118 } 119 120 ConcatResourceBundleReference<JavaScriptReferenceHeaderItem> bundleReference = 121 newBundleResourceReference(scope, name, items); 122 123 if (Application.exists()) 124 { 125 IJavaScriptCompressor javaScriptCompressor = Application.get().getResourceSettings().getJavaScriptCompressor(); 126 bundleReference.setCompressor(javaScriptCompressor); 127 } 128 129 JavaScriptReferenceHeaderItem item = JavaScriptHeaderItem.forReference(bundleReference); 130 item.setDefer(defer); 131 return addBundle(item); 132 } 133 134 /** 135 * Adds a css bundle that is automatically generated by concatenating the given package 136 * resources. If the given resources depend on each other, you should make sure that the 137 * resources are provided in the order they need to be concatenated. If the resources depend on 138 * other resources, that are not part of the bundle, the bundle will inherit these dependencies. 139 * 140 * This method is equivalent to {@link #addBundle(HeaderItem)} with a {@link CssHeaderItem} for 141 * a {@link ConcatResourceBundleReference}. 142 * 143 * @param scope 144 * The {@linkplain ResourceReference#getScope() scope} of the bundle 145 * @param name 146 * The name of the resource. This will show up as the filename in the markup. 147 * @param references 148 * The resources this bundle will consist of. 149 * @return the newly created bundle 150 */ 151 public CssReferenceHeaderItem addCssBundle(Class<?> scope, String name, 152 CssResourceReference... references) 153 { 154 List<CssReferenceHeaderItem> items = new ArrayList<CssReferenceHeaderItem>(); 155 for (CssResourceReference curReference : references) 156 { 157 items.add(CssHeaderItem.forReference(curReference)); 158 } 159 ConcatResourceBundleReference<CssReferenceHeaderItem> bundleReference = 160 newBundleResourceReference(scope, name, items); 161 if (Application.exists()) 162 { 163 ICssCompressor cssCompressor = Application.get().getResourceSettings().getCssCompressor(); 164 bundleReference.setCompressor(cssCompressor); 165 } 166 return addBundle(CssHeaderItem.forReference(bundleReference)); 167 } 168 169 /** 170 * Creates a ResourceReference that will point to the bundle with the items 171 * 172 * @param scope 173 * The {@linkplain ResourceReference#getScope() scope} of the bundle 174 * @param name 175 * The name of the resource. This will show up as the filename in the markup. 176 * @param items 177 * The HeaderItems for the resource references for the bundle 178 * @param <T> 179 * The type of the header items 180 * @return A ResourceReference that will point to the bundle with the items 181 */ 182 protected <T extends HeaderItem & IReferenceHeaderItem> ConcatResourceBundleReference<T> newBundleResourceReference(Class<?> scope, String name, List<T> items) 183 { 184 return new ConcatResourceBundleReference<T>(scope, name, items); 185 } 186 187 /** 188 * Adds a bundle to the registry. 189 * 190 * @param bundle 191 * The bundle to register 192 * @return the bundle 193 * @throws IllegalArgumentException 194 * if any of the provided resources of the given bundle is already provided by a 195 * different bundle. 196 */ 197 public <T extends HeaderItem> T addBundle(T bundle) 198 { 199 for (HeaderItem curProvidedResource : bundle.getProvidedResources()) 200 { 201 if (providedResourcesToBundles.containsKey(curProvidedResource)) 202 { 203 throw new IllegalArgumentException( 204 "Only one bundle can provide a certain resource. " + 205 providedResourcesToBundles.get(curProvidedResource) + 206 " already provides the resource " + curProvidedResource); 207 } 208 providedResourcesToBundles.put(curProvidedResource, bundle); 209 } 210 if (bundle instanceof IReferenceHeaderItem) 211 { 212 ResourceReference reference = ((IReferenceHeaderItem) bundle).getReference(); 213 if (reference.canBeRegistered()) 214 { 215 registry.registerResourceReference(reference); 216 } 217 } 218 return bundle; 219 } 220 221 /** 222 * Finds a bundle that provides the given item. 223 * 224 * @param item 225 * The item that should be provided by the bundle. 226 * @return The bundle that provides the given item or null if no bundle is found. 227 */ 228 public HeaderItem findBundle(HeaderItem item) 229 { 230 return providedResourcesToBundles.get(item); 231 } 232}