Package org.apache.wicket.markup.head

Source Code of org.apache.wicket.markup.head.ResourceAggregator$RecordedHeaderItem

/*
* 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.markup.head;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.wicket.Application;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.markup.html.DecoratingHeaderResponse;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.resource.ResourceReference;
import org.apache.wicket.resource.CircularDependencyException;
import org.apache.wicket.resource.bundles.ReplacementResourceBundleReference;
import org.apache.wicket.util.lang.Classes;

/**
* {@code ResourceAggregator} implements resource dependencies, resource bundles and sorting of
* resources. During the rendering of components, all {@link HeaderItem}s are
* {@linkplain RecordedHeaderItem recorded} and processed at the end.
*
* @author papegaaij
*/
public class ResourceAggregator extends DecoratingHeaderResponse
{
  /**
   * The location in which a {@link HeaderItem} is added, consisting of the component/behavior
   * that added the item, the index in the list for that component/behavior at which the item was
   * added and the index in the request.
   *
   * @author papegaaij
   */
  public static class RecordedHeaderItemLocation
  {
    private final Object renderBase;
    private final int indexInRenderBase;
    private final int indexInRequest;

    /**
     * Construct.
     *
     * @param renderBase
     *            The component or behavior that added the item.
     * @param indexInRenderBase
     *            Indicates the number of items added before this one on the same component or
     *            behavior.
     * @param indexInRequest
     *            Indicates the number of items added before this one in the same request.
     */
    public RecordedHeaderItemLocation(Object renderBase, int indexInRenderBase,
      int indexInRequest)
    {
      this.renderBase = renderBase;
      this.indexInRenderBase = indexInRenderBase;
      this.indexInRequest = indexInRequest;
    }

    /**
     * @return the component or behavior that added the item.
     */
    public Object getRenderBase()
    {
      return renderBase;
    }

    /**
     * @return the number of items added before this one on the same component or behavior.
     */
    public int getIndexInRenderBase()
    {
      return indexInRenderBase;
    }

    /**
     * @return the number of items added before this one in the same request.
     */
    public int getIndexInRequest()
    {
      return indexInRequest;
    }

    @Override
    public String toString()
    {
      return (renderBase == null ? "null" : Classes.simpleName(renderBase.getClass())) + '@' +
        indexInRenderBase;
    }
  }

  /**
   * Contains information about an {@link HeaderItem} that must be rendered.
   *
   * @author papegaaij
   */
  public static class RecordedHeaderItem
  {
    private final HeaderItem item;

    private final List<RecordedHeaderItemLocation> locations;

    /**
     * Construct.
     *
     * @param item
     */
    public RecordedHeaderItem(HeaderItem item)
    {
      this.item = item;
      locations = new ArrayList<>();
    }

    /**
     * Records a location at which the item was added.
     *
     * @param renderBase
     *            The component or behavior that added the item.
     * @param indexInRenderBase
     *            Indicates the number of items added before this one on the same component or
     *            behavior.
     * @param indexInRequest
     *            Indicates the number of items added before this one in this request.
     */
    public void addLocation(Object renderBase, int indexInRenderBase, int indexInRequest)
    {
      locations.add(new RecordedHeaderItemLocation(renderBase, indexInRenderBase,
        indexInRequest));
    }

    /**
     * @return the actual item
     */
    public HeaderItem getItem()
    {
      return item;
    }

    /**
     * @return The locations at which the item was added.
     */
    public List<RecordedHeaderItemLocation> getLocations()
    {
      return locations;
    }

    @Override
    public String toString()
    {
      return locations + ":" + item;
    }
  }

  private final Map<HeaderItem, RecordedHeaderItem> itemsToBeRendered;
  private final List<OnDomReadyHeaderItem> domReadyItemsToBeRendered;
  private final List<OnLoadHeaderItem> loadItemsToBeRendered;

  private Object renderBase;
  private int indexInRenderBase;
  private int indexInRequest;

  /**
   * Construct.
   *
   * @param real
   */
  public ResourceAggregator(IHeaderResponse real)
  {
    super(real);

    itemsToBeRendered = new LinkedHashMap<>();
    domReadyItemsToBeRendered = new ArrayList<>();
    loadItemsToBeRendered = new ArrayList<>();
  }

  @Override
  public void markRendered(Object object)
  {
    super.markRendered(object);
    if (object instanceof Component || object instanceof Behavior)
    {
      renderBase = null;
      indexInRenderBase = 0;
    }
  }

  @Override
  public boolean wasRendered(Object object)
  {
    boolean ret = super.wasRendered(object);
    if (!ret && object instanceof Component || object instanceof Behavior)
    {
      renderBase = object;
      indexInRenderBase = 0;
    }
    return ret;
  }

  private void recordHeaderItem(HeaderItem item, Set<HeaderItem> depsDone)
  {
    renderDependencies(item, depsDone);
    RecordedHeaderItem recordedItem = itemsToBeRendered.get(item);
    if (recordedItem == null)
    {
      recordedItem = new RecordedHeaderItem(item);
      itemsToBeRendered.put(item, recordedItem);
    }
    recordedItem.addLocation(renderBase, indexInRenderBase, indexInRequest);
    indexInRenderBase++;
    indexInRequest++;
  }

  private void renderDependencies(HeaderItem item, Set<HeaderItem> depsDone)
  {
    for (HeaderItem curDependency : item.getDependencies())
    {
      curDependency = getItemToBeRendered(curDependency);
      if (depsDone.add(curDependency))
      {
        recordHeaderItem(curDependency, depsDone);
      }
      else
      {
        throw new CircularDependencyException(depsDone, curDependency);
      }
      depsDone.remove(curDependency);
    }
  }

  @Override
  public void render(HeaderItem item)
  {
    item = getItemToBeRendered(item);
    if (item instanceof OnDomReadyHeaderItem)
    {
      renderDependencies(item, new LinkedHashSet<HeaderItem>());
      domReadyItemsToBeRendered.add((OnDomReadyHeaderItem)item);
    }
    else if (item instanceof OnLoadHeaderItem)
    {
      renderDependencies(item, new LinkedHashSet<HeaderItem>());
      loadItemsToBeRendered.add((OnLoadHeaderItem)item);
    }
    else
    {
      Set<HeaderItem> depsDone = new LinkedHashSet<>();
      depsDone.add(item);
      recordHeaderItem(item, depsDone);
    }
  }

  @Override
  public void close()
  {
    renderHeaderItems();

    if (RequestCycle.get().find(AjaxRequestTarget.class) == null)
    {
      renderCombinedEventScripts();
    }
    else
    {
      renderSeperateEventScripts();
    }
    super.close();
  }

  /**
   * Renders all normal header items, sorting them and taking bundles into account.
   */
  private void renderHeaderItems()
  {
    List<RecordedHeaderItem> sortedItemsToBeRendered = new ArrayList<>(
      itemsToBeRendered.values());
    Comparator<? super RecordedHeaderItem> headerItemComparator = Application.get()
      .getResourceSettings()
      .getHeaderItemComparator();
    if (headerItemComparator != null)
    {
      Collections.sort(sortedItemsToBeRendered, headerItemComparator);
    }
    for (RecordedHeaderItem curRenderItem : sortedItemsToBeRendered)
    {
      if (markItemRendered(curRenderItem.getItem()))
      {
        getRealResponse().render(curRenderItem.getItem());
      }
    }
  }

  /**
   * Combines all DOM ready and onLoad scripts and renders them as 2 script tags.
   */
  private void renderCombinedEventScripts()
  {
    StringBuilder combinedScript = new StringBuilder();
    for (OnDomReadyHeaderItem curItem : domReadyItemsToBeRendered)
    {
      if (markItemRendered(curItem))
      {
        combinedScript.append('\n');
        combinedScript.append(curItem.getJavaScript());
        combinedScript.append(';');
      }
    }
    if (combinedScript.length() > 0)
    {
      getRealResponse().render(
        OnDomReadyHeaderItem.forScript(combinedScript.append('\n').toString()));
    }

    combinedScript.setLength(0);
    for (OnLoadHeaderItem curItem : loadItemsToBeRendered)
    {
      if (markItemRendered(curItem))
      {
        combinedScript.append('\n');
        combinedScript.append(curItem.getJavaScript());
        combinedScript.append(';');
      }
    }
    if (combinedScript.length() > 0)
    {
      getRealResponse().render(
        OnLoadHeaderItem.forScript(combinedScript.append('\n').toString()));
    }
  }

  /**
   * Renders the DOM ready and onLoad scripts as separate tags.
   */
  private void renderSeperateEventScripts()
  {
    for (OnDomReadyHeaderItem curItem : domReadyItemsToBeRendered)
    {
      if (markItemRendered(curItem))
      {
        getRealResponse().render(curItem);
      }
    }

    for (OnLoadHeaderItem curItem : loadItemsToBeRendered)
    {
      if (markItemRendered(curItem))
      {
        getRealResponse().render(curItem);
      }
    }
  }

  private boolean markItemRendered(HeaderItem item)
  {
    if (wasRendered(item))
      return false;

    if (item instanceof IWrappedHeaderItem)
    {
      getRealResponse().markRendered(((IWrappedHeaderItem)item).getWrapped());
    }
    getRealResponse().markRendered(item);
    for (HeaderItem curProvided : item.getProvidedResources())
    {
      getRealResponse().markRendered(curProvided);
    }
    return true;
  }

  /**
   * Resolves the actual item that needs to be rendered for the given item. This can be a
   * {@link NoHeaderItem} when the item was already rendered. It can also be a bundle or the item
   * itself, when it is not part of a bundle.
   *
   * @param item
   * @return The item to be rendered
   */
  private HeaderItem getItemToBeRendered(HeaderItem item)
  {
    HeaderItem innerItem = item;
    while (innerItem instanceof IWrappedHeaderItem)
    {
      innerItem = ((IWrappedHeaderItem)innerItem).getWrapped();
    }
    if (getRealResponse().wasRendered(innerItem))
    {
      return NoHeaderItem.get();
    }

    HeaderItem bundle = Application.get().getResourceBundles().findBundle(innerItem);
    if (bundle == null)
    {
      return item;
    }

    bundle = preserveDetails(item, bundle);

    if (item instanceof IWrappedHeaderItem)
    {
      bundle = ((IWrappedHeaderItem)item).wrap(bundle);
    }
    return bundle;
  }

  /**
   * Preserves the resource reference details for resource replacements.
   *
   * For example if CSS resource with media <em>screen</em> is replaced with
   * {@link org.apache.wicket.protocol.http.WebApplication#addResourceReplacement(org.apache.wicket.request.resource.CssResourceReference, org.apache.wicket.request.resource.ResourceReference)} then the replacement will
   * will inherit the media attribute
   *
   * @param item   The replaced header item
   * @param bundle The bundle that represents the replacement
   * @return the bundle with the preserved details
   */
  protected HeaderItem preserveDetails(HeaderItem item, HeaderItem bundle)
  {
    HeaderItem resultBundle;
    if (item instanceof CssReferenceHeaderItem && bundle instanceof CssReferenceHeaderItem)
    {
      CssReferenceHeaderItem originalHeaderItem = (CssReferenceHeaderItem) item;
      resultBundle = preserveCssDetails(originalHeaderItem, (CssReferenceHeaderItem) bundle);
    }
    else if (item instanceof JavaScriptReferenceHeaderItem && bundle instanceof JavaScriptReferenceHeaderItem)
    {
      JavaScriptReferenceHeaderItem originalHeaderItem = (JavaScriptReferenceHeaderItem) item;
      resultBundle = preserveJavaScriptDetails(originalHeaderItem, (JavaScriptReferenceHeaderItem) bundle);
    }
    else
    {
      resultBundle = bundle;
    }

    return resultBundle;
  }

  /**
   * Preserves the resource reference details for JavaScript resource replacements.
   *
   * For example if CSS resource with media <em>screen</em> is replaced with
   * {@link org.apache.wicket.protocol.http.WebApplication#addResourceReplacement(org.apache.wicket.request.resource.JavaScriptResourceReference, org.apache.wicket.request.resource.ResourceReference)} then the replacement will
   * will inherit the media attribute
   *
   * @param item   The replaced header item
   * @param bundle The bundle that represents the replacement
   * @return the bundle with the preserved details
   */
  private HeaderItem preserveJavaScriptDetails(JavaScriptReferenceHeaderItem item, JavaScriptReferenceHeaderItem bundle)
  {
    HeaderItem resultBundle;
    ResourceReference bundleReference = bundle.getReference();
    if (bundleReference instanceof ReplacementResourceBundleReference)
    {
      resultBundle = JavaScriptHeaderItem.forReference(bundleReference,
          item.getPageParameters(),
          item.getId(),
          item.isDefer(),
          item.getCharset(),
          item.getCondition()
      );
    }
    else
    {
      resultBundle = bundle;
    }
    return resultBundle;
  }

  /**
   * Preserves the resource reference details for CSS resource replacements.
   *
   * For example if CSS resource with media <em>screen</em> is replaced with
   * {@link org.apache.wicket.protocol.http.WebApplication#addResourceReplacement(org.apache.wicket.request.resource.CssResourceReference, org.apache.wicket.request.resource.ResourceReference)} then the replacement will
   * will inherit the media attribute
   *
   * @param item   The replaced header item
   * @param bundle The bundle that represents the replacement
   * @return the bundle with the preserved details
   */
  protected HeaderItem preserveCssDetails(CssReferenceHeaderItem item, CssReferenceHeaderItem bundle)
  {
    HeaderItem resultBundle;
    ResourceReference bundleReference = bundle.getReference();
    if (bundleReference instanceof ReplacementResourceBundleReference)
    {
      resultBundle = CssHeaderItem.forReference(bundleReference,
          item.getPageParameters(),
          item.getMedia(),
          item.getCondition());
    }
    else
    {
      resultBundle = bundle;
    }
    return resultBundle;
  }
}
TOP

Related Classes of org.apache.wicket.markup.head.ResourceAggregator$RecordedHeaderItem

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.