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