001// Copyright 2013-2014 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007// http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.tapestry5.internal.webresources;
016
017import com.github.sommeri.less4j.Less4jException;
018import com.github.sommeri.less4j.LessCompiler;
019import com.github.sommeri.less4j.LessSource;
020import com.github.sommeri.less4j.core.DefaultLessCompiler;
021import org.apache.commons.io.IOUtils;
022import org.apache.tapestry5.internal.services.assets.BytestreamCache;
023import org.apache.tapestry5.ioc.Resource;
024import org.apache.tapestry5.ioc.internal.util.InternalUtils;
025import org.apache.tapestry5.services.assets.ResourceDependencies;
026import org.apache.tapestry5.services.assets.ResourceTransformer;
027
028import java.io.*;
029
030/**
031 * Direct wrapper around the LessCompiler, so that Less source files may use {@code @import}, which isn't
032 * supported by the normal WRO4J processor.
033 */
034public class LessResourceTransformer implements ResourceTransformer
035{
036    private final LessCompiler compiler = new DefaultLessCompiler();
037
038    public String getTransformedContentType()
039    {
040        return "text/css";
041    }
042
043    class ResourceLessSource extends LessSource
044    {
045        private final Resource resource;
046
047        private final ResourceDependencies dependencies;
048
049
050        ResourceLessSource(Resource resource, ResourceDependencies dependencies)
051        {
052            this.resource = resource;
053            this.dependencies = dependencies;
054        }
055
056        @Override
057        public LessSource relativeSource(String filename) throws FileNotFound, CannotReadFile, StringSourceException
058        {
059            Resource relative = resource.forFile(filename);
060
061            if (!relative.exists())
062            {
063                throw new FileNotFound();
064            }
065
066            dependencies.addDependency(relative);
067
068            return new ResourceLessSource(relative, dependencies);
069        }
070
071        @Override
072        public String getContent() throws FileNotFound, CannotReadFile
073        {
074            // Adapted from Less's URLSource
075            Reader input = null;
076            try
077            {
078                input = new InputStreamReader(resource.openStream());
079                String content = IOUtils.toString(input).replace("\r\n", "\n");
080
081                return content;
082            } catch (FileNotFoundException ex)
083            {
084                throw new FileNotFound();
085            } catch (IOException ex)
086            {
087                throw new CannotReadFile();
088            } finally
089            {
090                InternalUtils.close(input);
091            }
092        }
093
094        public byte[] getBytes() throws FileNotFound, CannotReadFile
095        {
096            Reader input = null;
097            try
098            {
099                input = new InputStreamReader(resource.openStream());
100
101                return IOUtils.toByteArray(input);
102            } catch (FileNotFoundException ex)
103            {
104                throw new FileNotFound();
105            } catch (IOException ex)
106            {
107                throw new CannotReadFile();
108            } finally
109            {
110                InternalUtils.close(input);
111            }
112
113        }
114    }
115
116
117    public InputStream transform(Resource source, ResourceDependencies dependencies) throws IOException
118    {
119        BytestreamCache compiled = invokeLessCompiler(source, dependencies);
120
121        return compiled.openStream();
122    }
123
124    private BytestreamCache invokeLessCompiler(Resource source, ResourceDependencies dependencies) throws IOException
125    {
126        try
127        {
128            LessSource lessSource = new ResourceLessSource(source, dependencies);
129
130            LessCompiler.CompilationResult compilationResult = compiler.compile(lessSource);
131
132            // Currently, ignoring any warnings.
133
134            return new BytestreamCache(compilationResult.getCss().getBytes("utf-8"));
135
136        } catch (Less4jException ex)
137        {
138            throw new IOException(ex);
139        } catch (UnsupportedEncodingException ex)
140        {
141            throw new IOException(ex);
142        }
143    }
144}