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