Repository: wicket Updated Branches: refs/heads/wicket-6.x 013a56ae3 -> 057457135
WICKET-5827 Allow to apply multiple Javascript / CSS compressors Project: http://git-wip-us.apache.org/repos/asf/wicket/repo Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/05745713 Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/05745713 Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/05745713 Branch: refs/heads/wicket-6.x Commit: 0574571358553ebc8e6911658c12a629cd677f28 Parents: 013a56a Author: klopfdreh <klopfdreh@tobiass-mbp> Authored: Wed Feb 4 13:26:25 2015 +0100 Committer: Martin Tzvetanov Grigorov <mgrigo...@apache.org> Committed: Fri Feb 13 22:50:15 2015 +0200 ---------------------------------------------------------------------- .../request/resource/CssPackageResource.java | 17 +- .../wicket/resource/CompositeCssCompressor.java | 101 ++++++++++++ .../resource/CompositeJavaScriptCompressor.java | 88 +++++++++++ .../apache/wicket/resource/CssUrlReplacer.java | 90 +++++++++++ .../IScopeAwareTextResourceProcessor.java | 38 +++++ .../wicket/resource/CssUrlReplacerTest.java | 158 +++++++++++++++++++ 6 files changed, 491 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/wicket/blob/05745713/wicket-core/src/main/java/org/apache/wicket/request/resource/CssPackageResource.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/request/resource/CssPackageResource.java b/wicket-core/src/main/java/org/apache/wicket/request/resource/CssPackageResource.java index f6cf010..bbc1826 100644 --- a/wicket-core/src/main/java/org/apache/wicket/request/resource/CssPackageResource.java +++ b/wicket-core/src/main/java/org/apache/wicket/request/resource/CssPackageResource.java @@ -20,6 +20,7 @@ import java.util.Locale; import org.apache.wicket.Application; import org.apache.wicket.css.ICssCompressor; +import org.apache.wicket.resource.IScopeAwareTextResourceProcessor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,6 +33,8 @@ public class CssPackageResource extends PackageResource private static final Logger log = LoggerFactory.getLogger(CssPackageResource.class); + private final String name; + /** * Construct. * @@ -46,6 +49,8 @@ public class CssPackageResource extends PackageResource { super(scope, name, locale, style, variation); + this.name = name; + // CSS resources can be compressed if there is configured ICssCompressor setCompress(true); } @@ -62,7 +67,17 @@ public class CssPackageResource extends PackageResource try { String nonCompressed = new String(processedResponse, "UTF-8"); - return compressor.compress(nonCompressed).getBytes(); + String output; + if (compressor instanceof IScopeAwareTextResourceProcessor) + { + IScopeAwareTextResourceProcessor scopeAwareProcessor = (IScopeAwareTextResourceProcessor)compressor; + output = scopeAwareProcessor.process(nonCompressed, getScope(), name); + } + else + { + output = compressor.compress(nonCompressed); + } + return output.getBytes(); } catch (Exception e) { http://git-wip-us.apache.org/repos/asf/wicket/blob/05745713/wicket-core/src/main/java/org/apache/wicket/resource/CompositeCssCompressor.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/resource/CompositeCssCompressor.java b/wicket-core/src/main/java/org/apache/wicket/resource/CompositeCssCompressor.java new file mode 100644 index 0000000..d8fdcc0 --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/resource/CompositeCssCompressor.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.wicket.resource; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.wicket.css.ICssCompressor; + +/** + * Used to apply several {@link ICssCompressor} to the CSS compression.<br> + * <br> + * Usage: + * + * <pre> + * CompositeCssCompressor compositeCssCompressor = new CompositeCssCompressor(); + * + * compositeCssCompressor.add(new MyCssCompressor()); + * compositeCssCompressor.add(new AnotherCssCompressor()); + * + * this.getResourceSettings().setCssCompressor(compositeCssCompressor); + * </pre> + * + * The compressors can also be given as constructor arguments. + * + * @since 6.20.0 + * @author Tobias Soloschenko + * + */ +public class CompositeCssCompressor implements IScopeAwareTextResourceProcessor +{ + /* Compressors to compress the CSS content */ + private final List<ICssCompressor> compressors = new ArrayList<ICssCompressor>(); + + /** + * Initializes the composite CSS compressor with the given {@link ICssCompressor}(s) + * + * @param compressors + * The {@link ICssCompressor}(s) this composite CSS compressor is initialized with + */ + public CompositeCssCompressor(ICssCompressor... compressors) + { + this.compressors.addAll(Arrays.asList(compressors)); + } + + /** + * Compresses the given original content in the order of compressors. If no compressor has been + * given the original content is going to be returned. + */ + @Override + public String process(String input, Class<?> scope, String name) + { + String compressed = input; + for (ICssCompressor compressor : compressors) + { + if (compressor instanceof IScopeAwareTextResourceProcessor) + { + IScopeAwareTextResourceProcessor processor = (IScopeAwareTextResourceProcessor)compressor; + processor.process(compressed, scope, name); + } + else + { + compressed = compressor.compress(compressed); + } + } + return compressed; + } + + @Override + public String compress(String original) + { + throw new UnsupportedOperationException(CompositeCssCompressor.class.getSimpleName() + + ".process() should be used instead!"); + } + + /** + * Adds a ICssCompressor to the list of delegates. + * + * @return {@code this} instance, for chaining + */ + public CompositeCssCompressor add(ICssCompressor compressor) + { + compressors.add(compressor); + return this; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/wicket/blob/05745713/wicket-core/src/main/java/org/apache/wicket/resource/CompositeJavaScriptCompressor.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/resource/CompositeJavaScriptCompressor.java b/wicket-core/src/main/java/org/apache/wicket/resource/CompositeJavaScriptCompressor.java new file mode 100644 index 0000000..c1f0c15 --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/resource/CompositeJavaScriptCompressor.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.wicket.resource; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.wicket.javascript.IJavaScriptCompressor; + +/** + * Used to apply several {@link IJavaScriptCompressor} to the javascript compression.<br> + * <br> + * Usage: + * + * <pre> + * CompositeJavaScriptCompressor compositeJavaScriptCompressor = new CompositeJavaScriptCompressor(); + * + * compositeJavaScriptCompressor.add(new MyJavaScriptCompressor()); + * compositeJavaScriptCompressor.add(new AnotherJavaScriptCompressor()); + * + * this.getResourceSettings().setJavaScriptCompressor(compositeJavaScriptCompressor); + * </pre> + * + * The compressors can also be given as constructor arguments. + * + * @since 6.20.0 + * @author Tobias Soloschenko + * + */ +public class CompositeJavaScriptCompressor implements IJavaScriptCompressor +{ + /* Compressors to compress javascript content */ + private final List<IJavaScriptCompressor> compressors = new ArrayList<IJavaScriptCompressor>(); + + /** + * Initializes the composite javascript compressor with the given {@link IJavaScriptCompressor} + * (s) + * + * @param compressors + * The {@link IJavaScriptCompressor}(s) this composite javascript compressor is + * initialized with + */ + public CompositeJavaScriptCompressor(IJavaScriptCompressor... compressors) + { + this.compressors.addAll(Arrays.asList(compressors)); + } + + /** + * Compresses the given original content in the order of compressors. If no compressor has been + * given the original content is going to be returned. + */ + @Override + public String compress(String original) + { + String compressed = original; + for (IJavaScriptCompressor compressor : compressors) + { + compressed = compressor.compress(compressed); + } + return compressed; + } + + /** + * Adds a IJavaScriptCompressor to the list of delegates. + * + * @return {@code this} instance, for chaining + */ + public CompositeJavaScriptCompressor add(IJavaScriptCompressor compressor) + { + compressors.add(compressor); + return this; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/wicket/blob/05745713/wicket-core/src/main/java/org/apache/wicket/resource/CssUrlReplacer.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/resource/CssUrlReplacer.java b/wicket-core/src/main/java/org/apache/wicket/resource/CssUrlReplacer.java new file mode 100644 index 0000000..6ad891c --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/resource/CssUrlReplacer.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.wicket.resource; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.wicket.request.Url; +import org.apache.wicket.request.cycle.RequestCycle; +import org.apache.wicket.request.resource.PackageResourceReference; + +/** + * This compressor is used to replace url within css files with resources that belongs to their + * corresponding component classes. The compress method is not compressing any content, but + * replacing the URLs with Wicket representatives.<br> + * <br> + * Usage: + * + * <pre> + * this.getResourceSettings().setCssCompressor(new CssUrlReplacer(this)); + * </pre> + * + * @since 6.20.0 + * @author Tobias Soloschenko + */ +public class CssUrlReplacer implements IScopeAwareTextResourceProcessor +{ + // The pattern to find URLs in CSS resources + private static final Pattern URL_PATTERN = Pattern.compile("url\\(['|\"]*(.*?)['|\"]*\\)"); + + /** + * Replaces the URLs of CSS resources with Wicket representatives. + */ + @Override + public String process(String input, Class<?> scope, String name) + { + RequestCycle cycle = RequestCycle.get(); + Url cssUrl = Url.parse(name); + Matcher matcher = URL_PATTERN.matcher(input); + StringBuffer output = new StringBuffer(); + + while (matcher.find()) + { + Url imageCandidateUrl = Url.parse(matcher.group(1)); + CharSequence processedUrl; + if (imageCandidateUrl.isFull()) + { + processedUrl = imageCandidateUrl.toString(Url.StringMode.FULL); + } + else if (imageCandidateUrl.isContextAbsolute()) + { + processedUrl = imageCandidateUrl.toString(); + } + else + { + // relativize against the url for the containing CSS file + Url cssUrlCopy = new Url(cssUrl); + cssUrlCopy.resolveRelative(imageCandidateUrl); + PackageResourceReference imageReference = new PackageResourceReference(scope, + cssUrlCopy.toString()); + processedUrl = cycle.urlFor(imageReference, null); + + } + matcher.appendReplacement(output, "url('" + processedUrl + "')"); + } + matcher.appendTail(output); + return output.toString(); + } + + @Override + public String compress(String original) + { + throw new UnsupportedOperationException(CssUrlReplacer.class.getSimpleName() + + ".process() should be used instead!"); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/wicket/blob/05745713/wicket-core/src/main/java/org/apache/wicket/resource/IScopeAwareTextResourceProcessor.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/resource/IScopeAwareTextResourceProcessor.java b/wicket-core/src/main/java/org/apache/wicket/resource/IScopeAwareTextResourceProcessor.java new file mode 100644 index 0000000..7d9e424 --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/resource/IScopeAwareTextResourceProcessor.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.wicket.resource; + +/** + * A {@link org.apache.wicket.resource.ITextResourceCompressor} that receives the scope class and + * the resource name as a context information that it can use for the processing of the resource + */ +public interface IScopeAwareTextResourceProcessor extends ITextResourceCompressor +{ + /** + * Processes/manipulates a text resource. + * + * + * @param input + * The original input to process + * @param scope + * The scope class of the package resource + * @param name + * The name of the package resource + * @return The processed input + */ + public String process(String input, Class<?> scope, String name); +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/wicket/blob/05745713/wicket-core/src/test/java/org/apache/wicket/resource/CssUrlReplacerTest.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/test/java/org/apache/wicket/resource/CssUrlReplacerTest.java b/wicket-core/src/test/java/org/apache/wicket/resource/CssUrlReplacerTest.java new file mode 100644 index 0000000..f83338a --- /dev/null +++ b/wicket-core/src/test/java/org/apache/wicket/resource/CssUrlReplacerTest.java @@ -0,0 +1,158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.wicket.resource; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +import org.apache.wicket.WicketTestCase; +import org.apache.wicket.mock.MockApplication; +import org.apache.wicket.protocol.http.WebApplication; +import org.apache.wicket.request.resource.caching.FilenameWithVersionResourceCachingStrategy; +import org.apache.wicket.request.resource.caching.IStaticCacheableResource; +import org.apache.wicket.request.resource.caching.ResourceUrl; +import org.apache.wicket.request.resource.caching.version.MessageDigestResourceVersion; +import org.junit.Test; + +public class CssUrlReplacerTest extends WicketTestCase +{ + + private static final String DECORATION_SUFFIX = "--decorated"; + + @Override + protected WebApplication newApplication() + { + return new MockApplication() + { + @Override + protected void init() + { + super.init(); + + getResourceSettings().setCachingStrategy( + new FilenameWithVersionResourceCachingStrategy("=VER=", + new MessageDigestResourceVersion()) + { + @Override + public void decorateUrl(ResourceUrl url, IStaticCacheableResource resource) + { + url.setFileName(url.getFileName() + DECORATION_SUFFIX); + } + }); + } + }; + } + + @Test + public void doNotProcessFullUrls() + { + String input = ".class {background-image: url('http://example.com/some.img');}"; + Class<?> scope = CssUrlReplacerTest.class; + String cssRelativePath = "res/css/some.css"; + CssUrlReplacer replacer = new CssUrlReplacer(); + + String processed = replacer.process(input, scope, cssRelativePath); + assertThat(processed, is(input)); + } + + @Test + public void doNotProcessContextAbsoluteUrls() + { + String input = ".class {background-image: url('/some.img');}"; + Class<?> scope = CssUrlReplacerTest.class; + String cssRelativePath = "res/css/some.css"; + CssUrlReplacer replacer = new CssUrlReplacer(); + + String processed = replacer.process(input, scope, cssRelativePath); + assertThat(processed, is(input)); + } + + @Test + public void sameFolderSingleQuotes() + { + String input = ".class {background-image: url('some.img');}"; + Class<?> scope = CssUrlReplacerTest.class; + String cssRelativePath = "res/css/some.css"; + CssUrlReplacer replacer = new CssUrlReplacer(); + + String processed = replacer.process(input, scope, cssRelativePath); + assertThat( + processed, + is(".class {background-image: url('./wicket/resource/org.apache.wicket.resource.CssUrlReplacerTest/res/css/some.img" + + DECORATION_SUFFIX + "');}")); + } + + @Test + public void sameFolderDoubleQuotes() + { + String input = ".class {background-image: url(\"some.img\");}"; + Class<?> scope = CssUrlReplacerTest.class; + String cssRelativePath = "res/css/some.css"; + CssUrlReplacer replacer = new CssUrlReplacer(); + + String processed = replacer.process(input, scope, cssRelativePath); + assertThat( + processed, + is(".class {background-image: url('./wicket/resource/org.apache.wicket.resource.CssUrlReplacerTest/res/css/some.img" + + DECORATION_SUFFIX + "');}")); + } + + @Test + public void parentFolderAppendFolder() + { + String input = ".class {background-image: url('../images/some.img');}"; + Class<?> scope = CssUrlReplacerTest.class; + String cssRelativePath = "res/css/some.css"; + CssUrlReplacer replacer = new CssUrlReplacer(); + + String processed = replacer.process(input, scope, cssRelativePath); + assertThat( + processed, + is(".class {background-image: url('./wicket/resource/org.apache.wicket.resource.CssUrlReplacerTest/res/images/some.img" + + DECORATION_SUFFIX + "');}")); + } + + @Test + public void sameFolderAppendFolder() + { + String input = ".class {background-image: url('./images/some.img');}"; + Class<?> scope = CssUrlReplacerTest.class; + String cssRelativePath = "res/css/some.css"; + CssUrlReplacer replacer = new CssUrlReplacer(); + + String processed = replacer.process(input, scope, cssRelativePath); + assertThat( + processed, + is(".class {background-image: url('./wicket/resource/org.apache.wicket.resource.CssUrlReplacerTest/res/css/images/some.img" + + DECORATION_SUFFIX + "');}")); + } + + @Test + public void severalUrls() + { + String input = ".class {\n" + "a: url('../images/a.img');\n" + "b: url('./b.img');\n" + "}"; + Class<?> scope = CssUrlReplacerTest.class; + String cssRelativePath = "res/css/some.css"; + CssUrlReplacer replacer = new CssUrlReplacer(); + + String processed = replacer.process(input, scope, cssRelativePath); + assertThat(processed, containsString("CssUrlReplacerTest/res/images/a.img" + + DECORATION_SUFFIX + "');")); + assertThat(processed, containsString("CssUrlReplacerTest/res/css/b.img" + + DECORATION_SUFFIX + "');")); + } +} \ No newline at end of file