View Javadoc
1   /*
2    * Copyright (c) 2002-2020, City of 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  package fr.paris.lutece.plugins.lutecetools.service;
35  
36  import java.io.IOException;
37  import java.io.StringReader;
38  import java.util.ArrayList;
39  import java.util.Collections;
40  import java.util.Date;
41  import java.util.List;
42  import java.util.regex.Matcher;
43  import java.util.regex.Pattern;
44  
45  import javax.xml.parsers.ParserConfigurationException;
46  import javax.xml.parsers.SAXParser;
47  import javax.xml.parsers.SAXParserFactory;
48  
49  import org.xml.sax.InputSource;
50  import org.xml.sax.SAXException;
51  
52  import fr.paris.lutece.plugins.lutecetools.business.Component;
53  import fr.paris.lutece.plugins.lutecetools.business.Dependency;
54  import fr.paris.lutece.plugins.lutecetools.service.version.VersionUtils;
55  import fr.paris.lutece.plugins.lutecetools.web.rs.Constants;
56  import fr.paris.lutece.portal.service.datastore.DatastoreService;
57  import fr.paris.lutece.portal.service.spring.SpringContextService;
58  import fr.paris.lutece.portal.service.util.AppLogService;
59  import fr.paris.lutece.portal.service.util.AppPropertiesService;
60  import fr.paris.lutece.util.httpaccess.HttpAccess;
61  import fr.paris.lutece.util.httpaccess.HttpAccessException;
62  
63  /**
64   * Version Service
65   */
66  public final class MavenRepoService
67  {
68      // Maven repos URLs
69  	private static final String PROPERTY_MAVEN_URL_PREFIX = "lutecetools.maven.repository.url";
70  	private static final List<String> PROPERTIES_MAVEN_URL = AppPropertiesService.getKeys( PROPERTY_MAVEN_URL_PREFIX );
71  		
72  	// Maven Releases Paths
73  	private static final String PROPERTY_RELEASES_PATH_PREFIX = "lutecetools.maven.repository.releases";
74  	private static final List<String> PROPERTIES_RELEASES_PATH = AppPropertiesService.getKeys( PROPERTY_RELEASES_PATH_PREFIX );
75  	
76  	// Maven Sanapshots Paths
77      private static final String PROPERTY_SNAPSHOTS_PATH_PREFIX = "lutecetools.maven.repository.snapshots";
78  	private static final List<String> PROPERTIES_SNAPSHOTS_PATH = AppPropertiesService.getKeys( PROPERTY_SNAPSHOTS_PATH_PREFIX );
79  
80  	// Maven path - to see the tree structure
81      private static final String PROPERTY_MAVEN_REPO_PATH_TREE = "lutecetools.maven.repository.treestructure.path";
82  	private static final String PATH_MAVEN_REPO_TREE = AppPropertiesService.getProperty( PROPERTY_MAVEN_REPO_PATH_TREE );
83  
84      // Maven path - To get file
85  	private static final String PROPERTY_MAVEN_REPO_PATH_FILE = "lutecetools.maven.repository.getfile.path";
86      private static final String PATH_MAVEN_REPO_FILE = AppPropertiesService.getProperty( PROPERTY_MAVEN_REPO_PATH_FILE );	
87  
88      // Path Plugins
89      private static final String PROPERTY_MAVEN_PATH_PLUGINS = "lutecetools.maven.repository.path.plugins";
90      private static final String URL_MAVEN_PATH_PLUGINS = AppPropertiesService.getProperty( PROPERTY_MAVEN_PATH_PLUGINS );
91      
92      // Path Pom site
93      private static final String PROPERTY_MAVEN_PATH_SITE_POM = "lutecetools.maven.repository.path.site-pom";
94      private static final String URL_MAVEN_PATH_SITE_POM = AppPropertiesService.getProperty( PROPERTY_MAVEN_PATH_SITE_POM );
95      
96      // Path Lutece core
97      private static final String PROPERTY_MAVEN_PATH_CORE = "lutecetools.maven.repository.path.core";
98      private static final String URL_MAVEN_PATH_CORE = AppPropertiesService.getProperty( PROPERTY_MAVEN_PATH_CORE );
99      
100     // Path Themes
101     private static final String PROPERTY_MAVEN_PATH_THEMES = "lutecetools.maven.repository.path.themes";
102     private static final String URL_MAVEN_PATH_THEMES = AppPropertiesService.getProperty( PROPERTY_MAVEN_PATH_THEMES );
103 
104     private static final String KEY_SITE_POM_VERSION = "lutecetools.pom.site.version";
105     private static final String RELEASE_NOT_FOUND = "Release not found";
106 
107     private static final String EXCEPTION_MESSAGE = "LuteceTools - MavenRepoService : Error retrieving pom infos : ";
108     private static final String PROPERTY_NON_AVAILABLE = "lutecetools.nonAvailable";
109     private static final String NON_AVAILABLE = AppPropertiesService.getProperty( PROPERTY_NON_AVAILABLE );
110     private static final String PROPERTY_UPDATE_DELAY = "lutecetools.update.delay";
111     private static final long DEFAULT_UPDATE_DELAY = 7200000L; // 2 hours
112     private static final long UPDATE_DELAY = AppPropertiesService.getPropertyLong( PROPERTY_UPDATE_DELAY,
113             DEFAULT_UPDATE_DELAY );
114 
115     // Tags
116     private static final String TAG_LUTECE_CORE = "lutece-core";
117 
118     private static MavenRepoService _singleton;
119     private static StringBuilder _sbLogs = new StringBuilder( );
120     private static List<ComponentInfoFiller> _listComponentFiller = new ArrayList<>( );
121 
122     /**
123      * Private constructor
124      */
125     private MavenRepoService( )
126     {
127     }
128 
129     /**
130      * Returns the unique instance
131      * 
132      * @return the unique instance
133      */
134     public static synchronized MavenRepoService instance( )
135     {
136         if ( _singleton == null )
137         {
138             _singleton = new MavenRepoService( );
139             _listComponentFiller = SpringContextService.getBeansOfType( ComponentInfoFiller.class );
140             AppLogService.info( "Lutecetools : registering info fillers" );
141             for ( ComponentInfoFiller filler : _listComponentFiller )
142             {
143                 AppLogService.info( " * " + filler.getName( ) );
144             }
145         }
146 
147         return _singleton;
148     }
149     
150     private String getAvailableUrl(List<String> listRepoPathTypeProperties, String strComponentPath, String strArtifactId)
151     {
152     	HttpAccess httpAccess = new HttpAccess( );
153     	String strHtml = null;
154     	
155     	for (String strUrlProperty : PROPERTIES_MAVEN_URL)
156     	{    		
157     		String[] tabUrl = strUrlProperty.split("\\.");
158     		for (String strTypeProperty : listRepoPathTypeProperties)
159         	{
160     			String[] tabRepoType = strTypeProperty.split("\\.");
161     			if ( tabRepoType[tabRepoType.length - 1].equals( tabUrl[tabUrl.length - 1] ) )
162     			{
163     				String url =  AppPropertiesService.getProperty(strUrlProperty) + PATH_MAVEN_REPO_TREE 
164     						+ AppPropertiesService.getProperty(strTypeProperty) + strComponentPath;
165     				
166     				if (strArtifactId != null && !strArtifactId.isEmpty())
167     	    			url = url + strArtifactId;
168     				
169     				try
170     	            {  
171     	    			strHtml = httpAccess.doGet( url );
172     	            }
173     	            catch ( HttpAccessException e )
174     	            {
175     	                AppLogService.info( "LuteceTools - MavenRepoService : Not available url : " + url );
176     	            }
177     	    		
178     	    		if ( strHtml != null && !strHtml.isEmpty())
179     	    		{
180     	    			return url;
181     	    		} 
182     			}	
183     			
184         	}    		   			
185     	}     
186     	
187     	return null;
188     }
189     
190     /**
191      * Set the component's version
192      * 
193      * @param component The component
194      */
195     public void setReleaseVersion( Dependency component )
196     {
197     	String strUrls = getAvailableUrl(PROPERTIES_RELEASES_PATH, URL_MAVEN_PATH_PLUGINS, component.getArtifactId( ));
198     	component.setVersion( getVersion( strUrls ) );  
199     }
200 
201     /**
202      * Set the POM site version
203      */
204     public void setPomSiteVersion( )
205     {
206     	String strUrl = getAvailableUrl(PROPERTIES_RELEASES_PATH, URL_MAVEN_PATH_SITE_POM, null);
207         DatastoreService.setDataValue( KEY_SITE_POM_VERSION, getVersion( strUrl ) );
208     }
209 
210     /**
211      * Retrieve a version from the maven repository
212      * 
213      * @param strUrl The maven repository URL
214      * @return The version
215      */
216     public String getVersion( String strUrl )
217     {
218         String strVersion = RELEASE_NOT_FOUND;
219 
220     	if (strUrl != null && !strUrl.isEmpty())
221     	{
222     		try
223             {
224                 HttpAccess httpAccess = new HttpAccess( );
225                 String strHtml = httpAccess.doGet( strUrl );
226 
227                 List<String> listElement = getAnchorsList( strHtml );
228                 List<String> listVersions = new ArrayList<>( );
229                 for ( String strAnchor : listElement )
230                 {
231                     if ( strAnchor.matches( "^[\\d].*" ) )
232                     {
233                         listVersions.add( strAnchor );
234                     }
235                 }
236 
237                 if ( listVersions.isEmpty( ) )
238                 {
239                     return RELEASE_NOT_FOUND;
240                 }
241                 else
242                 {
243                     return VersionUtils.getLatestVersion( listVersions );
244                 }
245             }
246             catch ( HttpAccessException e )
247             {
248                 AppLogService.error(
249                         "LuteceTools - MavenRepoService : Error retrieving release version : " + e.getMessage( ), e );
250             }
251     	}       
252 
253         return strVersion;
254     }
255 
256     /**
257      * Gets the component list
258      * 
259      * @return the component list
260      */
261     public ComponentsInfos getComponents( )
262     {
263         ComponentsInfosols/service/ComponentsInfos.html#ComponentsInfos">ComponentsInfos ciInfos = new ComponentsInfos( );
264         List<Component> list = new ArrayList<>( );
265         List<String> listComponents = getComponentsListFromRepository( );
266         int nCount = 0;
267         int nAvailable = 0;
268 
269         for ( String strArtifactId : listComponents )
270         {
271             Component component = getComponent( strArtifactId, false );
272             list.add( component );
273             nCount++;
274 
275             if ( !NON_AVAILABLE.equals( component.getVersion( ) ) )
276             {
277                 nAvailable++;
278             }
279         }
280         Collections.sort( list );
281 
282         ciInfos.setComponentCount( nCount );
283         ciInfos.setComponentAvailable( nAvailable );
284         ciInfos.setListComponents( list );
285 
286         return ciInfos;
287     }
288 
289     /**
290      * Get the components list
291      * 
292      * @return The list
293      */
294     public List<String> getComponentsListFromRepository( )
295     {
296         List<String> list = new ArrayList<>( );
297 
298         try
299         {
300             HttpAccess httpAccess = new HttpAccess( );
301             
302             String strUrl = getAvailableUrl(PROPERTIES_SNAPSHOTS_PATH, URL_MAVEN_PATH_PLUGINS, null);
303             String strHtml = httpAccess.doGet( strUrl ); 
304             list = getAnchorsList( strHtml );
305 
306             // remove the first link
307             list.remove( 0 );
308 
309             // add lutece-core
310             list.add( 0, TAG_LUTECE_CORE );
311         }
312         catch ( HttpAccessException e )
313         {
314             AppLogService.error(
315                     "LuteceTools - MavenRepoService : Error retrieving release version : " + e.getMessage( ), e );
316         }
317 
318         return list;
319     }
320 
321     /**
322      * Gets a component using cache feature
323      * 
324      * @param strArtifactId The component name
325      * @param bFetch
326      * @return The component
327      */
328     public Component getComponent( String strArtifactId, boolean bFetch )
329     {
330         return getComponent( strArtifactId, bFetch, false );
331     }
332 
333     /**
334      * Gets a component using cache feature
335      * 
336      * @param strArtifactId The component name
337      * @param bFetch
338      * @param bForceReload
339      * @return The component
340      */
341     public Component getComponent( String strArtifactId, boolean bFetch, boolean bForceReload )
342     {
343         return getComponent( strArtifactId, bFetch, bForceReload, null );
344     }
345 
346     /**
347      * Gets a component
348      * 
349      * @param strArtifactId
350      * @param bFetch
351      * @param bForceReload
352      * @param strType       the component type
353      * @return
354      */
355     public Component getComponent( String strArtifactId, boolean bFetch, boolean bForceReload, String strType )
356     {
357 
358         Component component = bForceReload ? null : ComponentService.load( strArtifactId );
359 
360         if ( component == null )
361         {
362             if ( bFetch )
363             {
364                 StringBuilder sbLogs = new StringBuilder( );
365                 component = fetchComponent( strArtifactId, strType, sbLogs );
366                 ComponentService.save( component );
367             }
368             else
369             {
370                 component = new Component( );
371                 component.setArtifactId( strArtifactId );
372                 component.set( Component.CORE_VERSION, NON_AVAILABLE );
373                 component.set( Component.PARENT_POM_VERSION, NON_AVAILABLE );
374                 component.set( Component.SCM_URL, NON_AVAILABLE );
375                 component.set( Component.SNAPSHOT_VERSION, NON_AVAILABLE );
376                 component.set( Component.SNAPSHOT_CORE_VERSION, NON_AVAILABLE );
377                 component.set( Component.SNAPSHOT_PARENT_POM_VERSION, NON_AVAILABLE );
378                 component.set( Component.JIRA_KEY, NON_AVAILABLE );
379                 component.setVersion( NON_AVAILABLE );
380                 component.set( Component.IS_GIT_REPO, false );
381             }
382         }
383 
384         return component;
385     }
386 
387     /**
388      * Fetch the component from the Maven repository
389      * 
390      * @param strArtifactId The Artifact ID
391      * 
392      * @param strType       the component type
393      * @param sbLogs        Logs
394      * @return The component
395      */
396     private Component fetchComponent( String strArtifactId, String strType, StringBuilder sbLogs )
397     {
398         Component component = new Component( );
399         component.setArtifactId( strArtifactId );
400         if ( Constants.MAVEN_REPO_LUTECE_CORE.equals( getMavenRepoDirectoryType( strArtifactId, strType ) ) )
401         {
402         	String strUrl = getAvailableUrl(PROPERTIES_RELEASES_PATH, URL_MAVEN_PATH_CORE, null);
403             component.setVersion( getVersion( strUrl ) );
404         }
405         else if ( Constants.MAVEN_REPO_LUTECE_SITE.equals( getMavenRepoDirectoryType( strArtifactId, strType ) ) )
406         {
407         	String strUrl = getAvailableUrl(PROPERTIES_RELEASES_PATH, URL_MAVEN_PATH_THEMES, strArtifactId);
408         	component.setVersion( getVersion( strUrl ) );
409         }
410         else 
411         {
412         	String strUrl = getAvailableUrl(PROPERTIES_RELEASES_PATH, URL_MAVEN_PATH_PLUGINS, strArtifactId);
413         	component.setVersion( getVersion( strUrl ) );
414         }
415 
416         long lTime1 = new Date( ).getTime( );
417         
418         //Get pom infos
419         getPomInfos( component, strType, sbLogs );
420 
421         long lTime2 = new Date( ).getTime( );
422         sbLogs.append( "\nLutece Tools - Fetching Maven Info for '" ).append( component.getArtifactId( ) )
423                 .append( "' - duration : " ).append( lTime2 - lTime1 ).append( "ms." );
424 
425         for ( ComponentInfoFiller filler : _listComponentFiller )
426         {
427             filler.fill( component, sbLogs );
428         }
429 
430         return component;
431     }
432 
433     /**
434      * Fill component infos coming from the pom
435      * 
436      * @param component The component name
437      * @param sbLogs    Logs
438      */
439     private void getPomInfos( Component component, String strType, StringBuilder sbLogs )
440     {
441         StringBuilder sbPomUrl;
442 
443         if ( !RELEASE_NOT_FOUND.equals( component.getVersion( ) ) )
444         {
445             if ( Constants.MAVEN_REPO_LUTECE_CORE
446                     .equals( getMavenRepoDirectoryType( component.getArtifactId( ), strType ) ) )
447             {           	
448             	sbPomUrl = new StringBuilder( getAvailableUrl(PROPERTIES_RELEASES_PATH, URL_MAVEN_PATH_CORE, null) );            	
449                 sbPomUrl.append( component.getVersion( ) ).append( '/' );
450             }
451             else if ( Constants.MAVEN_REPO_LUTECE_SITE
452                     .equals( getMavenRepoDirectoryType( component.getArtifactId( ), strType ) ) )
453             {
454             	sbPomUrl = new StringBuilder( getAvailableUrl(PROPERTIES_RELEASES_PATH, URL_MAVEN_PATH_THEMES, component.getArtifactId( )) );
455                 sbPomUrl.append( '/' ).append( component.getVersion( ) ).append( '/' );
456 
457             }
458             else
459             {
460             	sbPomUrl = new StringBuilder( getAvailableUrl(PROPERTIES_RELEASES_PATH, URL_MAVEN_PATH_PLUGINS, component.getArtifactId( )) );
461                 sbPomUrl.append( '/' ).append( component.getVersion( ) ).append( '/' );
462             }
463 
464             sbPomUrl.append( component.getArtifactId( ) ).append( '-' ).append( component.getVersion( ) )
465                     .append( ".pom" );
466             
467             String strPomUrl = sbPomUrl.toString( ).replace(PATH_MAVEN_REPO_TREE, PATH_MAVEN_REPO_FILE);
468             
469             getPomInfos( component, strPomUrl, false, sbLogs );
470 
471             PomService.instance( ).getLuteceDependencies( component, sbPomUrl.toString( ), false, sbLogs );
472         }
473         
474         // Get SNAPSHOT pom url
475         String strSnapshotPomUrl = getSnapshotPomUrl( component, sbLogs, strType );
476 
477         if ( strSnapshotPomUrl != null )
478         {
479             getPomInfos( component, strSnapshotPomUrl, true, sbLogs );
480             PomService.instance( ).getLuteceDependencies( component, strSnapshotPomUrl, true, sbLogs );
481         }
482         else
483         {
484             sbLogs.append( "\n*** ERROR *** No snapshot pom found for plugin : " ).append( component.getArtifactId( ) );
485         }
486     }
487 
488     /**
489      * Retreive POM infos for a given component
490      * 
491      * @param component The component
492      * @param strPomUrl The POM URL
493      * @param bSnapshot false for release, true for snapshot
494      * @param sbLogs    Logs
495      */
496     private void getPomInfos( Component component, String strPomUrl, boolean bSnapshot, StringBuilder sbLogs )
497     {
498         try
499         {
500             HttpAccess httpAccess = new HttpAccess( );
501             String strPom = httpAccess.doGet( strPomUrl );
502             SAXParserFactory saxParserFactory = SAXParserFactory.newInstance( );
503             SAXParser saxParser = saxParserFactory.newSAXParser( );
504             SaxPomHandlertools/service/SaxPomHandler.html#SaxPomHandler">SaxPomHandler handler = new SaxPomHandler( );
505             saxParser.parse( new InputSource( new StringReader( strPom ) ), handler );
506 
507             if ( bSnapshot )
508             {
509                 component.set( Component.SNAPSHOT_PARENT_POM_VERSION, handler.getParentPomVersion( ) );
510                 component.set( Component.SNAPSHOT_CORE_VERSION, handler.getCoreVersion( ) );
511                 component.set( Component.SNAPSHOT_SCM_URL, handler.getScmUrl( ) );
512             }
513             else
514             {
515                 component.set( Component.PARENT_POM_VERSION, handler.getParentPomVersion( ) );
516                 component.set( Component.CORE_VERSION, handler.getCoreVersion( ) );
517                 component.set( Component.SCM_URL, handler.getScmUrl( ) );
518             }
519             component.set( Component.SCM_CONNECTION, handler.getScmConnection( ) );
520             component.set( Component.SCM_DEVELOPER_CONNECTION, handler.getScmDeveloperConnection( ) );
521             component.set( Component.JIRA_KEY, handler.getJiraKey( ) );
522         }
523         catch ( HttpAccessException e )
524         {
525             sbLogs.append( "\n*** ERROR *** Error reading pom for component " ).append( component.getArtifactId( ) )
526                     .append( EXCEPTION_MESSAGE ).append( e.getMessage( ) );
527         }
528         catch ( IOException | SAXException | ParserConfigurationException e )
529         {
530             AppLogService.error( EXCEPTION_MESSAGE + e.getMessage( ), e );
531         }
532     }
533 
534     /**
535      * Retrieve the POM URL for the latest snapshot
536      * 
537      * @param component The component
538      * @param sbLogs    The logs
539      * @return The URL
540      */
541     private String getSnapshotPomUrl( Component component, StringBuilder sbLogs, String strType )
542     {
543         String strPomUrl = null;
544         String strSnapshotsDirUrl;
545 
546         if ( Constants.MAVEN_REPO_LUTECE_CORE
547                 .equals( getMavenRepoDirectoryType( component.getArtifactId( ), strType ) ) )
548         {
549         	strSnapshotsDirUrl = getAvailableUrl(PROPERTIES_SNAPSHOTS_PATH, URL_MAVEN_PATH_CORE, null);
550         }
551         else if ( Constants.MAVEN_REPO_LUTECE_SITE
552                 .equals( getMavenRepoDirectoryType( component.getArtifactId( ), strType ) ) )
553         {
554         	strSnapshotsDirUrl = getAvailableUrl(PROPERTIES_SNAPSHOTS_PATH, URL_MAVEN_PATH_THEMES, component.getArtifactId( ));            
555         }
556         else
557         {
558         	strSnapshotsDirUrl = getAvailableUrl(PROPERTIES_SNAPSHOTS_PATH, URL_MAVEN_PATH_PLUGINS, component.getArtifactId( ));            
559         }
560 
561         try
562         {
563             HttpAccess httpAccess = new HttpAccess( );
564             List<String> listVersions = new ArrayList<>( );
565             
566             String strHtml = httpAccess.doGet( strSnapshotsDirUrl );
567             List<String> listElement = getAnchorsList( strHtml );
568 
569             for ( String strAnchor : listElement )
570             {
571                 if ( strAnchor.matches( "^[\\d].*" ) )
572                 {
573                     listVersions.add( strAnchor );
574                 }
575             }
576 
577             if ( listVersions.isEmpty( ) )
578             {
579                 sbLogs.append( "\n*** ERROR ***  Error retrieving snapshot pom URL : no versions found" );
580                 return null;
581             }
582 
583             String strSnapshotVersion = VersionUtils.getLatestVersion( listVersions );
584             component.set( Component.SNAPSHOT_VERSION, strSnapshotVersion );
585 
586             String strLastSnapshotDirUrl = strSnapshotsDirUrl + "/" + strSnapshotVersion;
587             strPomUrl = strLastSnapshotDirUrl;
588             
589             strHtml = httpAccess.doGet( strLastSnapshotDirUrl );
590             listElement = getAnchorsList( strHtml );
591             String strPomFileName = getPomFileName(listElement);
592             
593             if ( strPomFileName == null || strPomFileName.isEmpty() )
594             {
595             	String strLastDirname = null;
596         		int nIteration = 0;
597             	for ( String strDirname : listElement )
598                 {
599             		String [ ] tabDir = strDirname.split("-");
600             		String [ ] tabVer = strSnapshotVersion.split("-");
601             		
602                     if ( tabDir[0].equals(tabVer[0]) && Integer.parseInt( tabDir[tabDir.length -1] ) >= nIteration )
603                     {
604                     	strLastDirname = strDirname;
605                     	nIteration = Integer.parseInt( tabDir[tabDir.length -1] );
606                     }                    
607                 }
608             	
609             	strLastSnapshotDirUrl = strLastSnapshotDirUrl + "/" + strLastDirname;
610             	
611                 strHtml = httpAccess.doGet( strLastSnapshotDirUrl );
612                 listElement = getAnchorsList( strHtml );                
613                 strPomFileName = getPomFileName(listElement);
614             }
615             
616             strPomUrl = strPomUrl.replace(PATH_MAVEN_REPO_TREE, PATH_MAVEN_REPO_FILE) + "/" + strPomFileName;
617         }
618         catch ( HttpAccessException e )
619         {
620             sbLogs.append( "\n*** ERROR ***  Error retrieving snapshot pom URL : " ).append( e.getMessage( ) );
621         }
622 
623         return strPomUrl;
624     }
625     
626     private String getPomFileName(List<String> listElement)
627     {
628     	String strPomFileName = null;
629 
630         for ( String strFilename : listElement )
631         {
632             if ( strFilename.endsWith( ".pom" ) )
633             {
634             	strPomFileName = strFilename;
635             }
636         }
637     	return strPomFileName;
638     }
639     
640    
641     /**
642      * Gets anchor list using regexp
643      * 
644      * @param strHtml The HTML code
645      * @return The list
646      */
647     List<String> getAnchorsList2( String strHtml )
648     {
649         List<String> list = new ArrayList<>( );
650         String strPattern = "<a[^>]*>(.+?)</a>";
651         Pattern pattern = Pattern.compile( strPattern, Pattern.DOTALL );
652         Matcher matcher = pattern.matcher( strHtml );
653 
654         while ( matcher.find( ) )
655         {
656             list.add( strHtml.substring( matcher.start( ), matcher.end( ) ) );
657         }
658 
659         return list;
660     }
661 
662     /**
663      * Gets anchor list using more optimized method
664      * 
665      * @param strHtml The HTML code
666      * @return The list
667      */
668     private List<String> getAnchorsList( String strHtml )
669     {
670         List<String> list = new ArrayList<>( );
671         String strCurrent = strHtml;
672 
673         int nPos = strCurrent.indexOf( "<a " );
674 
675         while ( nPos > 0 )
676         {
677             strCurrent = strCurrent.substring( nPos );
678 
679             int nEndTag = strCurrent.indexOf( ">" );
680             int nTagEnd = strCurrent.indexOf( "</a>" );
681             list.add( strCurrent.substring( nEndTag + 1, nTagEnd ).replaceAll( "\\/", "" ) );
682             strCurrent = strCurrent.substring( nTagEnd + 4 );
683             nPos = strCurrent.indexOf( "<a " );
684         }
685 
686         return list;
687     }
688 
689     private String getMavenRepoDirectoryType( String strArtifactId, String strComponentType )
690     {
691 
692         String strTypeRepo = null;
693         if ( strComponentType != null )
694         {
695             switch ( strComponentType )
696             {
697             case Constants.DEPENDENCY_TYPE_LUTECE_CORE:
698 
699                 strTypeRepo = Constants.MAVEN_REPO_LUTECE_CORE;
700                 break;
701             case Constants.DEPENDENCY_TYPE_LUTECE_SITE:
702 
703                 strTypeRepo = Constants.MAVEN_REPO_LUTECE_SITE;
704                 break;
705 
706             default:
707                 strTypeRepo = Constants.MAVEN_REPO_LUTECE_PLUGIN;
708                 break;
709             }
710         }
711         else
712         {
713 
714             if ( TAG_LUTECE_CORE.equals( strArtifactId ) )
715             {
716                 strTypeRepo = Constants.MAVEN_REPO_LUTECE_CORE;
717             }
718             else
719             {
720                 strTypeRepo = Constants.MAVEN_REPO_LUTECE_PLUGIN;
721             }
722         }
723 
724         return strTypeRepo;
725 
726     }
727 
728     /**
729      * Update the cache (reset and rebuild)
730      */
731     public void updateCache( )
732     {
733         GitHubService.updateGitHubRepositoriesList( );
734 
735         List<String> listComponents = getComponentsListFromRepository( );
736 
737         for ( String strArtifactId : listComponents )
738         {
739             Component component = ComponentService.load( strArtifactId );
740 
741             if ( shouldBeUpdated( component ) )
742             {
743                 component = fetchComponent( strArtifactId, null, _sbLogs );
744                 ComponentService.save( component );
745             }
746             else
747             {
748                 _sbLogs.append( "\nComponent " ).append( strArtifactId ).append( " is up to date" );
749             }
750         }
751     }
752 
753     public String getLogs( )
754     {
755         return _sbLogs.toString( );
756     }
757 
758     public static synchronized void clearLogs( )
759     {
760         _sbLogs = new StringBuilder( );
761     }
762 
763     /**
764      * Returns true if the component should be updated
765      * 
766      * @param component The component
767      * @return true if the component should be updated
768      */
769     private boolean shouldBeUpdated( Component component )
770     {
771         // The component is missing
772         if ( component == null )
773         {
774             return true;
775         }
776 
777         // The last update is too far
778         long lNow = new Date( ).getTime( );
779 
780         return ( lNow - component.getLastUpdate( ) ) > UPDATE_DELAY;
781     }
782 
783     public String getLatestCoreVersion( )
784     {
785     	return getVersion( getAvailableUrl(PROPERTIES_SNAPSHOTS_PATH, URL_MAVEN_PATH_CORE, null) );
786     }
787 
788 }