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}