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