001// Copyright 2005 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.tapestry.asset;
016
017import java.io.BufferedInputStream;
018import java.io.IOException;
019import java.io.InputStream;
020import java.net.URL;
021import java.security.MessageDigest;
022import java.util.HashMap;
023import java.util.Iterator;
024import java.util.Map;
025
026import org.apache.commons.codec.binary.Hex;
027import org.apache.hivemind.ApplicationRuntimeException;
028import org.apache.hivemind.ClassResolver;
029import org.apache.hivemind.util.IOUtils;
030import org.apache.tapestry.event.ReportStatusEvent;
031import org.apache.tapestry.event.ReportStatusListener;
032import org.apache.tapestry.event.ResetEventListener;
033
034/**
035 * Implementation of {@link org.apache.tapestry.asset.ResourceDigestSource} that calculates an
036 * DIGEST checksum digest and converts it to a string of hex digits.
037 * 
038 * @author Howard M. Lewis Ship
039 * @since 4.0
040 */
041public class ResourceDigestSourceImpl implements ResourceDigestSource, ResetEventListener,
042        ReportStatusListener
043{
044    private String _serviceId;
045
046    private ClassResolver _classResolver;
047
048    private static final int BUFFER_SIZE = 5000;
049
050    /**
051     * Map keyed on resource path of DIGEST checksum (as a string).
052     */
053
054    private final Map _cache = new HashMap();
055
056    public synchronized String getDigestForResource(String resourcePath)
057    {
058        String result = (String) _cache.get(resourcePath);
059
060        if (result == null)
061        {
062            result = computeMD5(resourcePath);
063            _cache.put(resourcePath, result);
064        }
065
066        return result;
067    }
068
069    public synchronized void resetEventDidOccur()
070    {
071        _cache.clear();
072    }
073
074    public synchronized void reportStatus(ReportStatusEvent event)
075    {
076        event.title(_serviceId);
077        event.property("resource count", _cache.size());
078
079        Iterator i = _cache.entrySet().iterator();
080
081        while (i.hasNext())
082        {
083            Map.Entry entry = (Map.Entry) i.next();
084
085            event.property(entry.getKey().toString(), entry.getValue());
086        }
087    }
088
089    private String computeMD5(String resourcePath)
090    {
091        URL url = _classResolver.getResource(resourcePath);
092
093        if (url == null)
094            throw new ApplicationRuntimeException(AssetMessages.noSuchResource(resourcePath));
095
096        InputStream stream = null;
097
098        try
099        {
100            MessageDigest digest = MessageDigest.getInstance("MD5");
101
102            stream = new BufferedInputStream(url.openStream());
103
104            digestStream(digest, stream);
105
106            stream.close();
107            stream = null;
108
109            byte[] bytes = digest.digest();
110            char[] encoded = Hex.encodeHex(bytes);
111
112            return new String(encoded);
113        }
114        catch (IOException ex)
115        {
116            throw new ApplicationRuntimeException(AssetMessages.unableToReadResource(
117                    resourcePath,
118                    ex));
119        }
120        catch (Exception ex)
121        {
122            throw new ApplicationRuntimeException(ex);
123        }
124        finally
125        {
126            IOUtils.close(stream);
127        }
128    }
129
130    private void digestStream(MessageDigest digest, InputStream stream) throws IOException
131    {
132        byte[] buffer = new byte[BUFFER_SIZE];
133
134        while (true)
135        {
136            int length = stream.read(buffer);
137
138            if (length < 0)
139                return;
140
141            digest.update(buffer, 0, length);
142        }
143    }
144
145    public void setClassResolver(ClassResolver classResolver)
146    {
147        _classResolver = classResolver;
148    }
149
150    public void setServiceId(String serviceId)
151    {
152        _serviceId = serviceId;
153    }
154}