    001// Copyright 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.services.assets;
    016
    017import org.apache.tapestry5.commons.Resource;
    018import org.apache.tapestry5.commons.util.CollectionFactory;
    019import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
    020import org.apache.tapestry5.services.assets.StreamableResource;
    021import org.apache.tapestry5.services.assets.StreamableResourceProcessing;
    022import org.apache.tapestry5.services.assets.StreamableResourceSource;
    023
    024import java.io.IOException;
    025import java.io.InputStream;
    026import java.util.Map;
    027import java.util.zip.Adler32;
    028
    029public class AssetChecksumGeneratorImpl implements AssetChecksumGenerator
    030{
    031
    032    private final StreamableResourceSource streamableResourceSource;
    033
    034    private final ResourceChangeTracker tracker;
    035
    036    private final Map<Integer, String> cache = CollectionFactory.newConcurrentMap();
    037
    038    public AssetChecksumGeneratorImpl(StreamableResourceSource streamableResourceSource, ResourceChangeTracker tracker)
    039    {
    040        this.streamableResourceSource = streamableResourceSource;
    041        this.tracker = tracker;
    042
    043        tracker.clearOnInvalidation(cache);
    044    }
    045
    046    public String generateChecksum(Resource resource) throws IOException
    047    {
    048        StreamableResource streamable = streamableResourceSource.getStreamableResource(resource, StreamableResourceProcessing.COMPRESSION_DISABLED,
    049                tracker);
    050
    051        return generateChecksum(streamable);
    052    }
    053
    054    public String generateChecksum(StreamableResource resource) throws IOException
    055    {
    056        return cache.computeIfAbsent(resource.hashCode(),
    057                r -> {
    058                    try {
    059                        return toChecksum(resource.openStream());
    060                    } catch (IOException e) {
    061                        throw new RuntimeException(e);
    062                    }
    063                });
    064    }
    065
    066    private String toChecksum(InputStream is) throws IOException
    067    {
    068        // Adler32 is very fast and suitable for these purposes (MD5 and SHA are slower, and
    069        // are targetted at cryptographic solutions).
    070        Adler32 checksum = new Adler32();
    071
    072        byte[] buffer = new byte[1024];
    073
    074        try
    075        {
    076            while (true)
    077            {
    078                int length = is.read(buffer);
    079
    080                if (length < 0)
    081                {
    082                    break;
    083                }
    084
    085                checksum.update(buffer, 0, length);
    086            }
    087
    088            // Reduces it down to just 32 bits which we express in hex.
    089            return Long.toHexString(checksum.getValue());
    090        } finally
    091        {
    092            is.close();
    093        }
    094    }
    095}