View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.doxia.site.inheritance;
20  
21  import javax.inject.Named;
22  import javax.inject.Singleton;
23  
24  import java.util.ArrayList;
25  import java.util.List;
26  
27  import org.apache.maven.doxia.site.Banner;
28  import org.apache.maven.doxia.site.Body;
29  import org.apache.maven.doxia.site.LinkItem;
30  import org.apache.maven.doxia.site.Logo;
31  import org.apache.maven.doxia.site.Menu;
32  import org.apache.maven.doxia.site.MenuItem;
33  import org.apache.maven.doxia.site.SiteModel;
34  import org.codehaus.plexus.util.xml.Xpp3Dom;
35  
36  /**
37   * Manage inheritance of the site model.
38   *
39   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
40   * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
41   */
42  @Singleton
43  @Named
44  public class DefaultSiteModelInheritanceAssembler implements SiteModelInheritanceAssembler {
45      /** {@inheritDoc} */
46      public void assembleModelInheritance(
47              String name, SiteModel child, SiteModel parent, String childBaseUrl, String parentBaseUrl) {
48          if (parent == null || !child.isMergeParent()) {
49              return;
50          }
51  
52          child.setCombineSelf(parent.getCombineSelf());
53  
54          URLRebaser urlContainer = new URLRebaser(parentBaseUrl, childBaseUrl);
55  
56          if (child.getBannerLeft() == null && parent.getBannerLeft() != null) {
57              child.setBannerLeft(parent.getBannerLeft().clone());
58              rebaseBannerPaths(child.getBannerLeft(), urlContainer);
59          }
60  
61          if (child.getBannerRight() == null && parent.getBannerRight() != null) {
62              child.setBannerRight(parent.getBannerRight().clone());
63              rebaseBannerPaths(child.getBannerRight(), urlContainer);
64          }
65  
66          if (child.isDefaultPublishDate() && parent.getPublishDate() != null) {
67              child.setPublishDate(parent.getPublishDate().clone());
68          }
69  
70          if (child.isDefaultVersion() && parent.getVersion() != null) {
71              child.setVersion(parent.getVersion().clone());
72          }
73  
74          if (child.getEdit() == null && parent.getEdit() != null) {
75              child.setEdit(parent.getEdit());
76          }
77  
78          if (child.getSkin() == null && parent.getSkin() != null) {
79              child.setSkin(parent.getSkin().clone());
80          }
81  
82          child.setPoweredBy(mergePoweredByLists(child.getPoweredBy(), parent.getPoweredBy(), urlContainer));
83  
84          if (parent.getLastModified() > child.getLastModified()) {
85              child.setLastModified(parent.getLastModified());
86          }
87  
88          assembleBodyInheritance(name, child, parent, urlContainer);
89  
90          assembleCustomInheritance(child, parent);
91      }
92  
93      /** {@inheritDoc} */
94      public void resolvePaths(final SiteModel siteModel, final String baseUrl) {
95          if (baseUrl == null) {
96              return;
97          }
98  
99          if (siteModel.getBannerLeft() != null) {
100             relativizeBannerPaths(siteModel.getBannerLeft(), baseUrl);
101         }
102 
103         if (siteModel.getBannerRight() != null) {
104             relativizeBannerPaths(siteModel.getBannerRight(), baseUrl);
105         }
106 
107         for (Logo logo : siteModel.getPoweredBy()) {
108             relativizeLogoPaths(logo, baseUrl);
109         }
110 
111         if (siteModel.getBody() != null) {
112             for (LinkItem linkItem : siteModel.getBody().getLinks()) {
113                 relativizeLinkItemPaths(linkItem, baseUrl);
114             }
115 
116             for (LinkItem linkItem : siteModel.getBody().getBreadcrumbs()) {
117                 relativizeLinkItemPaths(linkItem, baseUrl);
118             }
119 
120             for (Menu menu : siteModel.getBody().getMenus()) {
121                 relativizeMenuPaths(menu.getItems(), baseUrl);
122             }
123         }
124     }
125 
126     /**
127      * Resolves all relative paths between the elements in a banner. The banner element might contain relative paths
128      * to the oldBaseUrl, these are changed to the newBannerUrl.
129      *
130      * @param banner
131      * @param baseUrl
132      */
133     private void relativizeBannerPaths(final Banner banner, final String baseUrl) {
134         // banner has been checked to be not null, both href and src may be empty or null
135         banner.setHref(relativizeLink(banner.getHref(), baseUrl));
136         banner.setSrc(relativizeLink(banner.getSrc(), baseUrl));
137     }
138 
139     private void rebaseBannerPaths(final Banner banner, final URLRebaser urlContainer) {
140         if (banner.getHref() != null) // it may be empty
141         {
142             banner.setHref(urlContainer.rebaseLink(banner.getHref()));
143         }
144 
145         if (banner.getSrc() != null) {
146             banner.setSrc(urlContainer.rebaseLink(banner.getSrc()));
147         }
148     }
149 
150     private void assembleCustomInheritance(final SiteModel child, final SiteModel parent) {
151         if (child.getCustom() == null) {
152             child.setCustom(parent.getCustom());
153         } else {
154             child.setCustom(Xpp3Dom.mergeXpp3Dom((Xpp3Dom) child.getCustom(), (Xpp3Dom) parent.getCustom()));
155         }
156     }
157 
158     private void assembleBodyInheritance(
159             final String name, final SiteModel child, final SiteModel parent, final URLRebaser urlContainer) {
160         Body cBody = child.getBody();
161         Body pBody = parent.getBody();
162 
163         if (cBody != null || pBody != null) {
164             if (cBody == null) {
165                 cBody = new Body();
166                 child.setBody(cBody);
167             }
168 
169             if (pBody == null) {
170                 pBody = new Body();
171             }
172 
173             if (cBody.getHead() == null && pBody.getHead() != null) {
174                 cBody.setHead(pBody.getHead());
175             }
176 
177             cBody.setLinks(mergeLinkItemLists(cBody.getLinks(), pBody.getLinks(), urlContainer, false));
178 
179             if (cBody.getBreadcrumbs().isEmpty() && !pBody.getBreadcrumbs().isEmpty()) {
180                 LinkItem breadcrumb = new LinkItem();
181                 breadcrumb.setName(name);
182                 breadcrumb.setHref("index.html");
183                 cBody.getBreadcrumbs().add(breadcrumb);
184             }
185             cBody.setBreadcrumbs(
186                     mergeLinkItemLists(cBody.getBreadcrumbs(), pBody.getBreadcrumbs(), urlContainer, true));
187 
188             cBody.setMenus(mergeMenus(cBody.getMenus(), pBody.getMenus(), urlContainer));
189 
190             if (cBody.getFooter() == null && pBody.getFooter() != null) {
191                 cBody.setFooter(pBody.getFooter());
192             }
193         }
194     }
195 
196     private List<Menu> mergeMenus(
197             final List<Menu> childMenus, final List<Menu> parentMenus, final URLRebaser urlContainer) {
198         List<Menu> menus = new ArrayList<Menu>(childMenus.size() + parentMenus.size());
199 
200         for (Menu menu : childMenus) {
201             menus.add(menu);
202         }
203 
204         int topCounter = 0;
205         for (Menu menu : parentMenus) {
206             if ("top".equals(menu.getInherit())) {
207                 final Menu clone = menu.clone();
208 
209                 rebaseMenuPaths(clone.getItems(), urlContainer);
210 
211                 menus.add(topCounter, clone);
212                 topCounter++;
213             } else if ("bottom".equals(menu.getInherit())) {
214                 final Menu clone = menu.clone();
215 
216                 rebaseMenuPaths(clone.getItems(), urlContainer);
217 
218                 menus.add(clone);
219             }
220         }
221 
222         return menus;
223     }
224 
225     private void relativizeMenuPaths(final List<MenuItem> items, final String baseUrl) {
226         for (MenuItem item : items) {
227             relativizeLinkItemPaths(item, baseUrl);
228             relativizeMenuPaths(item.getItems(), baseUrl);
229         }
230     }
231 
232     private void rebaseMenuPaths(final List<MenuItem> items, final URLRebaser urlContainer) {
233         for (MenuItem item : items) {
234             rebaseLinkItemPaths(item, urlContainer);
235             rebaseMenuPaths(item.getItems(), urlContainer);
236         }
237     }
238 
239     private void relativizeLinkItemPaths(final LinkItem item, final String baseUrl) {
240         item.setHref(relativizeLink(item.getHref(), baseUrl));
241     }
242 
243     private void rebaseLinkItemPaths(final LinkItem item, final URLRebaser urlContainer) {
244         item.setHref(urlContainer.rebaseLink(item.getHref()));
245     }
246 
247     private void relativizeLogoPaths(final Logo logo, final String baseUrl) {
248         logo.setImg(relativizeLink(logo.getImg(), baseUrl));
249         relativizeLinkItemPaths(logo, baseUrl);
250     }
251 
252     private void rebaseLogoPaths(final Logo logo, final URLRebaser urlContainer) {
253         logo.setImg(urlContainer.rebaseLink(logo.getImg()));
254         rebaseLinkItemPaths(logo, urlContainer);
255     }
256 
257     private List<LinkItem> mergeLinkItemLists(
258             final List<LinkItem> childList,
259             final List<LinkItem> parentList,
260             final URLRebaser urlContainer,
261             boolean cutParentAfterDuplicate) {
262         List<LinkItem> items = new ArrayList<LinkItem>(childList.size() + parentList.size());
263 
264         for (LinkItem item : parentList) {
265             if (!items.contains(item) && !childList.contains(item)) {
266                 final LinkItem clone = item.clone();
267 
268                 rebaseLinkItemPaths(clone, urlContainer);
269 
270                 items.add(clone);
271             } else if (cutParentAfterDuplicate) {
272                 // if a parent item is found in child, ignore next items (case for breadcrumbs)
273                 // merge ( "B > E", "A > B > C > D" ) -> "A > B > E" (notice missing "C > D")
274                 // see https://issues.apache.org/jira/browse/DOXIASITETOOLS-62
275                 break;
276             }
277         }
278 
279         for (LinkItem item : childList) {
280             if (!items.contains(item)) {
281                 items.add(item);
282             }
283         }
284 
285         return items;
286     }
287 
288     private List<Logo> mergePoweredByLists(
289             final List<Logo> childList, final List<Logo> parentList, final URLRebaser urlContainer) {
290         List<Logo> logos = new ArrayList<Logo>(childList.size() + parentList.size());
291 
292         for (Logo logo : parentList) {
293             if (!logos.contains(logo)) {
294                 final Logo clone = logo.clone();
295 
296                 rebaseLogoPaths(clone, urlContainer);
297 
298                 logos.add(clone);
299             }
300         }
301 
302         for (Logo logo : childList) {
303             if (!logos.contains(logo)) {
304                 logos.add(logo);
305             }
306         }
307 
308         return logos;
309     }
310 
311     // relativize only affects absolute links, if the link has the same scheme, host and port
312     // as the base, it is made into a relative link as viewed from the base
313     private String relativizeLink(final String link, final String baseUri) {
314         if (link == null || baseUri == null) {
315             return link;
316         }
317 
318         // this shouldn't be necessary, just to swallow mal-formed hrefs
319         try {
320             final URIPathDescriptor path = new URIPathDescriptor(baseUri, link);
321 
322             return path.relativizeLink().toString();
323         } catch (IllegalArgumentException e) {
324             return link;
325         }
326     }
327 
328     /**
329      * 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
330      * path.
331      */
332     private static class URLRebaser {
333 
334         private final String oldPath;
335 
336         private final String newPath;
337 
338         /**
339          * Construct a URL rebaser.
340          *
341          * @param oldPath the old path.
342          * @param newPath the new path.
343          */
344         URLRebaser(final String oldPath, final String newPath) {
345             this.oldPath = oldPath;
346             this.newPath = newPath;
347         }
348 
349         /**
350          * Get the new path.
351          *
352          * @return the new path.
353          */
354         public String getNewPath() {
355             return this.newPath;
356         }
357 
358         /**
359          * Get the old path.
360          *
361          * @return the old path.
362          */
363         public String getOldPath() {
364             return this.oldPath;
365         }
366 
367         /**
368          * Rebase only affects relative links, a relative link wrt an old base gets translated,
369          * so it points to the same location as viewed from a new base
370          */
371         public String rebaseLink(final String link) {
372             if (link == null || getOldPath() == null) {
373                 return link;
374             }
375 
376             if (link.contains("${project.")) {
377                 throw new IllegalArgumentException("site.xml late interpolation ${project.*} expression found"
378                         + " in link: '" + link + "'. Use early interpolation ${this.*}");
379             }
380 
381             final URIPathDescriptor oldPath = new URIPathDescriptor(getOldPath(), link);
382 
383             return oldPath.rebaseLink(getNewPath()).toString();
384         }
385     }
386 }