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