1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package com.s3auth.hosts;
31
32 import com.amazonaws.services.s3.AmazonS3;
33 import com.amazonaws.services.s3.model.ListObjectsRequest;
34 import com.amazonaws.services.s3.model.ObjectListing;
35 import com.amazonaws.services.s3.model.S3ObjectSummary;
36 import com.google.common.collect.ImmutableSet;
37 import com.jcabi.aspects.Immutable;
38 import com.jcabi.aspects.Loggable;
39 import com.jcabi.xml.XMLDocument;
40 import com.jcabi.xml.XSL;
41 import com.jcabi.xml.XSLDocument;
42 import java.io.IOException;
43 import java.io.OutputStream;
44 import java.net.HttpURLConnection;
45 import java.util.Collection;
46 import java.util.Date;
47 import java.util.LinkedList;
48 import java.util.zip.CRC32;
49 import javax.validation.constraints.NotNull;
50 import javax.ws.rs.core.HttpHeaders;
51 import lombok.EqualsAndHashCode;
52 import lombok.ToString;
53 import org.apache.commons.io.Charsets;
54 import org.xembly.Directives;
55 import org.xembly.ImpossibleModificationException;
56 import org.xembly.Xembler;
57
58
59
60
61
62
63
64 @Immutable
65 @ToString
66 @EqualsAndHashCode(of = "content")
67 @Loggable(Loggable.DEBUG)
68 final class DirectoryListing implements Resource {
69
70
71
72
73 private static final XSL STYLESHEET = XSLDocument.make(
74 DirectoryListing.class.getResourceAsStream("directory.xsl")
75 );
76
77
78
79
80 @Immutable.Array
81 private final transient byte[] content;
82
83
84
85
86
87
88
89 DirectoryListing(@NotNull final AmazonS3 client,
90 @NotNull final String bucket, @NotNull final String key) {
91 ObjectListing listing = client.listObjects(
92 new ListObjectsRequest().withDelimiter("/").withPrefix(key)
93 .withBucketName(bucket)
94 );
95 final Collection<S3ObjectSummary> objects =
96 new LinkedList<S3ObjectSummary>();
97 objects.addAll(listing.getObjectSummaries());
98 while (listing.isTruncated()) {
99 listing = client.listNextBatchOfObjects(listing);
100 objects.addAll(listing.getObjectSummaries());
101 }
102
103 final Directives dirs = new Directives()
104 .add("directory").attr("prefix", key);
105 for (final String prefix : listing.getCommonPrefixes()) {
106 dirs.add("commonPrefix").set(prefix).up();
107 }
108 for (final S3ObjectSummary object : objects) {
109 dirs.add("object")
110 .add("path")
111 .set(object.getKey()).up()
112 .add("size")
113 .set(Long.toString(object.getSize())).up()
114 .up();
115 }
116 try {
117 this.content = DirectoryListing.STYLESHEET.transform(
118 new XMLDocument(new Xembler(dirs).xml())
119 ).toString().getBytes(Charsets.UTF_8);
120 } catch (final ImpossibleModificationException ex) {
121 throw new IllegalStateException(
122 "Unable to generate directory listing", ex
123 );
124 }
125 }
126
127 @Override
128 public int status() {
129 return HttpURLConnection.HTTP_OK;
130 }
131
132 @Override
133 public long writeTo(final OutputStream stream) throws IOException {
134 stream.write(this.content);
135 return (long) this.content.length;
136 }
137
138 @Override
139 public Collection<String> headers() {
140 final ImmutableSet.Builder<String> headers = ImmutableSet.builder();
141 headers.add(
142 DirectoryListing.header(
143 HttpHeaders.CONTENT_TYPE,
144 this.contentType()
145 )
146 );
147 headers.add(
148 DirectoryListing.header(
149 HttpHeaders.CONTENT_LENGTH,
150 String.valueOf(this.content.length)
151 )
152 );
153 return headers.build();
154 }
155
156 @Override
157 public String etag() {
158 final CRC32 crc = new CRC32();
159 crc.update(this.content);
160 return Long.toHexString(crc.getValue());
161 }
162 @Override
163 public Date lastModified() {
164 return new Date();
165 }
166
167 @Override
168 public String contentType() {
169 return "text/html";
170 }
171
172 @Override
173 public void close() {
174
175 }
176
177
178
179
180
181
182
183 @NotNull
184 private static String header(@NotNull final String name,
185 @NotNull final String value) {
186 return String.format("%s: %s", name, value);
187 }
188 }