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