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.Iterator;
024    import java.util.Map;
025    
026    import org.apache.commons.codec.binary.Hex;
027    import org.apache.hivemind.ApplicationRuntimeException;
028    import org.apache.hivemind.ClassResolver;
029    import org.apache.hivemind.util.IOUtils;
030    import org.apache.tapestry.event.ReportStatusEvent;
031    import org.apache.tapestry.event.ReportStatusListener;
032    import 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     */
041    public class ResourceDigestSourceImpl implements ResourceDigestSource, ResetEventListener,
042            ReportStatusListener
043    {
044        private static final int BUFFER_SIZE = 5000;
045    
046        private String _serviceId;
047    
048        private ClassResolver _classResolver;
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            if (resourcePath == null) return null;
059            
060            String result = (String) _cache.get(resourcePath);
061    
062            if (result == null)
063            {
064                result = computeMD5(resourcePath);
065                _cache.put(resourcePath, result);
066            }
067    
068            return result;
069        }
070    
071        public synchronized void resetEventDidOccur()
072        {
073            _cache.clear();
074        }
075    
076        public synchronized void reportStatus(ReportStatusEvent event)
077        {
078            event.title(_serviceId);
079            event.property("resource count", _cache.size());
080    
081            Iterator i = _cache.entrySet().iterator();
082    
083            while (i.hasNext())
084            {
085                Map.Entry entry = (Map.Entry) i.next();
086    
087                event.property(entry.getKey().toString(), entry.getValue());
088            }
089        }
090    
091        private String computeMD5(String resourcePath)
092        {
093            URL url = _classResolver.getResource(resourcePath);
094    
095            if (url == null)
096                throw new ApplicationRuntimeException(AssetMessages.noSuchResource(resourcePath));
097    
098            InputStream stream = null;
099    
100            try
101            {
102                MessageDigest digest = MessageDigest.getInstance("MD5");
103    
104                stream = new BufferedInputStream(url.openStream());
105    
106                digestStream(digest, stream);
107    
108                stream.close();
109                stream = null;
110    
111                byte[] bytes = digest.digest();
112                char[] encoded = Hex.encodeHex(bytes);
113    
114                return new String(encoded);
115            }
116            catch (IOException ex)
117            {
118                throw new ApplicationRuntimeException(AssetMessages.unableToReadResource(
119                        resourcePath,
120                        ex));
121            }
122            catch (Exception ex)
123            {
124                throw new ApplicationRuntimeException(ex);
125            }
126            finally
127            {
128                IOUtils.close(stream);
129            }
130        }
131    
132        private void digestStream(MessageDigest digest, InputStream stream) throws IOException
133        {
134            byte[] buffer = new byte[BUFFER_SIZE];
135    
136            while (true)
137            {
138                int length = stream.read(buffer);
139    
140                if (length < 0)
141                    return;
142    
143                digest.update(buffer, 0, length);
144            }
145        }
146    
147        public void setClassResolver(ClassResolver classResolver)
148        {
149            _classResolver = classResolver;
150        }
151    
152        public void setServiceId(String serviceId)
153        {
154            _serviceId = serviceId;
155        }
156    }