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