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}