001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.request.resource.caching.version; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.security.MessageDigest; 022import java.security.NoSuchAlgorithmException; 023import java.util.regex.Pattern; 024 025import org.apache.wicket.WicketRuntimeException; 026import org.apache.wicket.request.resource.caching.IStaticCacheableResource; 027import org.apache.wicket.util.io.IOUtils; 028import org.apache.wicket.util.lang.Args; 029import org.apache.wicket.util.lang.Bytes; 030import org.apache.wicket.util.resource.IResourceStream; 031import org.apache.wicket.util.resource.ResourceStreamNotFoundException; 032import org.apache.wicket.util.string.Strings; 033import org.slf4j.Logger; 034import org.slf4j.LoggerFactory; 035 036/** 037 * computes the message digest of a {@link org.apache.wicket.request.resource.caching.IStaticCacheableResource} 038 * and uses it as a version string 039 * <p/> 040 * you can use any message digest algorithm that can be retrieved 041 * by Java Cryptography Architecture (JCA) on your current platform. 042 * Check <a href="http://download.oracle.com/javase/1.5.0/docs/guide/security/CryptoSpec.html#AppA">here</a> 043 * for more information on possible algorithms. 044 * 045 * @author Peter Ertl 046 * 047 * @since 1.5 048 */ 049public class MessageDigestResourceVersion implements IResourceVersion 050{ 051 private static final Logger log = LoggerFactory.getLogger(MessageDigestResourceVersion.class); 052 053 private static final String DEFAULT_ALGORITHM = "MD5"; 054 private static final int DEFAULT_BUFFER_BYTES = 8192; // needed for javadoc {@value ..} 055 private static final Bytes DEFAULT_BUFFER_SIZE = Bytes.bytes(DEFAULT_BUFFER_BYTES); 056 057 /** 058 * A valid pattern is a sequence of digits and upper cased English letters A-F 059 */ 060 private static final Pattern DIGEST_PATTERN = Pattern.compile("[0-9A-F]+"); 061 062 /** 063 * message digest algorithm for computing hashes 064 */ 065 private final String algorithm; 066 067 /** 068 * buffer size for computing the digest 069 */ 070 private final Bytes bufferSize; 071 072 /** 073 * create an instance of the message digest 074 * resource version provider using algorithm {@value #DEFAULT_ALGORITHM} 075 * 076 * @see #MessageDigestResourceVersion(String) 077 * @see #MessageDigestResourceVersion(String, org.apache.wicket.util.lang.Bytes) 078 */ 079 public MessageDigestResourceVersion() 080 { 081 this(DEFAULT_ALGORITHM, DEFAULT_BUFFER_SIZE); 082 } 083 084 /** 085 * create an instance of the message digest resource version provider 086 * using the specified algorithm. The algorithm name must be one 087 * that can be retrieved by Java Cryptography Architecture (JCA) 088 * using {@link MessageDigest#getInstance(String)}. For digest computation 089 * an internal buffer of up to {@value #DEFAULT_BUFFER_BYTES} 090 * bytes will be used. 091 * 092 * @param algorithm 093 * digest algorithm 094 * 095 * @see #MessageDigestResourceVersion() 096 * @see #MessageDigestResourceVersion(String, org.apache.wicket.util.lang.Bytes) 097 */ 098 public MessageDigestResourceVersion(String algorithm) 099 { 100 this(algorithm, DEFAULT_BUFFER_SIZE); 101 } 102 103 /** 104 * create an instance of the message digest resource version provider 105 * using the specified algorithm. The algorithm name must be one 106 * that can be retrieved by Java Cryptography Architecture (JCA) 107 * using {@link MessageDigest#getInstance(String)}. For digest computation 108 * an internal buffer with a maximum size specified by parameter 109 * <code>bufferSize</code> will be used. 110 * 111 * @param algorithm 112 * digest algorithm 113 * @param bufferSize 114 * maximum size for internal buffer 115 */ 116 public MessageDigestResourceVersion(String algorithm, Bytes bufferSize) 117 { 118 this.algorithm = Args.notEmpty(algorithm, "algorithm"); 119 this.bufferSize = Args.notNull(bufferSize, "bufferSize"); 120 } 121 122 @Override 123 public String getVersion(IStaticCacheableResource resource) 124 { 125 IResourceStream stream = resource.getResourceStream(); 126 127 // if resource stream can not be found do not cache 128 if (stream == null) 129 { 130 return null; 131 } 132 133 try 134 { 135 final InputStream inputStream = stream.getInputStream(); 136 137 try 138 { 139 // get binary hash 140 final byte[] hash = computeDigest(inputStream); 141 142 // convert to hexadecimal 143 return Strings.toHexString(hash); 144 } 145 finally 146 { 147 IOUtils.close(stream); 148 } 149 } 150 catch (IOException e) 151 { 152 log.warn("unable to compute hash for " + resource, e); 153 return null; 154 } 155 catch (ResourceStreamNotFoundException e) 156 { 157 log.warn("unable to locate resource for " + resource, e); 158 return null; 159 } 160 } 161 162 @Override 163 public Pattern getVersionPattern() 164 { 165 return DIGEST_PATTERN; 166 } 167 168 /** 169 * get instance of message digest provider from JCA 170 * 171 * @return message digest provider 172 */ 173 protected MessageDigest getMessageDigest() 174 { 175 try 176 { 177 return MessageDigest.getInstance(algorithm); 178 } 179 catch (NoSuchAlgorithmException e) 180 { 181 throw new WicketRuntimeException("message digest " + algorithm + " not found", e); 182 } 183 } 184 185 /** 186 * compute digest for resource stream 187 * 188 * @param inputStream 189 * input stream to compute message digest for 190 * 191 * @return binary message digest 192 * 193 * @throws IOException 194 */ 195 protected byte[] computeDigest(InputStream inputStream) throws IOException 196 { 197 final MessageDigest digest = getMessageDigest(); 198 199 // get actual buffer size 200 final int bufferLen = (int)Math.min(Integer.MAX_VALUE, bufferSize.bytes()); 201 202 // allocate read buffer 203 final byte[] buf = new byte[bufferLen]; 204 int len; 205 206 // read stream and update message digest 207 while ((len = inputStream.read(buf)) != -1) 208 { 209 digest.update(buf, 0, len); 210 } 211 // finish message digest and return hash 212 return digest.digest(); 213 } 214}