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 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    }