Coverage Report - fr.paris.lutece.plugins.releaser.service.SiteService
 
Classes in this File Line Coverage Branch Coverage Complexity
SiteService
2 %
4/181
4 %
4/94
3,217
 
 1  
 /*
 2  
  * Copyright (c) 2002-2017, Mairie de Paris
 3  
  * All rights reserved.
 4  
  *
 5  
  * Redistribution and use in source and binary forms, with or without
 6  
  * modification, are permitted provided that the following conditions
 7  
  * are met:
 8  
  *
 9  
  *  1. Redistributions of source code must retain the above copyright notice
 10  
  *     and the following disclaimer.
 11  
  *
 12  
  *  2. Redistributions in binary form must reproduce the above copyright notice
 13  
  *     and the following disclaimer in the documentation and/or other materials
 14  
  *     provided with the distribution.
 15  
  *
 16  
  *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
 17  
  *     contributors may be used to endorse or promote products derived from
 18  
  *     this software without specific prior written permission.
 19  
  *
 20  
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 21  
  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 22  
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 23  
  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 24  
  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 25  
  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 26  
  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 27  
  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 28  
  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 29  
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 30  
  * POSSIBILITY OF SUCH DAMAGE.
 31  
  *
 32  
  * License 1.0
 33  
  */
 34  
 
 35  
 package fr.paris.lutece.plugins.releaser.service;
 36  
 
 37  
 
 38  
 import java.util.ArrayList;
 39  
 import java.util.HashMap;
 40  
 import java.util.List;
 41  
 import java.util.Locale;
 42  
 import java.util.Map;
 43  
 import java.util.concurrent.ExecutionException;
 44  
 import java.util.concurrent.ExecutorService;
 45  
 import java.util.concurrent.Executors;
 46  
 import java.util.concurrent.Future;
 47  
 
 48  
 import javax.servlet.http.HttpServletRequest;
 49  
 
 50  
 import fr.paris.lutece.plugins.releaser.business.Component;
 51  
 import fr.paris.lutece.plugins.releaser.business.Dependency;
 52  
 import fr.paris.lutece.plugins.releaser.business.Site;
 53  
 import fr.paris.lutece.plugins.releaser.business.SiteHome;
 54  
 import fr.paris.lutece.plugins.releaser.business.WorkflowReleaseContext;
 55  
 import fr.paris.lutece.plugins.releaser.util.ConstanteUtils;
 56  
 import fr.paris.lutece.plugins.releaser.util.ReleaserUtils;
 57  
 import fr.paris.lutece.plugins.releaser.util.pom.PomParser;
 58  
 import fr.paris.lutece.plugins.releaser.util.svn.SvnSiteService;
 59  
 import fr.paris.lutece.plugins.releaser.util.version.Version;
 60  
 import fr.paris.lutece.plugins.releaser.util.version.VersionParsingException;
 61  
 import fr.paris.lutece.portal.business.user.AdminUser;
 62  
 import fr.paris.lutece.portal.service.datastore.DatastoreService;
 63  
 import fr.paris.lutece.portal.service.i18n.I18nService;
 64  
 import fr.paris.lutece.portal.service.util.AppLogService;
 65  
 
 66  
 /**
 67  
  * SiteService
 68  
  */
 69  0
 public class SiteService
 70  
 {
 71  
     private static final int NB_POOL_REMOTE_INFORMATION = 20;
 72  
     private static final String MESSAGE_AVOID_SNAPSHOT = "releaser.message.avoidSnapshot";
 73  
     private static final String MESSAGE_UPGRADE_SELECTED = "releaser.message.upgradeSelected";
 74  
     private static final String MESSAGE_TO_BE_RELEASED = "releaser.message.toBeReleased";
 75  
     private static final String MESSAGE_MORE_RECENT_VERSION_AVAILABLE = "releaser.message.moreRecentVersionAvailable";
 76  
     private static final String MESSAGE_AN_RELEASE_VERSION_ALREADY_EXIST = "releaser.message.releleaseVersionAlreadyExist";
 77  
     
 78  
 
 79  
     /**
 80  
      * Load a site from its id
 81  
      * 
 82  
      * @param nSiteId
 83  
      *            The site id
 84  
      * @return A site object
 85  
      */
 86  
     public static Site getSite( int nSiteId ,HttpServletRequest request,Locale locale )
 87  
     {
 88  0
         Site site = SiteHome.findByPrimaryKey( nSiteId );
 89  0
         String strPom = SvnSiteService.fetchPom( site.getScmUrl( ) + "/pom.xml" ,request,locale);
 90  0
         if ( strPom != null )
 91  
         {
 92  0
             PomParser parser = new PomParser( );
 93  0
             parser.parse( site, strPom );
 94  0
             initSite( site,request,locale );
 95  
         }
 96  0
         return site;
 97  
     }
 98  
 
 99  
     
 100  
     private static void initSite( Site site,HttpServletRequest request,Locale locale )
 101  
     {
 102  
         // Find last release in the repository
 103  0
         String strLastReleaseVersion = SvnSiteService.getLastRelease( site.getArtifactId() , site.getScmUrl(),request,locale );
 104  0
         site.setLastReleaseVersion( strLastReleaseVersion );
 105  
         
 106  
         // To find next releases
 107  
         
 108  0
         String strOriginVersion = getOriginVersion( strLastReleaseVersion, site.getVersion() );
 109  
         
 110  0
         site.setNextReleaseVersion( Version.getReleaseVersion( strOriginVersion ));
 111  0
         site.setNextSnapshotVersion( Version.getNextSnapshotVersion( strOriginVersion ) );
 112  0
         site.setTargetVersions( Version.getNextReleaseVersions( strOriginVersion ));
 113  
 
 114  0
         initComponents( site );
 115  0
     }
 116  
     
 117  
     /**
 118  
      * Define which version between last released or current snapshot should be the origin
 119  
      * for next release versions. Ex of cases :<br>
 120  
      * last release : 3.2.1         current : 4.0.0-SNAPSHOT  --> current> <br>
 121  
      * last release : 3.2.1         current : 3.2.2-SNAPSHOT  --> last or current <br>
 122  
      * last release : missing       current : 1.0.0-SNAPSHOT  --> current <br>
 123  
      * last release : 3.2.1-RC-02   current : 3.2.1-SNAPSHOT  --> last <br>
 124  
      * 
 125  
      * @param strLastRelease The last release
 126  
      * @param strCurrentVersion The current release
 127  
      * @return The origin version
 128  
      */
 129  
     public static String getOriginVersion( String strLastRelease , String strCurrentVersion )
 130  
     {
 131  4
         String strOriginVersion = strCurrentVersion;
 132  4
         if( ( strLastRelease != null ) && Version.isCandidate( strLastRelease ))
 133  
         {
 134  1
             strOriginVersion = strLastRelease;
 135  
         }
 136  4
         return strOriginVersion;
 137  
     }
 138  
     
 139  
    
 140  
     
 141  
     /**
 142  
      * Initialize the component list for a given site
 143  
      * 
 144  
      * @param site
 145  
      *            The site
 146  
      */
 147  
     private static void initComponents( Site site )
 148  
     {
 149  0
         for ( Dependency dependency : site.getCurrentDependencies( ) )
 150  
         {
 151  0
             Component component = new Component( );
 152  
             
 153  0
             component.setIsProject( isProjectComponent( site, dependency.getArtifactId( ) ) );
 154  0
             component.setArtifactId( dependency.getArtifactId( ) );
 155  0
             component.setGroupId( dependency.getGroupId( ) );
 156  0
             component.setType( dependency.getType( ) );
 157  0
             component.setCurrentVersion( dependency.getVersion( ) );
 158  0
             site.addComponent( component );
 159  0
         }
 160  
         
 161  0
         ExecutorService executor = Executors.newFixedThreadPool(NB_POOL_REMOTE_INFORMATION);
 162  
         
 163  0
         List<Future> futures = 
 164  0
                 new ArrayList<Future>( site.getCurrentDependencies( ).size( ));
 165  
               
 166  
            
 167  0
         for ( Component component : site.getComponents( ) )
 168  
         {
 169  0
             futures.add(executor.submit(new GetRemoteInformationsTask( component )));
 170  0
         }
 171  
        
 172  
         
 173  
         //wait all futures stop before continue
 174  0
         for (Future future : futures) {
 175  
             
 176  
             try
 177  
             {
 178  0
                 future.get();
 179  
             }
 180  0
             catch( InterruptedException e )
 181  
             {
 182  0
               AppLogService.error( e );
 183  
             }
 184  0
             catch( ExecutionException e )
 185  
             {
 186  
                 // TODO Auto-generated catch block
 187  0
                 e.printStackTrace();
 188  0
             }
 189  
               
 190  0
           }
 191  
               
 192  0
         executor.shutdown();     
 193  
             
 194  0
         for ( Component component : site.getComponents( ) )
 195  
         {
 196  
            
 197  0
             ComponentService.getService( ).updateRemoteInformations( component );
 198  0
             defineTargetVersion( component );
 199  0
             defineNextSnapshotVersion( component );
 200  0
         }
 201  
            
 202  
             
 203  
           
 204  
         
 205  0
     }
 206  
     
 207  
     
 208  
 
 209  
     /**
 210  
      * Define the target version for a given component : <br>
 211  
      * - current version for non project component <br>
 212  
      * - nex release for project component
 213  
      * 
 214  
      * @param component
 215  
      *            The component
 216  
      */
 217  
     private static void defineTargetVersion( Component component )
 218  
     {
 219  0
         if ( component.isProject( ) && component.isSnapshotVersion( ) )
 220  
         {
 221  0
             if( component.getLastAvailableVersion( )!=null && !component.getCurrentVersion( ).equals( component.getLastAvailableSnapshotVersion( )) || component.isTheme( ) )
 222  
             {
 223  0
                 component.setTargetVersion( component.getLastAvailableVersion( ) );  
 224  
             }
 225  
             else
 226  
             {
 227  0
                 component.setTargetVersions( Version.getNextReleaseVersions( component.getCurrentVersion() ));
 228  0
                 String strTargetVersion = Version.getReleaseVersion( component.getCurrentVersion() );
 229  0
                 component.setTargetVersion( strTargetVersion );
 230  0
             }
 231  
         }
 232  
         else
 233  
         {
 234  0
             component.setTargetVersion( component.getCurrentVersion( ) );
 235  
         }
 236  0
     }
 237  
 
 238  
     /**
 239  
      * Define the next snapshot version for a given component
 240  
      * 
 241  
      * @param component
 242  
      *            The component
 243  
      */
 244  
     private static void defineNextSnapshotVersion( Component component )
 245  
     {
 246  0
         String strNextSnapshotVersion = Version.NOT_AVAILABLE;
 247  0
         if(  !component.getCurrentVersion( ).equals( component.getLastAvailableSnapshotVersion( )) || component.isTheme( ))
 248  
         {
 249  0
             component.setNextSnapshotVersion( component.getLastAvailableSnapshotVersion( ) ); 
 250  
         }
 251  
         else
 252  
         {
 253  
             try
 254  
             {
 255  0
                 Version version = Version.parse( component.getTargetVersion( ) );
 256  0
                 boolean bSnapshot = true;
 257  0
                 strNextSnapshotVersion = version.nextPatch( bSnapshot ).toString( );
 258  
             }
 259  0
             catch( VersionParsingException ex )
 260  
             {
 261  0
                 AppLogService.error( "Error parsing version for component " + component.getArtifactId( ) + " : " + ex.getMessage( ), ex );
 262  
     
 263  0
             }
 264  0
             component.setNextSnapshotVersion( strNextSnapshotVersion );
 265  
         }
 266  
 
 267  0
     }
 268  
 
 269  
     private static boolean isProjectComponent( Site site, String strArtifactId )
 270  
     {
 271  0
         return new Boolean(DatastoreService.getDataValue( getComponetIsProjectDataKey( site, strArtifactId ), Boolean.FALSE.toString( ) ));
 272  
     }
 273  
     
 274  
     public  static void updateComponentAsProjectStatus( Site site, String strArtifactId ,Boolean bIsProject)
 275  
     {
 276  0
        DatastoreService.setDataValue( getComponetIsProjectDataKey(  site, strArtifactId),bIsProject.toString( ));
 277  
        
 278  0
     }
 279  
     
 280  
     public  static void removeComponentAsProjectBySite( int  nIdSite )
 281  
     {
 282  0
         DatastoreService.removeDataByPrefix( getPrefixIsProjectDataKey( nIdSite ) );
 283  
        
 284  0
     }
 285  
     
 286  
    private static String getComponetIsProjectDataKey(  Site site, String strArtifactId )
 287  
    {
 288  
        
 289  0
        return getPrefixIsProjectDataKey( site.getId( ) ) +strArtifactId;
 290  
        
 291  
    }
 292  
    
 293  
    private static String getPrefixIsProjectDataKey(  int  nIdSite  )
 294  
    {
 295  
        
 296  0
        return ConstanteUtils.CONSTANTE_COMPONENT_PROJECT_PREFIX+"_"+nIdSite+"_";
 297  
        
 298  
    }
 299  
     
 300  
 
 301  
     /**
 302  
      * Build release comments for a given site
 303  
      * 
 304  
      * @param site
 305  
      *            The site
 306  
      * @param locale
 307  
      *            The locale to use for comments
 308  
      */
 309  
     public static void buildComments( Site site, Locale locale )
 310  
     {
 311  0
         for ( Component component : site.getComponents( ) )
 312  
         {
 313  0
             component.resetComments( );
 314  0
             buildReleaseComments( component, locale );
 315  0
         }
 316  0
     }
 317  
 
 318  
     /**
 319  
      * Build release comments for a given component
 320  
      * 
 321  
      * @param component
 322  
      *            The component
 323  
      * @param locale
 324  
      *            The locale to use for comments
 325  
      */
 326  
     private static void buildReleaseComments( Component component, Locale locale )
 327  
     {
 328  
         
 329  0
         if ( !component.isProject( ) )
 330  
         {
 331  0
             if ( Version.isSnapshot( component.getTargetVersion( ) ) )
 332  
             {
 333  0
                 String strComment = I18nService.getLocalizedString( MESSAGE_AVOID_SNAPSHOT, locale );
 334  0
                 component.addReleaseComment( strComment );
 335  0
             }
 336  0
             else if (component.getLastAvailableVersion( )!=null && !component.getTargetVersion( ).equals( component.getLastAvailableVersion( )) )
 337  
             {
 338  0
                 String [ ] arguments = {
 339  0
                         component.getLastAvailableVersion( )
 340  
                     };
 341  0
                  String strComment = I18nService.getLocalizedString( MESSAGE_MORE_RECENT_VERSION_AVAILABLE, arguments, locale );
 342  
                   
 343  0
                 component.addReleaseComment( strComment );
 344  0
             }
 345  
         }
 346  
         else
 347  
         {
 348  0
             if(component.isSnapshotVersion( ))
 349  
             {
 350  0
                 if(  !component.getCurrentVersion( ).equals( component.getLastAvailableSnapshotVersion( )) )
 351  
                 {
 352  
                     
 353  0
                         String [ ] arguments = {
 354  0
                             component.getLastAvailableVersion( )
 355  
                         };
 356  0
                         String strComment = I18nService.getLocalizedString( MESSAGE_UPGRADE_SELECTED, arguments, locale );
 357  0
                         component.addReleaseComment( strComment );
 358  0
                 }
 359  0
                 else if(  !component.shouldBeReleased( ) && !component.isDowngrade( ) )
 360  
                 {
 361  
                     
 362  0
                         String [ ] arguments = {
 363  0
                             component.getLastAvailableVersion( )
 364  
                         };
 365  0
                         String strComment = I18nService.getLocalizedString( MESSAGE_AN_RELEASE_VERSION_ALREADY_EXIST, arguments, locale );
 366  0
                         component.addReleaseComment( strComment );
 367  0
                 }
 368  
            
 369  0
             else if(component.shouldBeReleased( ))
 370  
             {
 371  
             
 372  0
                 String strComment = I18nService.getLocalizedString( MESSAGE_TO_BE_RELEASED, locale );
 373  0
                 component.addReleaseComment( strComment );
 374  0
               }
 375  
             }
 376  0
             else if ( !component.getCurrentVersion( ).equals( component.getLastAvailableVersion( )) )
 377  
             {
 378  0
                 String [ ] arguments = {
 379  0
                         component.getLastAvailableVersion( )
 380  
                     };
 381  0
                  String strComment = I18nService.getLocalizedString( MESSAGE_MORE_RECENT_VERSION_AVAILABLE, arguments, locale );
 382  
                   
 383  0
                 component.addReleaseComment( strComment );
 384  
             }
 385  
         }
 386  0
     }
 387  
 
 388  
     
 389  
    
 390  
 
 391  
     public static void upgradeComponent( Site site, String strArtifactId )
 392  
     {
 393  0
         for ( Component component : site.getComponents( ) )
 394  
         {
 395  0
             if ( component.getArtifactId( ).equals( strArtifactId ) )
 396  
             {
 397  0
                 component.setTargetVersion( component.getLastAvailableVersion( ) );
 398  0
                 component.setUpgrade( true );
 399  
             }
 400  0
         }
 401  0
     }
 402  
     
 403  
     
 404  
     public static void cancelUpgradeComponent( Site site, String strArtifactId )
 405  
     {
 406  0
         for ( Component component : site.getComponents( ) )
 407  
         {
 408  0
             if ( component.getArtifactId( ).equals( strArtifactId ) )
 409  
             {
 410  
            
 411  0
                 component.setTargetVersion(  component.getCurrentVersion( ) );
 412  0
                 component.setUpgrade( false );
 413  
             }
 414  0
         }
 415  0
     }
 416  
     public static void downgradeComponent( Site site, String strArtifactId )
 417  
     {
 418  0
         for ( Component component : site.getComponents( ) )
 419  
         {
 420  0
             if ( component.getArtifactId( ).equals( strArtifactId ) && component.isSnapshotVersion( ))
 421  
             {
 422  0
                 component.setTargetVersion( component.getLastAvailableVersion( ) );
 423  0
                 component.setNextSnapshotVersion( component.getLastAvailableSnapshotVersion( ) );
 424  0
                 component.setDowngrade( true );
 425  
             }
 426  0
         }
 427  0
     }
 428  
     
 429  
     public static void cancelDowngradeComponent( Site site, String strArtifactId )
 430  
     {
 431  0
         for ( Component component : site.getComponents( ) )
 432  
         {
 433  0
             if ( component.getArtifactId( ).equals( strArtifactId ) && component.isSnapshotVersion( ))
 434  
             {
 435  0
                 component.setDowngrade( false );
 436  0
                 defineTargetVersion( component );
 437  0
                 defineNextSnapshotVersion( component );
 438  
             }
 439  0
         }
 440  0
     }
 441  
     
 442  
     public static int releaseComponent( Site site, String strArtifactId,Locale locale,AdminUser user,HttpServletRequest request)
 443  
     {
 444  0
         for ( Component component : site.getComponents( ) )
 445  
         {
 446  0
             if ( component.getArtifactId( ).equals( strArtifactId )  && component.shouldBeReleased( ))
 447  
             {
 448  
                 //Release component
 449  0
                return  ComponentService.getService( ).release( component, locale,user,request );
 450  
                 
 451  
             }
 452  0
         }
 453  0
         return ConstanteUtils.CONSTANTE_ID_NULL;
 454  
     }
 455  
     
 456  
     
 457  
     public static Map<String, Integer> releaseSite( Site site,Locale locale,AdminUser user,HttpServletRequest request)
 458  
     {
 459  0
         Map<String, Integer> mapResultContext=new HashMap<String, Integer>();
 460  
         
 461  
         
 462  
         Integer nIdWfContext;
 463  
         //Release all snapshot compnent
 464  0
         for ( Component component : site.getComponents( ) )
 465  
         {
 466  0
             if ( component.isProject( ) && component.shouldBeReleased( )&& !component.isTheme( ))
 467  
             {
 468  0
                 component.setErrorLastRelease( false );
 469  0
                 nIdWfContext=ComponentService.getService( ).release( component, locale,user,request );
 470  0
                 mapResultContext.put( component.getArtifactId( ), nIdWfContext );
 471  
                
 472  
             }
 473  0
         }
 474  
         
 475  
         
 476  
        
 477  0
         WorkflowReleaseContext context=new WorkflowReleaseContext( );
 478  0
         context.setSite( site );
 479  0
         context.setReleaserUser( ReleaserUtils.getReleaserUser( request, locale ) );
 480  
        
 481  0
         int nIdWorkflow=WorkflowReleaseContextService.getService( ).getIdWorkflow( context );
 482  0
         WorkflowReleaseContextService.getService( ).addWorkflowReleaseContext( context );
 483  
         //start
 484  0
         WorkflowReleaseContextService.getService( ).startWorkflowReleaseContext( context, nIdWorkflow, locale, request, user );
 485  
         //Add wf site context
 486  0
         mapResultContext.put( site.getArtifactId( ), context.getId( ) );
 487  
         
 488  0
          return mapResultContext;
 489  
         
 490  
        
 491  
         
 492  
         
 493  
     }
 494  
     
 495  
     
 496  
     
 497  
 
 498  
     /**
 499  
      * Add or Remove a component from the project's components list
 500  
      * 
 501  
      * @param site
 502  
      *            The site
 503  
      * @param strArtifactId
 504  
      *            The component artifact id
 505  
      */
 506  
     public static void toggleProjectComponent( Site site, String strArtifactId )
 507  
     {
 508  0
         for ( Component component : site.getComponents( ) )
 509  
         {
 510  0
             if ( component.getArtifactId( ).equals( strArtifactId ) )
 511  
             {
 512  0
                 component.setIsProject( !component.isProject( ) );
 513  0
                 updateComponentAsProjectStatus( site, strArtifactId, component.isProject( ) );
 514  
             }
 515  0
         }
 516  0
     }
 517  
 
 518  
     /**
 519  
      * Change the next release version
 520  
      * 
 521  
      * @param site
 522  
      *            The site
 523  
      * @param strArtifactId
 524  
      *            The component artifact id
 525  
      */
 526  
     public static void changeNextReleaseVersion( Site site, String strArtifactId )
 527  
     {
 528  0
         for ( Component component : site.getComponents( ) )
 529  
         {
 530  0
             if ( component.getArtifactId( ).equals( strArtifactId ) )
 531  
             {
 532  0
                 ComponentService.getService( ).changeNextReleaseVersion( component );
 533  
             }
 534  0
         }
 535  0
     }
 536  
 
 537  
     /**
 538  
      * Change the next release version
 539  
      * 
 540  
      * @param site
 541  
      *            The site
 542  
      */
 543  
     public static void changeNextReleaseVersion( Site site )
 544  
     {
 545  0
         List<String> listTargetVersions = site.getTargetVersions();
 546  0
         int nNewIndex = (site.getTargetVersionIndex() + 1) % listTargetVersions.size();
 547  0
         String strTargetVersion = listTargetVersions.get( nNewIndex );
 548  0
         site.setNextReleaseVersion( strTargetVersion );
 549  0
         site.setTargetVersionIndex( nNewIndex );
 550  0
         site.setNextSnapshotVersion( Version.getNextSnapshotVersion( strTargetVersion ));
 551  0
     }
 552  
 
 553  
     
 554  
     
 555  
     /**
 556  
      * Generate the pom.xml file for a given site
 557  
      * 
 558  
      * @param site
 559  
      *            The site
 560  
      * @return The pom.xml content
 561  
      */
 562  
     public String generateTargetPOM( Site site )
 563  
     {
 564  0
         throw new UnsupportedOperationException( "Not supported yet." ); // To change body of generated methods, choose Tools | Templates.
 565  
     }
 566  
     
 567  
 
 568  
 }