
org.apache.wicket.resource.aggregation.AbstractResourceAggregatingHeaderResponse Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.wicket.resource.aggregation;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.wicket.markup.html.DecoratingHeaderResponse;
import org.apache.wicket.markup.html.IHeaderResponse;
import org.apache.wicket.request.resource.ResourceReference;
import org.apache.wicket.resource.ResourceUtil;
/**
* A header response that can be used to aggregate resources (primarily resource references) into
* groups that can be rendered after the entire hierarchy of IHeaderContributors have been
* traversed. A subclass of this could use that group to render a single URL to some aggregating
* servlet (for example) that could cut down on the number of HTTP requests the client must make.
*
* Resource references are aggregated according to the key that your subclass creates in the
* {@link #newGroupingKey(ResourceReferenceAndStringData)}. This key is used in a Map, so it needs
* to properly implement hashCode and equals.
*
* If your key does not implement Comparable<KeyClass>, you need to also return a Comparator
* for it from the {@link #getGroupingKeyComparator()} method.
*
* @author Jeremy Thomerson
* @param
* the type of ResourceReferenceCollection returned by
* {@link #newResourceReferenceCollection(Object)} and passed to all the methods that
* take a ResourceReferenceCollection. You will typically just use
* ResourceReferenceCollection for this param, unless you are returning a specific type
* of ResourceReferenceCollection from your subclass.
* @param
* the class of the key that you will create from
* {@link #newGroupingKey(ResourceReferenceAndStringData)}
*/
public abstract class AbstractResourceAggregatingHeaderResponse
extends DecoratingHeaderResponse
{
private final List topLevelReferences = new ArrayList();
/**
* Construct.
*
* @param real
* the wrapped header response
*/
public AbstractResourceAggregatingHeaderResponse(IHeaderResponse real)
{
super(real);
}
@Override
public void renderJavaScriptReference(ResourceReference reference)
{
topLevelReferences.add(new ResourceReferenceAndStringData(reference, null, false));
}
@Override
public void renderJavaScriptReference(ResourceReference reference, String id)
{
topLevelReferences.add(new ResourceReferenceAndStringData(reference, id, false));
}
@Override
public void renderCSSReference(ResourceReference reference)
{
topLevelReferences.add(new ResourceReferenceAndStringData(reference, null, true));
}
@Override
public void renderCSSReference(ResourceReference reference, String media)
{
topLevelReferences.add(new ResourceReferenceAndStringData(reference, media, true));
}
@Override
public void close()
{
// up until now, no ResourceReference objects have been passed to the real response. we need
// to group them into top-level groups and then render those groups
SortedMap map = new TreeMap(getGroupingKeyComparator());
for (ResourceReferenceAndStringData ref : topLevelReferences)
{
K key = newGroupingKey(ref);
R coll = map.get(key);
if (coll == null)
{
map.put(key, coll = newResourceReferenceCollection(key));
}
coll.add(ref);
}
// now, render our groups to the real response
Set alreadyRendered = new LinkedHashSet();
for (Entry entry : map.entrySet())
{
renderCollection(alreadyRendered, entry.getKey(), entry.getValue());
}
onAllCollectionsRendered(topLevelReferences);
// finally, we close the real response
super.close();
}
/* methods designed to be overridden if needed */
/**
* creates a ResourceReferenceCollection. If you want a specific type of
* ResourceReferenceCollection for your subclass of
* {@link AbstractResourceAggregatingHeaderResponse}, override this method.
*
* Note that because of the generics definition, you will probably have to cast to R. R is the
* parameter used when creating your subclass defining the type of ResourceReferenceCollection
* this returns and is passed into all methods that take a ResourceReferenceCollection
*
* @param key
* the grouping key that will be used for this collection. all references added to it
* will have the same key
* @return a newly created collection to contain resource references
*/
@SuppressWarnings("unchecked")
protected R newResourceReferenceCollection(K key)
{
return (R)new ResourceReferenceCollection();
}
/**
* This key is what is used to determine how to group (or aggregate) your resources. It must
* implement equals and hashCode correctly so that it can be used as a key in a HashMap. These
* methods must be implemented so that if two references are given over the course of the
* hierarchy traversal, and those two references should be grouped (or aggregated), the keys
* returned for each should equal each other and their hash codes should be equal as well.
*
* Typical implementations should use whether or not the resource reference is CSS as their
* first grouping parameter, since you don't want to render JS and CSS in the same tag (one
* needs to be in a link tag and one in a script tag).
*
* Note that if your grouping key class (K) does not implement Comparable<K>, you must
* also override {@link #getGroupingKeyComparator()} and return a valid comparator that sorts
* keys in the order you want references rendered.
*
* @param ref
* the resource reference with associated data that came from the render*Reference
* methods
* @return a new key used to group the references.
*/
protected abstract K newGroupingKey(ResourceReferenceAndStringData ref);
/**
* This comparator is used to sort the grouping keys that you return from
* {@link #newGroupingKey(ResourceReferenceAndStringData)}.
*
* Note that if your grouping key class (K) implements Comparable<K>, you do not need to
* override this method.
*
* @return a Comparator for K
*/
protected Comparator getGroupingKeyComparator()
{
return null;
}
/**
* When the entire hierarchy has been traversed and {@link #close()} is called, we loop through
* the grouped collections and render them in this method. This method is typically overridden
* to render your collection how you want to render them.
*
* For instance, if you want to aggregate your groups into a single HTTP request, you can
* override this method, create the URL to your aggregation servlet (or {@link Resource}), and
* then call getRealResponse().renderJavaScriptReference(yourUrl), or the appropriate
* method to render the URL for a group of CSS references.
*
* @param alreadyRendered
* a set of resource references that have already been rendered in other groups
* @param key
* they grouping key for this group
* @param coll
* the collection of resource references to render
*/
protected void renderCollection(Set alreadyRendered, K key,
R coll)
{
for (ResourceReferenceAndStringData data : coll)
{
renderIfNotAlreadyRendered(alreadyRendered, data);
}
}
/**
* Renders a single resource reference, only if it has not already been rendered. Note that you
* will typically not need to override this method. You should typically override
* {@link #render(ResourceReferenceAndStringData)} directly, which is called from this method if
* the resource reference has not been rendered elsewhere.
*
* @param alreadyRendered
* the set of references that have already been rendered in other groups
* @param data
* the reference (and associated data) to conditionally render.
*/
protected void renderIfNotAlreadyRendered(Set alreadyRendered,
ResourceReferenceAndStringData data)
{
if (!alreadyRendered.contains(data))
{
render(data);
alreadyRendered.add(data);
}
}
/**
* Renders a single resource reference. This is called from
* {@link #renderIfNotAlreadyRendered(Set, ResourceReferenceAndStringData)} for references that
* had not been rendered elsewhere.
*
* @param data
* the reference (and associated data) to conditionally render.
*/
protected void render(ResourceReferenceAndStringData data)
{
ResourceUtil.renderTo(getRealResponse(), data.getReference(), data.isCss(),
data.getString());
}
/**
* After all the collections have been rendered, we call this callback so your subclass can add
* any other logic as needed. For instance, if you are aggregating YUI resources, your
* {@link #renderCollection(Set, Object, ResourceReferenceCollection)} method might have
* rendered only a YUI constructor that loaded all the JS files for each group. Then, you need
* to loop through the references again, and render any JS inside a sandboxed YUI.use()
* statement. You would render those here by creating the YUI.use statement, and call
* getHeaderResponse().renderJavaScript(yourJS, null)
*
* @param allTopLevelReferences
* all the references that were rendered by the developers
*/
protected void onAllCollectionsRendered(
List allTopLevelReferences)
{
}
/* other interface methods: */
@Override
public void renderJavaScriptReference(String url)
{
// TODO: can we aggregate this? probably shouldn't...
getRealResponse().renderJavaScriptReference(url);
}
@Override
public void renderJavaScriptReference(String url, String id)
{
// TODO: can we aggregate this? probably shouldn't...
getRealResponse().renderJavaScriptReference(url, id);
}
@Override
public void renderCSSReference(String url)
{
// TODO: can we aggregate this? probably shouldn't...
getRealResponse().renderCSSReference(url);
}
@Override
public void renderCSSReference(String url, String media)
{
// TODO: can we aggregate this? probably shouldn't...
getRealResponse().renderCSSReference(url, media);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy