Skip to content

Commit bc14c5b

Browse files
committed
Polish [CssLinkResource|AppCacheManifest]Transformer
This commit updates the two transformers to make them more consistent with updates of their spring-web-reactive equivalents. Issue: SPR-14521
1 parent 33d9074 commit bc14c5b

File tree

2 files changed

+148
-115
lines changed

2 files changed

+148
-115
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/resource/AppCacheManifestTransformer.java

Lines changed: 112 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121
import java.io.StringWriter;
2222
import java.nio.charset.Charset;
2323
import java.nio.charset.StandardCharsets;
24+
import java.util.Arrays;
25+
import java.util.Collection;
2426
import java.util.Collections;
25-
import java.util.HashMap;
26-
import java.util.Map;
2727
import java.util.Scanner;
2828
import javax.servlet.http.HttpServletRequest;
2929

@@ -59,15 +59,18 @@
5959
*/
6060
public class AppCacheManifestTransformer extends ResourceTransformerSupport {
6161

62+
private static final Collection<String> MANIFEST_SECTION_HEADERS =
63+
Arrays.asList("CACHE MANIFEST", "NETWORK:", "FALLBACK:", "CACHE:");
64+
6265
private static final String MANIFEST_HEADER = "CACHE MANIFEST";
6366

67+
private static final String CACHE_HEADER = "CACHE:";
68+
6469
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
6570

6671
private static final Log logger = LogFactory.getLog(AppCacheManifestTransformer.class);
6772

6873

69-
private final Map<String, SectionTransformer> sectionTransformers = new HashMap<>();
70-
7174
private final String fileExtension;
7275

7376

@@ -84,20 +87,14 @@ public AppCacheManifestTransformer() {
8487
*/
8588
public AppCacheManifestTransformer(String fileExtension) {
8689
this.fileExtension = fileExtension;
87-
88-
SectionTransformer noOpSection = new NoOpSection();
89-
this.sectionTransformers.put(MANIFEST_HEADER, noOpSection);
90-
this.sectionTransformers.put("NETWORK:", noOpSection);
91-
this.sectionTransformers.put("FALLBACK:", noOpSection);
92-
this.sectionTransformers.put("CACHE:", new CacheSection());
9390
}
9491

9592

9693
@Override
97-
public Resource transform(HttpServletRequest request, Resource resource, ResourceTransformerChain transformerChain)
98-
throws IOException {
94+
public Resource transform(HttpServletRequest request, Resource resource,
95+
ResourceTransformerChain chain) throws IOException {
9996

100-
resource = transformerChain.transform(request, resource);
97+
resource = chain.transform(request, resource);
10198
if (!this.fileExtension.equals(StringUtils.getFilenameExtension(resource.getFilename()))) {
10299
return resource;
103100
}
@@ -107,7 +104,7 @@ public Resource transform(HttpServletRequest request, Resource resource, Resourc
107104

108105
if (!content.startsWith(MANIFEST_HEADER)) {
109106
if (logger.isTraceEnabled()) {
110-
logger.trace("AppCache manifest does not start with 'CACHE MANIFEST', skipping: " + resource);
107+
logger.trace("Manifest should start with 'CACHE MANIFEST', skip: " + resource);
111108
}
112109
return resource;
113110
}
@@ -116,111 +113,145 @@ public Resource transform(HttpServletRequest request, Resource resource, Resourc
116113
logger.trace("Transforming resource: " + resource);
117114
}
118115

119-
StringWriter contentWriter = new StringWriter();
120-
HashBuilder hashBuilder = new HashBuilder(content.length());
121-
122116
Scanner scanner = new Scanner(content);
123-
SectionTransformer currentTransformer = this.sectionTransformers.get(MANIFEST_HEADER);
124-
while (scanner.hasNextLine()) {
117+
LineInfo previous = null;
118+
LineAggregator aggregator = new LineAggregator(resource, content);
119+
120+
while (scanner.hasNext()) {
125121
String line = scanner.nextLine();
126-
if (this.sectionTransformers.containsKey(line.trim())) {
127-
currentTransformer = this.sectionTransformers.get(line.trim());
128-
contentWriter.write(line + "\n");
129-
hashBuilder.appendString(line);
130-
}
131-
else {
132-
contentWriter.write(
133-
currentTransformer.transform(line, hashBuilder, resource, transformerChain, request) + "\n");
134-
}
122+
LineInfo current = new LineInfo(line, previous);
123+
LineOutput lineOutput = processLine(current, request, resource, chain);
124+
aggregator.add(lineOutput);
125+
previous = current;
135126
}
136127

137-
String hash = hashBuilder.build();
138-
contentWriter.write("\n" + "# Hash: " + hash);
128+
return aggregator.createResource();
129+
}
130+
131+
private static byte[] getResourceBytes(Resource resource) throws IOException {
132+
return FileCopyUtils.copyToByteArray(resource.getInputStream());
133+
}
134+
135+
private LineOutput processLine(LineInfo info, HttpServletRequest request,
136+
Resource resource, ResourceTransformerChain transformerChain) {
137+
138+
if (!info.isLink()) {
139+
return new LineOutput(info.getLine(), null);
140+
}
141+
142+
Resource appCacheResource = transformerChain.getResolverChain()
143+
.resolveResource(null, info.getLine(), Collections.singletonList(resource));
144+
145+
String path = resolveUrlPath(info.getLine(), request, resource, transformerChain);
139146
if (logger.isTraceEnabled()) {
140-
logger.trace("AppCache file: [" + resource.getFilename()+ "] hash: [" + hash + "]");
147+
logger.trace("Link modified: " + path + " (original: " + info.getLine() + ")");
141148
}
142149

143-
return new TransformedResource(resource, contentWriter.toString().getBytes(DEFAULT_CHARSET));
150+
return new LineOutput(path, appCacheResource);
144151
}
145152

146153

147-
@FunctionalInterface
148-
private interface SectionTransformer {
154+
private static class LineInfo {
149155

150-
/**
151-
* Transforms a line in a section of the manifest.
152-
* <p>The actual transformation depends on the chosen transformation strategy
153-
* for the current manifest section (CACHE, NETWORK, FALLBACK, etc).
154-
*/
155-
String transform(String line, HashBuilder builder, Resource resource,
156-
ResourceTransformerChain transformerChain, HttpServletRequest request) throws IOException;
157-
}
156+
private final String line;
158157

158+
private final boolean cacheSection;
159159

160-
private static class NoOpSection implements SectionTransformer {
160+
private final boolean link;
161161

162-
public String transform(String line, HashBuilder builder, Resource resource,
163-
ResourceTransformerChain transformerChain, HttpServletRequest request) throws IOException {
164162

165-
builder.appendString(line);
166-
return line;
163+
public LineInfo(String line, LineInfo previous) {
164+
this.line = line;
165+
this.cacheSection = initCacheSectionFlag(line, previous);
166+
this.link = iniLinkFlag(line, this.cacheSection);
167+
}
168+
169+
private static boolean initCacheSectionFlag(String line, LineInfo previousLine) {
170+
if (MANIFEST_SECTION_HEADERS.contains(line.trim())) {
171+
return line.trim().equals(CACHE_HEADER);
172+
}
173+
else if (previousLine != null) {
174+
return previousLine.isCacheSection();
175+
}
176+
throw new IllegalStateException(
177+
"Manifest does not start with " + MANIFEST_HEADER + ": " + line);
178+
}
179+
180+
private static boolean iniLinkFlag(String line, boolean isCacheSection) {
181+
return (isCacheSection && StringUtils.hasText(line) && !line.startsWith("#")
182+
&& !line.startsWith("//") && !hasScheme(line));
183+
}
184+
185+
private static boolean hasScheme(String line) {
186+
int index = line.indexOf(":");
187+
return (line.startsWith("//") || (index > 0 && !line.substring(0, index).contains("/")));
188+
}
189+
190+
191+
public String getLine() {
192+
return this.line;
193+
}
194+
195+
public boolean isCacheSection() {
196+
return this.cacheSection;
197+
}
198+
199+
public boolean isLink() {
200+
return this.link;
167201
}
168202
}
169203

204+
private static class LineOutput {
170205

171-
private class CacheSection implements SectionTransformer {
206+
private final String line;
172207

173-
private static final String COMMENT_DIRECTIVE = "#";
208+
private final Resource resource;
174209

175-
@Override
176-
public String transform(String line, HashBuilder builder, Resource resource,
177-
ResourceTransformerChain transformerChain, HttpServletRequest request) throws IOException {
178210

179-
if (isLink(line) && !hasScheme(line)) {
180-
ResourceResolverChain resolverChain = transformerChain.getResolverChain();
181-
Resource appCacheResource =
182-
resolverChain.resolveResource(null, line, Collections.singletonList(resource));
183-
String path = resolveUrlPath(line, request, resource, transformerChain);
184-
builder.appendResource(appCacheResource);
185-
if (logger.isTraceEnabled()) {
186-
logger.trace("Link modified: " + path + " (original: " + line + ")");
187-
}
188-
return path;
189-
}
190-
builder.appendString(line);
191-
return line;
211+
public LineOutput(String line, Resource resource) {
212+
this.line = line;
213+
this.resource = resource;
192214
}
193215

194-
private boolean hasScheme(String link) {
195-
int schemeIndex = link.indexOf(":");
196-
return (link.startsWith("//") || (schemeIndex > 0 && !link.substring(0, schemeIndex).contains("/")));
216+
public String getLine() {
217+
return this.line;
197218
}
198219

199-
private boolean isLink(String line) {
200-
return (StringUtils.hasText(line) && !line.startsWith(COMMENT_DIRECTIVE));
220+
public Resource getResource() {
221+
return this.resource;
201222
}
202223
}
203224

225+
private static class LineAggregator {
204226

205-
private static class HashBuilder {
227+
private final StringWriter writer = new StringWriter();
206228

207229
private final ByteArrayOutputStream baos;
208230

209-
public HashBuilder(int initialSize) {
210-
this.baos = new ByteArrayOutputStream(initialSize);
211-
}
231+
private final Resource resource;
232+
212233

213-
public void appendResource(Resource resource) throws IOException {
214-
byte[] content = FileCopyUtils.copyToByteArray(resource.getInputStream());
215-
this.baos.write(DigestUtils.md5Digest(content));
234+
public LineAggregator(Resource resource, String content) {
235+
this.resource = resource;
236+
this.baos = new ByteArrayOutputStream(content.length());
216237
}
217238

218-
public void appendString(String content) throws IOException {
219-
this.baos.write(content.getBytes(DEFAULT_CHARSET));
239+
public void add(LineOutput lineOutput) throws IOException {
240+
this.writer.write(lineOutput.getLine() + "\n");
241+
byte[] bytes = (lineOutput.getResource() != null ?
242+
DigestUtils.md5Digest(getResourceBytes(lineOutput.getResource())) :
243+
lineOutput.getLine().getBytes(DEFAULT_CHARSET));
244+
this.baos.write(bytes);
220245
}
221246

222-
public String build() {
223-
return DigestUtils.md5DigestAsHex(this.baos.toByteArray());
247+
public TransformedResource createResource() {
248+
String hash = DigestUtils.md5DigestAsHex(this.baos.toByteArray());
249+
this.writer.write("\n" + "# Hash: " + hash);
250+
if (logger.isTraceEnabled()) {
251+
logger.trace("AppCache file: [" + resource.getFilename()+ "] hash: [" + hash + "]");
252+
}
253+
byte[] bytes = this.writer.toString().getBytes(DEFAULT_CHARSET);
254+
return new TransformedResource(this.resource, bytes);
224255
}
225256
}
226257

0 commit comments

Comments
 (0)