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.getEdit() == null && parent.getEdit() != null )
82          {
83              child.setEdit( parent.getEdit() );
84          }
85  
86          if ( child.getSkin() == null && parent.getSkin() != null )
87          {
88              child.setSkin( parent.getSkin().clone() );
89          }
90  
91          child.setPoweredBy( mergePoweredByLists( child.getPoweredBy(), parent.getPoweredBy(), urlContainer ) );
92  
93          if ( parent.getLastModified() > child.getLastModified() )
94          {
95              child.setLastModified( parent.getLastModified() );
96          }
97  
98          if ( child.getGoogleAdSenseClient() == null && parent.getGoogleAdSenseClient() != null )
99          {
100             child.setGoogleAdSenseClient( parent.getGoogleAdSenseClient() );
101         }
102 
103         if ( child.getGoogleAdSenseSlot() == null && parent.getGoogleAdSenseSlot() != null )
104         {
105             child.setGoogleAdSenseSlot( parent.getGoogleAdSenseSlot() );
106         }
107 
108         if ( child.getGoogleAnalyticsAccountId() == null && parent.getGoogleAnalyticsAccountId() != null )
109         {
110             child.setGoogleAnalyticsAccountId( parent.getGoogleAnalyticsAccountId() );
111         }
112 
113         assembleBodyInheritance( name, child, parent, urlContainer );
114 
115         assembleCustomInheritance( child, parent );
116     }
117 
118     /** {@inheritDoc} */
119     public void resolvePaths( final DecorationModel decoration, final String baseUrl )
120     {
121         if ( baseUrl == null )
122         {
123             return;
124         }
125 
126         if ( decoration.getBannerLeft() != null )
127         {
128             relativizeBannerPaths( decoration.getBannerLeft(), baseUrl );
129         }
130 
131         if ( decoration.getBannerRight() != null )
132         {
133             relativizeBannerPaths( decoration.getBannerRight(), baseUrl );
134         }
135 
136         for ( Logo logo : decoration.getPoweredBy() )
137         {
138             relativizeLogoPaths( logo, baseUrl );
139         }
140 
141         if ( decoration.getBody() != null )
142         {
143             for ( LinkItem linkItem : decoration.getBody().getLinks() )
144             {
145                 relativizeLinkItemPaths( linkItem, baseUrl );
146             }
147 
148             for ( LinkItem linkItem : decoration.getBody().getBreadcrumbs() )
149             {
150                 relativizeLinkItemPaths( linkItem, baseUrl );
151             }
152 
153             for ( Menu menu : decoration.getBody().getMenus() )
154             {
155                 relativizeMenuPaths( menu.getItems(), baseUrl );
156             }
157         }
158     }
159 
160     /**
161      * Resolves all relative paths between the elements in a banner. The banner element might contain relative paths
162      * to the oldBaseUrl, these are changed to the newBannerUrl.
163      *
164      * @param banner
165      * @param baseUrl
166      */
167     private void relativizeBannerPaths( final Banner banner, final String baseUrl )
168     {
169         // banner has been checked to be not null, both href and src may be empty or null
170         banner.setHref( relativizeLink( banner.getHref(), baseUrl ) );
171         banner.setSrc( relativizeLink( banner.getSrc(), baseUrl ) );
172     }
173 
174     private void rebaseBannerPaths( final Banner banner, final URLRebaser urlContainer )
175     {
176         if ( banner.getHref() != null ) // it may be empty
177         {
178             banner.setHref( urlContainer.rebaseLink( banner.getHref() ) );
179         }
180 
181         if ( banner.getSrc() != null )
182         {
183             banner.setSrc( urlContainer.rebaseLink( banner.getSrc() ) );
184         }
185     }
186 
187     private void assembleCustomInheritance( final DecorationModel child, final DecorationModel parent )
188     {
189         if ( child.getCustom() == null )
190         {
191             child.setCustom( parent.getCustom() );
192         }
193         else
194         {
195             child.setCustom( Xpp3Dom.mergeXpp3Dom( (Xpp3Dom) child.getCustom(), (Xpp3Dom) parent.getCustom() ) );
196         }
197     }
198 
199     private void assembleBodyInheritance( final String name, final DecorationModel child, final DecorationModel parent,
200                                           final URLRebaser urlContainer )
201     {
202         Body cBody = child.getBody();
203         Body pBody = parent.getBody();
204 
205         if ( cBody != null || pBody != null )
206         {
207             if ( cBody == null )
208             {
209                 cBody = new Body();
210                 child.setBody( cBody );
211             }
212 
213             if ( pBody == null )
214             {
215                 pBody = new Body();
216             }
217 
218             if ( cBody.getHead() == null && pBody.getHead() != null )
219             {
220                 cBody.setHead( 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( "index.html" );
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 URLRebaser 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 URLRebaser 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 URLRebaser urlContainer )
303     {
304         item.setHref( urlContainer.rebaseLink( item.getHref() ) );
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 URLRebaser urlContainer )
314     {
315         logo.setImg( urlContainer.rebaseLink( logo.getImg() ) );
316         rebaseLinkItemPaths( logo, urlContainer );
317     }
318 
319     private List<LinkItem> mergeLinkItemLists( final List<LinkItem> childList, final List<LinkItem> parentList,
320                                                final URLRebaser 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 https://issues.apache.org/jira/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 URLRebaser 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     // relativize only affects absolute links, if the link has the same scheme, host and port
383     // as the base, it is made into a relative link as viewed from the base
384     private String relativizeLink( final String link, final String baseUri )
385     {
386         if ( link == null || baseUri == null )
387         {
388             return link;
389         }
390 
391         // this shouldn't be necessary, just to swallow mal-formed hrefs
392         try
393         {
394             final URIPathDescriptor path = new URIPathDescriptor( baseUri, link );
395 
396             return path.relativizeLink().toString();
397         }
398         catch ( IllegalArgumentException e )
399         {
400             return link;
401         }
402     }
403 
404     /**
405      * 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
406      * path.
407      */
408     private static class URLRebaser
409     {
410 
411         private final String oldPath;
412 
413         private final String newPath;
414 
415         /**
416          * Construct a URL rebaser.
417          *
418          * @param oldPath the old path.
419          * @param newPath the new path.
420          */
421         public URLRebaser( final String oldPath, final String newPath )
422         {
423             this.oldPath = oldPath;
424             this.newPath = newPath;
425         }
426 
427         /**
428          * Get the new path.
429          *
430          * @return the new path.
431          */
432         public String getNewPath()
433         {
434             return this.newPath;
435         }
436 
437         /**
438          * Get the old path.
439          *
440          * @return the old path.
441          */
442         public String getOldPath()
443         {
444             return this.oldPath;
445         }
446 
447         /**
448          * Rebase only affects relative links, a relative link wrt an old base gets translated,
449          * so it points to the same location as viewed from a new base
450          */
451         public String rebaseLink( final String link )
452         {
453             if ( link == null || getOldPath() == null )
454             {
455                 return link;
456             }
457 
458             if ( link.contains( "${project." ) )
459             {
460                 throw new IllegalArgumentException( "site.xml late interpolation ${project.*} expression found"
461                     + " in link: '" + link + "'. Use early interpolation ${this.*}" );
462             }
463 
464             final URIPathDescriptor oldPath = new URIPathDescriptor( getOldPath(), link );
465 
466             return oldPath.rebaseLink( getNewPath() ).toString();
467         }
468     }
469 }