@ -33,6 +33,7 @@ import java.text.MessageFormat;
import java.text.SimpleDateFormat ;
import java.text.SimpleDateFormat ;
import java.util.ArrayList ;
import java.util.ArrayList ;
import java.util.Collections ;
import java.util.Collections ;
import java.util.Comparator ;
import java.util.Date ;
import java.util.Date ;
import java.util.HashSet ;
import java.util.HashSet ;
import java.util.Iterator ;
import java.util.Iterator ;
@ -44,6 +45,8 @@ import java.util.Set;
import java.util.SortedMap ;
import java.util.SortedMap ;
import java.util.TimeZone ;
import java.util.TimeZone ;
import java.util.TreeMap ;
import java.util.TreeMap ;
import java.util.stream.Collectors ;
import java.time.Instant ;
import javax.crypto.Mac ;
import javax.crypto.Mac ;
import javax.crypto.spec.SecretKeySpec ;
import javax.crypto.spec.SecretKeySpec ;
@ -288,6 +291,8 @@ public class AmazonS3 {
* < p >
* < p >
* This method is primarily meant for obtaining a " recursive directory
* This method is primarily meant for obtaining a " recursive directory
* listing " rooted under the specified bucket and prefix location .
* listing " rooted under the specified bucket and prefix location .
* It returns the keys sorted in reverse order of LastModified time
* ( freshest keys first ) .
*
*
* @param bucket
* @param bucket
* name of the bucket whose objects should be listed .
* name of the bucket whose objects should be listed .
@ -311,7 +316,10 @@ public class AmazonS3 {
do {
do {
lp . list ( ) ;
lp . list ( ) ;
} while ( lp . truncated ) ;
} while ( lp . truncated ) ;
return lp . entries ;
Comparator < KeyInfo > comparator = Comparator . comparingLong ( KeyInfo : : getLastModifiedSecs ) ;
return lp . entries . stream ( ) . sorted ( comparator . reversed ( ) )
. map ( KeyInfo : : getName ) . collect ( Collectors . toList ( ) ) ;
}
}
/ * *
/ * *
@ -620,8 +628,26 @@ public class AmazonS3 {
return p ;
return p ;
}
}
/ * *
* KeyInfo enables sorting of keys by lastModified time
* /
private static final class KeyInfo {
private final String name ;
private final long lastModifiedSecs ;
public KeyInfo ( String aname , long lsecs ) {
name = aname ;
lastModifiedSecs = lsecs ;
}
public String getName ( ) {
return name ;
}
public long getLastModifiedSecs ( ) {
return lastModifiedSecs ;
}
}
private final class ListParser extends DefaultHandler {
private final class ListParser extends DefaultHandler {
final List < String > entries = new ArrayList < > ( ) ;
final List < KeyInfo > entries = new ArrayList < > ( ) ;
private final String bucket ;
private final String bucket ;
@ -630,6 +656,8 @@ public class AmazonS3 {
boolean truncated ;
boolean truncated ;
private StringBuilder data ;
private StringBuilder data ;
private String keyName ;
private Instant keyLastModified ;
ListParser ( String bn , String p ) {
ListParser ( String bn , String p ) {
bucket = bn ;
bucket = bn ;
@ -641,7 +669,7 @@ public class AmazonS3 {
if ( prefix . length ( ) > 0 )
if ( prefix . length ( ) > 0 )
args . put ( "prefix" , prefix ) ; //$NON-NLS-1$
args . put ( "prefix" , prefix ) ; //$NON-NLS-1$
if ( ! entries . isEmpty ( ) )
if ( ! entries . isEmpty ( ) )
args . put ( "marker" , prefix + entries . get ( entries . size ( ) - 1 ) ) ; //$NON-NLS-1$
args . put ( "marker" , prefix + entries . get ( entries . size ( ) - 1 ) . getName ( ) ) ; //$NON-NLS-1$
for ( int curAttempt = 0 ; curAttempt < maxAttempts ; curAttempt + + ) {
for ( int curAttempt = 0 ; curAttempt < maxAttempts ; curAttempt + + ) {
final HttpURLConnection c = open ( "GET" , bucket , "" , args ) ; //$NON-NLS-1$ //$NON-NLS-2$
final HttpURLConnection c = open ( "GET" , bucket , "" , args ) ; //$NON-NLS-1$ //$NON-NLS-2$
@ -650,6 +678,8 @@ public class AmazonS3 {
case HttpURLConnection . HTTP_OK :
case HttpURLConnection . HTTP_OK :
truncated = false ;
truncated = false ;
data = null ;
data = null ;
keyName = null ;
keyLastModified = null ;
final XMLReader xr ;
final XMLReader xr ;
try {
try {
@ -683,9 +713,14 @@ public class AmazonS3 {
public void startElement ( final String uri , final String name ,
public void startElement ( final String uri , final String name ,
final String qName , final Attributes attributes )
final String qName , final Attributes attributes )
throws SAXException {
throws SAXException {
if ( "Key" . equals ( name ) | | "IsTruncated" . equals ( name ) ) //$NON-NLS-1$ //$NON-NLS-2$
if ( "Key" . equals ( name ) | | "IsTruncated" . equals ( name ) | | "LastModified" . equals ( name ) ) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3 $
data = new StringBuilder ( ) ;
data = new StringBuilder ( ) ;
}
}
if ( "Contents" . equals ( name ) ) { //$NON-NLS-1$
keyName = null ;
keyLastModified = null ;
}
}
@Override
@Override
public void ignorableWhitespace ( final char [ ] ch , final int s ,
public void ignorableWhitespace ( final char [ ] ch , final int s ,
@ -704,10 +739,16 @@ public class AmazonS3 {
@Override
@Override
public void endElement ( final String uri , final String name ,
public void endElement ( final String uri , final String name ,
final String qName ) throws SAXException {
final String qName ) throws SAXException {
if ( "Key" . equals ( name ) ) //$NON-NLS-1$
if ( "Key" . equals ( name ) ) { //$NON-NLS-1$
entries . add ( data . toString ( ) . substring ( prefix . length ( ) ) ) ;
keyName = data . toString ( ) . substring ( prefix . length ( ) ) ;
else if ( "IsTruncated" . equals ( name ) ) //$NON-NLS-1$
} else if ( "IsTruncated" . equals ( name ) ) { //$NON-NLS-1$
truncated = StringUtils . equalsIgnoreCase ( "true" , data . toString ( ) ) ; //$NON-NLS-1$
truncated = StringUtils . equalsIgnoreCase ( "true" , data . toString ( ) ) ; //$NON-NLS-1$
} else if ( "LastModified" . equals ( name ) ) { //$NON-NLS-1$
keyLastModified = Instant . parse ( data . toString ( ) ) ;
} else if ( "Contents" . equals ( name ) ) { //$NON-NLS-1$
entries . add ( new KeyInfo ( keyName , keyLastModified . getEpochSecond ( ) ) ) ;
}
data = null ;
data = null ;
}
}
}
}