2121import java .io .StringWriter ;
2222import java .nio .charset .Charset ;
2323import java .nio .charset .StandardCharsets ;
24+ import java .util .Arrays ;
25+ import java .util .Collection ;
2426import java .util .Collections ;
25- import java .util .HashMap ;
26- import java .util .Map ;
2727import java .util .Scanner ;
2828import javax .servlet .http .HttpServletRequest ;
2929
5959 */
6060public 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