/** * Copyright (c) 2008-2011 Sonatype, Inc. * All rights reserved. Includes the third-party code listed at http://www.sonatype.com/products/nexus/attributions. * * This program is free software: you can redistribute it and/or modify it only under the terms of the GNU Affero General * Public License Version 3 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License Version 3 * for more details. * * You should have received a copy of the GNU Affero General Public License Version 3 along with this program. If not, see * http://www.gnu.org/licenses. * * Sonatype Nexus (TM) Open Source Version is available from Sonatype, Inc. Sonatype and Sonatype Nexus are trademarks of * Sonatype, Inc. Apache Maven is a trademark of the Apache Foundation. M2Eclipse is a trademark of the Eclipse Foundation. * All other trademarks are the property of their respective owners. */ package org.sonatype.nexus.plugins.rrb; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonatype.nexus.plugins.rrb.parsers.ArtifactoryRemoteRepositoryParser; import org.sonatype.nexus.plugins.rrb.parsers.HtmlRemoteRepositoryParser; import org.sonatype.nexus.plugins.rrb.parsers.RemoteRepositoryParser; import org.sonatype.nexus.plugins.rrb.parsers.S3RemoteRepositoryParser; import org.sonatype.nexus.proxy.repository.ProxyRepository; import com.ning.http.client.AsyncHttpClient; import com.ning.http.client.Request; import com.ning.http.client.RequestBuilder; import com.ning.http.client.Response; /** * Class for retrieving directory data from remote repository. This class is not thread-safe! */ public class MavenRepositoryReader { private final Logger logger = LoggerFactory.getLogger( MavenRepositoryReader.class ); private final AsyncHttpClient client; private String remotePath; private String remoteUrl; private String localUrl; private ProxyRepository proxyRepository; private String id; public MavenRepositoryReader( final AsyncHttpClient client ) { this.client = client; } /** * @param remotePath remote path added to the URL * @param localUrl url to the local resource service * @return a list containing the remote data */ public List<RepositoryDirectory> extract( String remotePath, String localUrl, ProxyRepository proxyRepository, String id ) { logger.debug( "remotePath={}", remotePath ); this.remotePath = remotePath; this.localUrl = localUrl; this.proxyRepository = proxyRepository; this.id = id; String baseRemoteUrl = proxyRepository.getRemoteUrl(); if ( !baseRemoteUrl.endsWith( "/" ) && !remotePath.startsWith( "/" ) ) { this.remoteUrl = baseRemoteUrl + "/" + remotePath; } else { this.remoteUrl = baseRemoteUrl + remotePath; } StringBuilder html = getContent(); if ( logger.isDebugEnabled() ) { logger.trace( html.toString() ); } return parseResult( html ); } private ArrayList<RepositoryDirectory> parseResult( StringBuilder indata ) { RemoteRepositoryParser parser = null; String baseUrl = ""; if ( proxyRepository != null ) { baseUrl = proxyRepository.getRemoteUrl(); } if ( indata.indexOf( "<html " ) != -1 ) { // if title="Artifactory" then it is an Artifactory repo... if ( indata.indexOf( "title=\"Artifactory\"" ) != -1 ) { logger.debug( "is Artifactory repository" ); parser = new ArtifactoryRemoteRepositoryParser( remotePath, localUrl, id, baseUrl ); } else { logger.debug( "is html repository" ); parser = new HtmlRemoteRepositoryParser( remotePath, localUrl, id, baseUrl ); } } else if ( indata.indexOf( "xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"" ) != -1 || ( indata.indexOf( "<?xml" ) != -1 && responseContainsError( indata ) ) ) { logger.debug( "is S3 repository" ); if ( responseContainsError( indata ) && !responseContainsAccessDenied( indata ) ) { logger.debug( "response from S3 repository contains error, need to find rootUrl" ); remoteUrl = findcreateNewUrl( indata ); indata = getContent(); } else if ( responseContainsError( indata ) && responseContainsAccessDenied( indata ) ) { logger.debug( "response from S3 repository contains access denied response" ); indata = new StringBuilder(); } parser = new S3RemoteRepositoryParser( remotePath, localUrl, id, baseUrl.replace( findRootUrl( indata ), "" ) ); } else { logger.debug( "Found no matching parser, using default html parser" ); parser = new HtmlRemoteRepositoryParser( remotePath, localUrl, id, baseUrl ); } return parser.extractLinks( indata ); } private String findcreateNewUrl( StringBuilder indata ) { logger.debug( "indata={}", indata.toString() ); String key = extracktKey( indata ); String newUrl = ""; if ( !key.equals( "" ) ) { newUrl = findRootUrl( indata ); newUrl += "?prefix=" + key; } if ( !newUrl.endsWith( "/" ) ) { newUrl += "/"; } logger.debug( "newUrl={}", newUrl ); return newUrl; } private String findRootUrl( StringBuilder indata ) { int end = remoteUrl.indexOf( extracktKey( indata ) ); if ( end > 0 ) { String newUrl = remoteUrl.substring( 0, end ); if ( newUrl.indexOf( '?' ) != -1 ) { newUrl = newUrl.substring( 0, newUrl.indexOf( '?' ) ); } return newUrl; } return remoteUrl; } private String extracktKey( StringBuilder indata ) { String key = ""; int start = indata.indexOf( "<Key>" ); int end = indata.indexOf( "</Key>" ); if ( start > 0 && end > start ) { key = indata.substring( start + 5, end ); } return key; } /** * Used to detect error in S3 response. * * @param indata * @return */ private boolean responseContainsError( StringBuilder indata ) { if ( indata.indexOf( "<Error>" ) != -1 || indata.indexOf( "<error>" ) != -1 ) { return true; } return false; } /** * Used to detect access denied in S3 response. * * @param indata * @return */ private boolean responseContainsAccessDenied( StringBuilder indata ) { if ( indata.indexOf( "<Code>AccessDenied</Code>" ) != -1 || indata.indexOf( "<code>AccessDenied</code>" ) != -1 ) { return true; } return false; } private StringBuilder getContent() { RequestBuilder builder = new RequestBuilder(); if ( remoteUrl.indexOf( "?prefix" ) != -1 ) { builder.setUrl( remoteUrl + "&delimiter=/" ); } else { builder.setUrl( remoteUrl + "?delimiter=/" ); } Response response = null; StringBuilder result = new StringBuilder(); try { response = doCall( builder.build(), result ); } catch ( IOException e ) { if ( logger.isDebugEnabled() ) { logger.warn( e.getMessage(), e ); } else { logger.warn( e.getMessage() ); } } // here is the deal, For reasons I do not understand, S3 comes back with an empty response (and a 200), // stripping off the last '/' // returns the error we are looking for (so we can do a query) String serverHeader = response != null ? response.getHeader( "Server" ) : null; if ( result.length() == 0 && serverHeader != null && serverHeader.equalsIgnoreCase( "AmazonS3" ) && remoteUrl.endsWith( "/" ) ) { remoteUrl = remoteUrl.substring( 0, remoteUrl.length() - 1 ); // now just call it again return getContent(); } return result; } private Response doCall( Request request, StringBuilder result ) throws IOException { try { Response response = client.executeRequest( request ).get(); final int responseCode = response.getStatusCode(); logger.debug( "responseCode={}", responseCode ); BufferedReader reader = new BufferedReader( new InputStreamReader( response.getResponseBodyAsStream() ) ); String line = null; while ( ( line = reader.readLine() ) != null ) { result.append( line + "\n" ); } return response; } catch ( Exception e ) { throw new IOException( e ); } } }