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.internal.webresources;
014
015import org.apache.tapestry5.internal.TapestryInternalUtils;
016import org.apache.tapestry5.internal.services.assets.BytestreamCache;
017import org.apache.tapestry5.internal.services.assets.StreamableResourceImpl;
018import org.apache.tapestry5.ioc.IOOperation;
019import org.apache.tapestry5.ioc.OperationTracker;
020import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
021import org.apache.tapestry5.services.assets.CompressionStatus;
022import org.apache.tapestry5.services.assets.ResourceMinimizer;
023import org.apache.tapestry5.services.assets.StreamableResource;
024import org.slf4j.Logger;
025
026import java.io.ByteArrayOutputStream;
027import java.io.IOException;
028import java.io.InputStream;
029
030/**
031 * Base class for resource minimizers.
032 *
033 * @since 5.3
034 */
035public abstract class AbstractMinimizer implements ResourceMinimizer
036{
037    private static final double NANOS_TO_MILLIS = 1.0d / 1000000.0d;
038
039    protected final Logger logger;
040
041    protected final OperationTracker tracker;
042
043    private final AssetChecksumGenerator checksumGenerator;
044
045    private final String resourceType;
046
047    public AbstractMinimizer(Logger logger, OperationTracker tracker, AssetChecksumGenerator checksumGenerator, String resourceType)
048    {
049        this.logger = logger;
050        this.tracker = tracker;
051        this.resourceType = resourceType;
052        this.checksumGenerator = checksumGenerator;
053    }
054
055    @Override
056    public StreamableResource minimize(final StreamableResource input) throws IOException
057    {
058        if (!isEnabled(input))
059        {
060            return input;
061        }
062
063        long startNanos = System.nanoTime();
064
065        final ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
066
067        tracker.perform("Minimizing " + input, new IOOperation<Void>()
068        {
069            @Override
070            public Void perform() throws IOException
071            {
072                InputStream in = doMinimize(input);
073
074                TapestryInternalUtils.copy(in, bos);
075
076                in.close();
077
078                return null;
079            }
080        });
081
082        // The content is minimized, but can still be (GZip) compressed.
083
084        StreamableResource output = new StreamableResourceImpl("minimized " + input.getDescription(),
085                input.getContentType(), CompressionStatus.COMPRESSABLE,
086                input.getLastModified(), new BytestreamCache(bos), checksumGenerator, input.getResponseCustomizer());
087
088        if (logger.isInfoEnabled())
089        {
090            long elapsedNanos = System.nanoTime() - startNanos;
091
092            int inputSize = input.getSize();
093            int outputSize = output.getSize();
094
095            double elapsedMillis = ((double) elapsedNanos) * NANOS_TO_MILLIS;
096            // e.g., reducing 100 bytes to 25 would be a (100-25)/100 reduction, or 75%
097            double reduction = 100d * ((double) (inputSize - outputSize)) / ((double) inputSize);
098
099            logger.info(String.format("Minimized %s (%,d input bytes of %s to %,d output bytes in %.2f ms, %.2f%% reduction)",
100                    input.getDescription(), inputSize, resourceType, outputSize, elapsedMillis, reduction));
101        }
102
103        return output;
104    }
105
106    /**
107     * Implemented in subclasses to do the actual work.
108     *
109     * @param resource
110     *         content to minimize
111     * @return stream of minimized content
112     */
113    protected abstract InputStream doMinimize(StreamableResource resource) throws IOException;
114
115    /**
116     * Determines if the resource can be minimized.
117     *
118     * @return true, subclasses may override
119     */
120    protected boolean isEnabled(StreamableResource resource)
121    {
122        return true;
123    }
124}