    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}