blob: 860c65df1bc3f3a5c9830c609366dc35a5e130b2 [file] [log] [blame]
/*
* 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.maven.doxia.site.decoration.inheritance;
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.ArrayList;
import java.util.List;
import org.apache.maven.doxia.site.decoration.Banner;
import org.apache.maven.doxia.site.decoration.Body;
import org.apache.maven.doxia.site.decoration.DecorationModel;
import org.apache.maven.doxia.site.decoration.LinkItem;
import org.apache.maven.doxia.site.decoration.Logo;
import org.apache.maven.doxia.site.decoration.Menu;
import org.apache.maven.doxia.site.decoration.MenuItem;
import org.codehaus.plexus.util.xml.Xpp3Dom;
/**
* Manage inheritance of the decoration model.
*
* @author <a href="mailto:brett@apache.org">Brett Porter</a>
* @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
*/
@Singleton
@Named
public class DefaultDecorationModelInheritanceAssembler implements DecorationModelInheritanceAssembler {
/** {@inheritDoc} */
public void assembleModelInheritance(
String name, DecorationModel child, DecorationModel parent, String childBaseUrl, String parentBaseUrl) {
if (parent == null || !child.isMergeParent()) {
return;
}
child.setCombineSelf(parent.getCombineSelf());
URLRebaser urlContainer = new URLRebaser(parentBaseUrl, childBaseUrl);
if (child.getBannerLeft() == null && parent.getBannerLeft() != null) {
child.setBannerLeft(parent.getBannerLeft().clone());
rebaseBannerPaths(child.getBannerLeft(), urlContainer);
}
if (child.getBannerRight() == null && parent.getBannerRight() != null) {
child.setBannerRight(parent.getBannerRight().clone());
rebaseBannerPaths(child.getBannerRight(), urlContainer);
}
if (child.isDefaultPublishDate() && parent.getPublishDate() != null) {
child.setPublishDate(parent.getPublishDate().clone());
}
if (child.isDefaultVersion() && parent.getVersion() != null) {
child.setVersion(parent.getVersion().clone());
}
if (child.getEdit() == null && parent.getEdit() != null) {
child.setEdit(parent.getEdit());
}
if (child.getSkin() == null && parent.getSkin() != null) {
child.setSkin(parent.getSkin().clone());
}
child.setPoweredBy(mergePoweredByLists(child.getPoweredBy(), parent.getPoweredBy(), urlContainer));
if (parent.getLastModified() > child.getLastModified()) {
child.setLastModified(parent.getLastModified());
}
assembleBodyInheritance(name, child, parent, urlContainer);
assembleCustomInheritance(child, parent);
}
/** {@inheritDoc} */
public void resolvePaths(final DecorationModel decoration, final String baseUrl) {
if (baseUrl == null) {
return;
}
if (decoration.getBannerLeft() != null) {
relativizeBannerPaths(decoration.getBannerLeft(), baseUrl);
}
if (decoration.getBannerRight() != null) {
relativizeBannerPaths(decoration.getBannerRight(), baseUrl);
}
for (Logo logo : decoration.getPoweredBy()) {
relativizeLogoPaths(logo, baseUrl);
}
if (decoration.getBody() != null) {
for (LinkItem linkItem : decoration.getBody().getLinks()) {
relativizeLinkItemPaths(linkItem, baseUrl);
}
for (LinkItem linkItem : decoration.getBody().getBreadcrumbs()) {
relativizeLinkItemPaths(linkItem, baseUrl);
}
for (Menu menu : decoration.getBody().getMenus()) {
relativizeMenuPaths(menu.getItems(), baseUrl);
}
}
}
/**
* Resolves all relative paths between the elements in a banner. The banner element might contain relative paths
* to the oldBaseUrl, these are changed to the newBannerUrl.
*
* @param banner
* @param baseUrl
*/
private void relativizeBannerPaths(final Banner banner, final String baseUrl) {
// banner has been checked to be not null, both href and src may be empty or null
banner.setHref(relativizeLink(banner.getHref(), baseUrl));
banner.setSrc(relativizeLink(banner.getSrc(), baseUrl));
}
private void rebaseBannerPaths(final Banner banner, final URLRebaser urlContainer) {
if (banner.getHref() != null) // it may be empty
{
banner.setHref(urlContainer.rebaseLink(banner.getHref()));
}
if (banner.getSrc() != null) {
banner.setSrc(urlContainer.rebaseLink(banner.getSrc()));
}
}
private void assembleCustomInheritance(final DecorationModel child, final DecorationModel parent) {
if (child.getCustom() == null) {
child.setCustom(parent.getCustom());
} else {
child.setCustom(Xpp3Dom.mergeXpp3Dom((Xpp3Dom) child.getCustom(), (Xpp3Dom) parent.getCustom()));
}
}
private void assembleBodyInheritance(
final String name,
final DecorationModel child,
final DecorationModel parent,
final URLRebaser urlContainer) {
Body cBody = child.getBody();
Body pBody = parent.getBody();
if (cBody != null || pBody != null) {
if (cBody == null) {
cBody = new Body();
child.setBody(cBody);
}
if (pBody == null) {
pBody = new Body();
}
if (cBody.getHead() == null && pBody.getHead() != null) {
cBody.setHead(pBody.getHead());
}
cBody.setLinks(mergeLinkItemLists(cBody.getLinks(), pBody.getLinks(), urlContainer, false));
if (cBody.getBreadcrumbs().isEmpty() && !pBody.getBreadcrumbs().isEmpty()) {
LinkItem breadcrumb = new LinkItem();
breadcrumb.setName(name);
breadcrumb.setHref("index.html");
cBody.getBreadcrumbs().add(breadcrumb);
}
cBody.setBreadcrumbs(
mergeLinkItemLists(cBody.getBreadcrumbs(), pBody.getBreadcrumbs(), urlContainer, true));
cBody.setMenus(mergeMenus(cBody.getMenus(), pBody.getMenus(), urlContainer));
if (cBody.getFooter() == null && pBody.getFooter() != null) {
cBody.setFooter(pBody.getFooter());
}
}
}
private List<Menu> mergeMenus(
final List<Menu> childMenus, final List<Menu> parentMenus, final URLRebaser urlContainer) {
List<Menu> menus = new ArrayList<Menu>(childMenus.size() + parentMenus.size());
for (Menu menu : childMenus) {
menus.add(menu);
}
int topCounter = 0;
for (Menu menu : parentMenus) {
if ("top".equals(menu.getInherit())) {
final Menu clone = menu.clone();
rebaseMenuPaths(clone.getItems(), urlContainer);
menus.add(topCounter, clone);
topCounter++;
} else if ("bottom".equals(menu.getInherit())) {
final Menu clone = menu.clone();
rebaseMenuPaths(clone.getItems(), urlContainer);
menus.add(clone);
}
}
return menus;
}
private void relativizeMenuPaths(final List<MenuItem> items, final String baseUrl) {
for (MenuItem item : items) {
relativizeLinkItemPaths(item, baseUrl);
relativizeMenuPaths(item.getItems(), baseUrl);
}
}
private void rebaseMenuPaths(final List<MenuItem> items, final URLRebaser urlContainer) {
for (MenuItem item : items) {
rebaseLinkItemPaths(item, urlContainer);
rebaseMenuPaths(item.getItems(), urlContainer);
}
}
private void relativizeLinkItemPaths(final LinkItem item, final String baseUrl) {
item.setHref(relativizeLink(item.getHref(), baseUrl));
}
private void rebaseLinkItemPaths(final LinkItem item, final URLRebaser urlContainer) {
item.setHref(urlContainer.rebaseLink(item.getHref()));
}
private void relativizeLogoPaths(final Logo logo, final String baseUrl) {
logo.setImg(relativizeLink(logo.getImg(), baseUrl));
relativizeLinkItemPaths(logo, baseUrl);
}
private void rebaseLogoPaths(final Logo logo, final URLRebaser urlContainer) {
logo.setImg(urlContainer.rebaseLink(logo.getImg()));
rebaseLinkItemPaths(logo, urlContainer);
}
private List<LinkItem> mergeLinkItemLists(
final List<LinkItem> childList,
final List<LinkItem> parentList,
final URLRebaser urlContainer,
boolean cutParentAfterDuplicate) {
List<LinkItem> items = new ArrayList<LinkItem>(childList.size() + parentList.size());
for (LinkItem item : parentList) {
if (!items.contains(item) && !childList.contains(item)) {
final LinkItem clone = item.clone();
rebaseLinkItemPaths(clone, urlContainer);
items.add(clone);
} else if (cutParentAfterDuplicate) {
// if a parent item is found in child, ignore next items (case for breadcrumbs)
// merge ( "B > E", "A > B > C > D" ) -> "A > B > E" (notice missing "C > D")
// see https://issues.apache.org/jira/browse/DOXIASITETOOLS-62
break;
}
}
for (LinkItem item : childList) {
if (!items.contains(item)) {
items.add(item);
}
}
return items;
}
private List<Logo> mergePoweredByLists(
final List<Logo> childList, final List<Logo> parentList, final URLRebaser urlContainer) {
List<Logo> logos = new ArrayList<Logo>(childList.size() + parentList.size());
for (Logo logo : parentList) {
if (!logos.contains(logo)) {
final Logo clone = logo.clone();
rebaseLogoPaths(clone, urlContainer);
logos.add(clone);
}
}
for (Logo logo : childList) {
if (!logos.contains(logo)) {
logos.add(logo);
}
}
return logos;
}
// relativize only affects absolute links, if the link has the same scheme, host and port
// as the base, it is made into a relative link as viewed from the base
private String relativizeLink(final String link, final String baseUri) {
if (link == null || baseUri == null) {
return link;
}
// this shouldn't be necessary, just to swallow mal-formed hrefs
try {
final URIPathDescriptor path = new URIPathDescriptor(baseUri, link);
return path.relativizeLink().toString();
} catch (IllegalArgumentException e) {
return link;
}
}
/**
* URL rebaser: based on an old and a new path, can rebase a link based on old path to a value based on the new
* path.
*/
private static class URLRebaser {
private final String oldPath;
private final String newPath;
/**
* Construct a URL rebaser.
*
* @param oldPath the old path.
* @param newPath the new path.
*/
URLRebaser(final String oldPath, final String newPath) {
this.oldPath = oldPath;
this.newPath = newPath;
}
/**
* Get the new path.
*
* @return the new path.
*/
public String getNewPath() {
return this.newPath;
}
/**
* Get the old path.
*
* @return the old path.
*/
public String getOldPath() {
return this.oldPath;
}
/**
* Rebase only affects relative links, a relative link wrt an old base gets translated,
* so it points to the same location as viewed from a new base
*/
public String rebaseLink(final String link) {
if (link == null || getOldPath() == null) {
return link;
}
if (link.contains("${project.")) {
throw new IllegalArgumentException("site.xml late interpolation ${project.*} expression found"
+ " in link: '" + link + "'. Use early interpolation ${this.*}");
}
final URIPathDescriptor oldPath = new URIPathDescriptor(getOldPath(), link);
return oldPath.rebaseLink(getNewPath()).toString();
}
}
}