001// Licensed under the Apache License, Version 2.0 (the "License");
002// you may not use this file except in compliance with the License.
003// You may obtain a copy of the License at
004//
005// http://www.apache.org/licenses/LICENSE-2.0
006//
007// Unless required by applicable law or agreed to in writing, software
008// distributed under the License is distributed on an "AS IS" BASIS,
009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
010// See the License for the specific language governing permissions and
011// limitations under the License.
012
013package org.apache.tapestry5.webresources.modules;
014
015import com.github.sommeri.less4j.LessCompiler;
016import com.github.sommeri.less4j.core.parser.AntlrException;
017import org.apache.tapestry5.MarkupWriter;
018import org.apache.tapestry5.internal.webresources.*;
019import org.apache.tapestry5.ioc.MappedConfiguration;
020import org.apache.tapestry5.ioc.ServiceBinder;
021import org.apache.tapestry5.ioc.annotations.Autobuild;
022import org.apache.tapestry5.ioc.annotations.Contribute;
023import org.apache.tapestry5.ioc.annotations.Primary;
024import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
025import org.apache.tapestry5.ioc.internal.util.InternalUtils;
026import org.apache.tapestry5.ioc.services.FactoryDefaults;
027import org.apache.tapestry5.ioc.services.SymbolProvider;
028import org.apache.tapestry5.services.ObjectRenderer;
029import org.apache.tapestry5.services.assets.ResourceMinimizer;
030import org.apache.tapestry5.services.assets.ResourceTransformer;
031import org.apache.tapestry5.services.assets.StreamableResourceSource;
032import org.apache.tapestry5.webresources.WebResourcesSymbols;
033
034import java.util.List;
035
036/**
037 * Configures use of various transformers and mimimizers to support:
038 * <ul>
039 * <li>Less to CSS</li>
040 * <li>CoffeeScript to JavaScript</li>
041 * <li>CSS minimization via YUI Compressor</li>
042 * <li>JavaScript minimization via Google Closure</li>
043 * </ul>
044 *
045 * @since 5.4
046 */
047public class WebResourcesModule
048{
049    public static void bind(ServiceBinder binder)
050    {
051        binder.bind(ResourceTransformerFactory.class, ResourceTransformerFactoryImpl.class);
052    }
053
054    @Contribute(SymbolProvider.class)
055    @FactoryDefaults
056    public static void setupDefaultCacheDirectory(MappedConfiguration<String, Object> configuration)
057    {
058        configuration.add(WebResourcesSymbols.CACHE_DIR, "${java.io.tmpdir}/tapestry-asset-cache");
059    }
060
061
062    @Contribute(StreamableResourceSource.class)
063    public static void provideCompilers(MappedConfiguration<String, ResourceTransformer> configuration, ResourceTransformerFactory factory,
064                                        @Autobuild CoffeeScriptCompiler coffeeScriptCompiler)
065    {
066        // contribution ids are file extensions:
067
068        configuration.add("coffee",
069                factory.createCompiler("text/javascript", "CoffeeScript", "JavaScript",
070                        coffeeScriptCompiler,
071                        CacheMode.SINGLE_FILE));
072
073        configuration.add("less",
074                factory.createCompiler("text/css", "Less", "CSS", new LessResourceTransformer(),
075                        CacheMode.MULTIPLE_FILE));
076    }
077
078    @Contribute(ResourceMinimizer.class)
079    @Primary
080    public static void setupDefaultResourceMinimizers(MappedConfiguration<String, ResourceMinimizer> configuration)
081    {
082        configuration.addInstance("text/css", CSSMinimizer.class);
083        configuration.addInstance("text/javascript", GoogleClosureMinimizer.class);
084    }
085
086    /**
087     * Alas {@link AntlrException}s do not have a useful toString() which makes them useless in the exception report;
088     * here we provide an {@link ObjectRenderer} that breaks them apart into useful strings. Eventually we may be
089     * able to synthesize a {@link org.apache.tapestry5.ioc.Location} from them as well and show some of the source .less file.
090     */
091    @Contribute(ObjectRenderer.class)
092    @Primary
093    public static void provideLessCompilerProblemRenderer(MappedConfiguration<Class, ObjectRenderer> configuration)
094    {
095        configuration.add(LessCompiler.Problem.class, new ObjectRenderer<LessCompiler.Problem>()
096        {
097            @Override
098            public void render(LessCompiler.Problem problem, MarkupWriter writer)
099            {
100                List<String> strings = CollectionFactory.newList();
101
102                if (InternalUtils.isNonBlank(problem.getMessage()))
103                {
104                    strings.add(problem.getMessage());
105                }
106
107                // Inside WRO4J we see that the LessSource is a StringSource with no useful toString(), so
108                // it is omitted. We may need to create our own processors, stripping away a couple of layers of
109                // WRO4J to get proper exception reporting!
110
111                if (problem.getLine() > 0)
112                {
113                    strings.add("line " + problem.getLine());
114                }
115
116                if (problem.getCharacter() > 0)
117                {
118                    strings.add("position " + problem.getCharacter());
119                }
120
121                writer.write(InternalUtils.join(strings, " - "));
122            }
123        });
124    }
125}