Browse Source
This change adds a ResourceTransformer that can be invoked in a chain after resource resolution. The CssLinkResourceTransformer modifies a CSS file being served in order to update its @import and url() links (e.g. to images or other CSS files) to match the resource resolution strategy (e.g. adding MD5 content-based hashes). Issue: SPR-11800pull/548/head
23 changed files with 808 additions and 55 deletions
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
/* |
||||
* Copyright 2002-2014 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.web.servlet.resource; |
||||
|
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
import org.springframework.cache.Cache; |
||||
import org.springframework.core.io.Resource; |
||||
import org.springframework.util.Assert; |
||||
|
||||
import javax.servlet.http.HttpServletRequest; |
||||
import java.io.IOException; |
||||
|
||||
/** |
||||
* A {@link org.springframework.web.servlet.resource.ResourceTransformer} that |
||||
* checks a {@link org.springframework.cache.Cache} to see if a previously |
||||
* transformed or otherwise |
||||
* delegates to the resolver chain and saves the result in the cache. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.1 |
||||
*/ |
||||
public class CachingResourceTransformer implements ResourceTransformer { |
||||
|
||||
private static final Log logger = LogFactory.getLog(CachingResourceTransformer.class); |
||||
|
||||
private final Cache cache; |
||||
|
||||
|
||||
public CachingResourceTransformer(Cache cache) { |
||||
Assert.notNull(cache, "'cache' is required"); |
||||
this.cache = cache; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Return the configured {@code Cache}. |
||||
*/ |
||||
public Cache getCache() { |
||||
return this.cache; |
||||
} |
||||
|
||||
@Override |
||||
public Resource transform(HttpServletRequest request, Resource resource, ResourceTransformerChain transformerChain) |
||||
throws IOException { |
||||
|
||||
Resource transformed = this.cache.get(resource, Resource.class); |
||||
if (transformed != null) { |
||||
if (logger.isTraceEnabled()) { |
||||
logger.trace("Found match"); |
||||
} |
||||
return transformed; |
||||
} |
||||
|
||||
transformed = transformerChain.transform(request, resource); |
||||
|
||||
if (logger.isTraceEnabled()) { |
||||
logger.trace("Putting transformed resource in cache"); |
||||
} |
||||
this.cache.put(resource, transformed); |
||||
|
||||
return transformed; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,259 @@
@@ -0,0 +1,259 @@
|
||||
/* |
||||
* Copyright 2002-2014 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.web.servlet.resource; |
||||
|
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
import org.springframework.core.io.Resource; |
||||
import org.springframework.util.FileCopyUtils; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
import javax.servlet.http.HttpServletRequest; |
||||
import java.io.IOException; |
||||
import java.io.StringWriter; |
||||
import java.nio.charset.Charset; |
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.Collections; |
||||
import java.util.HashSet; |
||||
import java.util.List; |
||||
import java.util.Set; |
||||
|
||||
/** |
||||
* A {@link ResourceTransformer} implementation that modifies links in a CSS |
||||
* file to match the public URL paths that should be exposed to clients (e.g. |
||||
* with an MD5 content-based hash inserted in the URL). |
||||
* |
||||
* <p>The implementation looks for links in CSS {@code @import} statements and |
||||
* also inside CSS {@code url()} functions. All links are then passed through the |
||||
* {@link ResourceResolverChain} and resolved relative to the location of the |
||||
* containing CSS file. If successfully resolved, the link is modified, otherwise |
||||
* the original link is preserved. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.1 |
||||
*/ |
||||
public class CssLinkResourceTransformer implements ResourceTransformer { |
||||
|
||||
private static final Log logger = LogFactory.getLog(CssLinkResourceTransformer.class); |
||||
|
||||
private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); |
||||
|
||||
|
||||
private final List<CssLinkParser> linkParsers = new ArrayList<CssLinkParser>(); |
||||
|
||||
|
||||
public CssLinkResourceTransformer() { |
||||
this.linkParsers.add(new ImportStatementCssLinkParser()); |
||||
this.linkParsers.add(new UrlFunctionCssLinkParser()); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Resource transform(HttpServletRequest request, Resource resource, ResourceTransformerChain transformerChain) |
||||
throws IOException { |
||||
|
||||
resource = transformerChain.transform(request, resource); |
||||
|
||||
String filename = resource.getFilename(); |
||||
if (!"css".equals(StringUtils.getFilenameExtension(filename))) { |
||||
return resource; |
||||
} |
||||
|
||||
if (logger.isTraceEnabled()) { |
||||
logger.trace("Transforming resource: " + resource); |
||||
} |
||||
|
||||
byte[] bytes = FileCopyUtils.copyToByteArray(resource.getInputStream()); |
||||
String content = new String(bytes, DEFAULT_CHARSET); |
||||
|
||||
Set<CssLinkInfo> infos = new HashSet<CssLinkInfo>(5); |
||||
for (CssLinkParser parser : this.linkParsers) { |
||||
parser.parseLink(content, infos); |
||||
} |
||||
|
||||
if (infos.isEmpty()) { |
||||
if (logger.isTraceEnabled()) { |
||||
logger.trace("No links found."); |
||||
} |
||||
return resource; |
||||
} |
||||
|
||||
List<CssLinkInfo> sortedInfos = new ArrayList<CssLinkInfo>(infos); |
||||
Collections.sort(sortedInfos); |
||||
|
||||
int index = 0; |
||||
StringWriter writer = new StringWriter(); |
||||
for (CssLinkInfo info : sortedInfos) { |
||||
writer.write(content.substring(index, info.getStart())); |
||||
String link = content.substring(info.getStart(), info.getEnd()); |
||||
String newLink = transformerChain.getResolverChain().resolveUrlPath(link, Arrays.asList(resource)); |
||||
if (logger.isTraceEnabled()) { |
||||
if (newLink != null && !link.equals(newLink)) { |
||||
logger.trace("Link modified: " + newLink + " (original: " + link + ")"); |
||||
} |
||||
else { |
||||
logger.trace("Link not modified: " + link); |
||||
} |
||||
} |
||||
writer.write(newLink != null ? newLink : link); |
||||
index = info.getEnd(); |
||||
} |
||||
writer.write(content.substring(index)); |
||||
|
||||
return new TransformedResource(resource, writer.toString().getBytes(DEFAULT_CHARSET)); |
||||
} |
||||
|
||||
|
||||
protected static interface CssLinkParser { |
||||
|
||||
void parseLink(String content, Set<CssLinkInfo> linkInfos); |
||||
|
||||
} |
||||
|
||||
protected static abstract class AbstractCssLinkParser implements CssLinkParser { |
||||
|
||||
/** |
||||
* Return the keyword to use to search for links. |
||||
*/ |
||||
protected abstract String getKeyword(); |
||||
|
||||
@Override |
||||
public void parseLink(String content, Set<CssLinkInfo> linkInfos) { |
||||
int index = 0; |
||||
do { |
||||
index = content.indexOf(getKeyword(), index); |
||||
if (index == -1) { |
||||
break; |
||||
} |
||||
index = skipWhitespace(content, index + getKeyword().length()); |
||||
if (content.charAt(index) == '\'') { |
||||
index = addLink(index, "'", content, linkInfos); |
||||
} |
||||
else if (content.charAt(index) == '"') { |
||||
index = addLink(index, "\"", content, linkInfos); |
||||
} |
||||
else { |
||||
index = extractLink(index, content, linkInfos); |
||||
|
||||
} |
||||
} while (true); |
||||
} |
||||
|
||||
private int skipWhitespace(String content, int index) { |
||||
while (true) { |
||||
if (Character.isWhitespace(content.charAt(index))) { |
||||
index++; |
||||
continue; |
||||
} |
||||
return index; |
||||
} |
||||
} |
||||
|
||||
protected int addLink(int index, String endKey, String content, Set<CssLinkInfo> linkInfos) { |
||||
int start = index + 1; |
||||
int end = content.indexOf(endKey, start); |
||||
linkInfos.add(new CssLinkInfo(start, end)); |
||||
return end + endKey.length(); |
||||
} |
||||
|
||||
/** |
||||
* Invoked after a keyword match, after whitespaces removed, and when |
||||
* the next char is neither a single nor double quote. |
||||
*/ |
||||
protected abstract int extractLink(int index, String content, Set<CssLinkInfo> linkInfos); |
||||
|
||||
} |
||||
|
||||
private static class ImportStatementCssLinkParser extends AbstractCssLinkParser { |
||||
|
||||
@Override |
||||
protected String getKeyword() { |
||||
return "@import"; |
||||
} |
||||
|
||||
@Override |
||||
protected int extractLink(int index, String content, Set<CssLinkInfo> linkInfos) { |
||||
if (content.substring(index, index + 4).equals("url(")) { |
||||
// Ignore, UrlLinkParser will take care
|
||||
} |
||||
else if (logger.isErrorEnabled()) { |
||||
logger.error("Unexpected syntax for @import link at index " + index); |
||||
} |
||||
return index; |
||||
} |
||||
} |
||||
|
||||
private static class UrlFunctionCssLinkParser extends AbstractCssLinkParser { |
||||
|
||||
@Override |
||||
protected String getKeyword() { |
||||
return "url("; |
||||
} |
||||
|
||||
@Override |
||||
protected int extractLink(int index, String content, Set<CssLinkInfo> linkInfos) { |
||||
// A url() function without unquoted
|
||||
return addLink(index - 1, ")", content, linkInfos); |
||||
} |
||||
} |
||||
|
||||
|
||||
private static class CssLinkInfo implements Comparable<CssLinkInfo> { |
||||
|
||||
private final int start; |
||||
|
||||
private final int end; |
||||
|
||||
|
||||
private CssLinkInfo(int start, int end) { |
||||
this.start = start; |
||||
this.end = end; |
||||
} |
||||
|
||||
public int getStart() { |
||||
return this.start; |
||||
} |
||||
|
||||
public int getEnd() { |
||||
return this.end; |
||||
} |
||||
|
||||
@Override |
||||
public int compareTo(CssLinkInfo other) { |
||||
return Integer.compare(this.start, other.start); |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object obj) { |
||||
if (this == obj) { |
||||
return true; |
||||
} |
||||
if (obj != null && obj instanceof CssLinkInfo) { |
||||
CssLinkInfo other = (CssLinkInfo) obj; |
||||
return (this.start == other.start && this.end == other.end); |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return this.start * 31 + this.end; |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,84 @@
@@ -0,0 +1,84 @@
|
||||
/* |
||||
* Copyright 2002-2014 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.web.servlet.resource; |
||||
|
||||
import org.springframework.core.io.Resource; |
||||
import org.springframework.util.Assert; |
||||
|
||||
import javax.servlet.http.HttpServletRequest; |
||||
import java.io.IOException; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* A default implementation of {@link ResourceTransformerChain} for invoking a list |
||||
* of {@link ResourceTransformer}s. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.1 |
||||
*/ |
||||
class DefaultResourceTransformerChain implements ResourceTransformerChain { |
||||
|
||||
private final ResourceResolverChain resolverChain; |
||||
|
||||
private final List<ResourceTransformer> transformers = new ArrayList<ResourceTransformer>(); |
||||
|
||||
private int index = -1; |
||||
|
||||
public DefaultResourceTransformerChain(ResourceResolverChain resolverChain, |
||||
List<ResourceTransformer> transformers) { |
||||
|
||||
Assert.notNull(resolverChain, "'resolverChain' is required"); |
||||
this.resolverChain = resolverChain; |
||||
if (transformers != null) { |
||||
this.transformers.addAll(transformers); |
||||
} |
||||
} |
||||
|
||||
|
||||
public ResourceResolverChain getResolverChain() { |
||||
return this.resolverChain; |
||||
} |
||||
|
||||
@Override |
||||
public Resource transform(HttpServletRequest request, Resource resource) throws IOException { |
||||
ResourceTransformer transformer = getNext(); |
||||
if (transformer == null) { |
||||
return resource; |
||||
} |
||||
try { |
||||
return transformer.transform(request, resource, this); |
||||
} |
||||
finally { |
||||
this.index--; |
||||
} |
||||
} |
||||
|
||||
private ResourceTransformer getNext() { |
||||
|
||||
Assert.state(this.index <= this.transformers.size(), |
||||
"Current index exceeds the number of configured ResourceTransformer's"); |
||||
|
||||
if (this.index == (this.transformers.size() - 1)) { |
||||
return null; |
||||
} |
||||
|
||||
this.index++; |
||||
return this.transformers.get(this.index); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
|
||||
/* |
||||
* Copyright 2002-2014 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.web.servlet.resource; |
||||
|
||||
|
||||
import org.springframework.core.io.Resource; |
||||
|
||||
import javax.servlet.http.HttpServletRequest; |
||||
import java.io.IOException; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* An abstraction for transforming the content of a resource. |
||||
* |
||||
* @author Jeremy Grelle |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.1 |
||||
*/ |
||||
public interface ResourceTransformer { |
||||
|
||||
/** |
||||
* Transform the given resource. |
||||
* |
||||
* @param request the current request |
||||
* @param resource the resource to transform |
||||
* @param transformerChain the chain of remaining transformers to delegate to |
||||
* @return the transformed resource, never {@code null} |
||||
* |
||||
* @throws IOException if the transformation fails |
||||
*/ |
||||
Resource transform(HttpServletRequest request, Resource resource, ResourceTransformerChain transformerChain) |
||||
throws IOException; |
||||
|
||||
} |
||||
@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
|
||||
/* |
||||
* Copyright 2002-2014 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.web.servlet.resource; |
||||
|
||||
import org.springframework.core.io.Resource; |
||||
|
||||
import javax.servlet.http.HttpServletRequest; |
||||
import java.io.IOException; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* A contract for invoking a chain of {@link ResourceTransformer}s where each resolver |
||||
* is given a reference to the chain allowing it to delegate when necessary. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.1 |
||||
*/ |
||||
public interface ResourceTransformerChain { |
||||
|
||||
/** |
||||
* Return the {@code ResourceResolverChain} that was used to resolve the |
||||
* {@code Resource} being transformed. This may be needed for resolving |
||||
* related resources, e.g. links to other resources. |
||||
*/ |
||||
ResourceResolverChain getResolverChain(); |
||||
|
||||
/** |
||||
* Transform the given resource. |
||||
* |
||||
* @param request the current request |
||||
* @param resource the candidate resource to transform |
||||
* @return the transformed or the same resource, never {@code null} |
||||
* |
||||
* @throws java.io.IOException if transformation fails |
||||
*/ |
||||
Resource transform(HttpServletRequest request, Resource resource) throws IOException; |
||||
|
||||
} |
||||
@ -0,0 +1,63 @@
@@ -0,0 +1,63 @@
|
||||
/* |
||||
* Copyright 2002-2014 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.web.servlet.resource; |
||||
|
||||
import org.springframework.core.io.ByteArrayResource; |
||||
import org.springframework.core.io.Resource; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
/** |
||||
* An extension of {@link org.springframework.core.io.ByteArrayResource} that a |
||||
* {@link ResourceTransformer} can use to represent an original resource |
||||
* preserving all other information except the content. |
||||
* |
||||
* @author Jeremy Grelle |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.1 |
||||
*/ |
||||
public class TransformedResource extends ByteArrayResource { |
||||
|
||||
private final String filename; |
||||
|
||||
private final long lastModified; |
||||
|
||||
|
||||
public TransformedResource(Resource original, byte[] transformedContent) { |
||||
super(transformedContent); |
||||
this.filename = original.getFilename(); |
||||
try { |
||||
this.lastModified = original.lastModified(); |
||||
} |
||||
catch (IOException e) { |
||||
// should never happen
|
||||
throw new IllegalArgumentException(e); |
||||
} |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public String getFilename() { |
||||
return this.filename; |
||||
} |
||||
|
||||
@Override |
||||
public long lastModified() throws IOException { |
||||
return this.lastModified; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,92 @@
@@ -0,0 +1,92 @@
|
||||
/* |
||||
* Copyright 2002-2014 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.web.servlet.resource; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.springframework.core.io.ClassPathResource; |
||||
import org.springframework.core.io.Resource; |
||||
import org.springframework.mock.web.test.MockHttpServletRequest; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import static org.junit.Assert.assertEquals; |
||||
import static org.junit.Assert.assertSame; |
||||
|
||||
/** |
||||
* Unit tests for |
||||
* {@link org.springframework.web.servlet.resource.CssLinkResourceTransformer}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.1 |
||||
*/ |
||||
public class CssLinkResourceTransformerTests { |
||||
|
||||
private ResourceTransformerChain transformerChain; |
||||
|
||||
private MockHttpServletRequest request; |
||||
|
||||
|
||||
@Before |
||||
public void setUp() { |
||||
List<ResourceResolver> resolvers = new ArrayList<ResourceResolver>(); |
||||
resolvers.add(new FingerprintResourceResolver()); |
||||
resolvers.add(new PathResourceResolver()); |
||||
ResourceResolverChain resolverChain = new DefaultResourceResolverChain(resolvers); |
||||
|
||||
List<ResourceTransformer> transformers = new ArrayList<>(); |
||||
transformers.add(new CssLinkResourceTransformer()); |
||||
this.transformerChain = new DefaultResourceTransformerChain(resolverChain, transformers); |
||||
|
||||
this.request = new MockHttpServletRequest(); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void transformNotCss() throws Exception { |
||||
Resource expected = new ClassPathResource("test/images/image.png", getClass()); |
||||
Resource actual = this.transformerChain.transform(this.request, expected); |
||||
assertSame(expected, actual); |
||||
} |
||||
|
||||
@Test |
||||
public void transform() throws Exception { |
||||
Resource mainCss = new ClassPathResource("test/main.css", getClass()); |
||||
Resource resource = this.transformerChain.transform(this.request, mainCss); |
||||
TransformedResource transformedResource = (TransformedResource) resource; |
||||
|
||||
String expected = "\n" + |
||||
"@import url(\"bar-11e16cf79faee7ac698c805cf28248d2.css\");\n" + |
||||
"@import url('bar-11e16cf79faee7ac698c805cf28248d2.css');\n" + |
||||
"@import url(bar-11e16cf79faee7ac698c805cf28248d2.css);\n\n" + |
||||
"@import \"foo-e36d2e05253c6c7085a91522ce43a0b4.css\";\n" + |
||||
"@import 'foo-e36d2e05253c6c7085a91522ce43a0b4.css';\n\n" + |
||||
"body { background: url(\"images/image-f448cd1d5dba82b774f3202c878230b3.png\") }\n\n" + |
||||
"li { list-style: url(http://www.example.com/redball.png) disc }\n"; |
||||
|
||||
assertEquals(expected, new String(transformedResource.getByteArray(), "UTF-8")); |
||||
} |
||||
|
||||
@Test |
||||
public void transformNoLinks() throws Exception { |
||||
Resource expected = new ClassPathResource("test/foo.css", getClass()); |
||||
Resource actual = this.transformerChain.transform(this.request, expected); |
||||
assertSame(expected, actual); |
||||
} |
||||
|
||||
} |
||||
@ -1 +0,0 @@
@@ -1 +0,0 @@
|
||||
h1 { color:red; } |
||||
|
After Width: | Height: | Size: 155 B |
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
|
||||
@import url("bar.css"); |
||||
@import url('bar.css'); |
||||
@import url(bar.css); |
||||
|
||||
@import "foo.css"; |
||||
@import 'foo.css'; |
||||
|
||||
body { background: url("images/image.png") } |
||||
|
||||
li { list-style: url(http://www.example.com/redball.png) disc } |
||||
Loading…
Reference in new issue