001: /*
002: * $Id: DigestUtils.java,v 1.10 2004/09/30 17:27:56 spal Exp $
003: * $Source: /cvsroot/sqlunit/sqlunit/src/net/sourceforge/sqlunit/utils/DigestUtils.java,v $
004: * SQLUnit - a test harness for unit testing database stored procedures.
005: * Copyright (C) 2003 The SQLUnit Team
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: */
021: package net.sourceforge.sqlunit.utils;
022:
023: import net.sourceforge.sqlunit.IErrorCodes;
024: import net.sourceforge.sqlunit.SQLUnitException;
025: import net.sourceforge.sqlunit.SymbolTable;
026:
027: import org.apache.log4j.Logger;
028:
029: import java.io.ByteArrayInputStream;
030: import java.io.ByteArrayOutputStream;
031: import java.io.File;
032: import java.io.FileInputStream;
033: import java.io.FileNotFoundException;
034: import java.io.FileOutputStream;
035: import java.io.IOException;
036: import java.io.InputStream;
037: import java.io.ObjectInputStream;
038: import java.io.ObjectOutputStream;
039: import java.security.MessageDigest;
040: import java.util.HashMap;
041: import java.util.Iterator;
042: import java.util.Map;
043:
044: /**
045: * Provides utility methods needed for MD5 Digestion.
046: * @author Sujit Pal (spal@users.sourceforge.net)
047: * @version $Revision: 1.10 $
048: */
049: public final class DigestUtils {
050:
051: private static final Logger LOG = Logger
052: .getLogger(DigestUtils.class);
053:
054: private static final char HEX_FF = 0xff;
055: private static final String FILE_PREFIX = "file:";
056: private static final int FILE_PREFIX_LENGTH = 5;
057: private static final int BYTES_IN_KB = 1024;
058:
059: /**
060: * Private Constructor. Cannot be instantiated.
061: */
062: private DigestUtils() {
063: // private constructor, cannot instantiate
064: }
065:
066: /**
067: * Returns the MD5 Checksum for a file or memory buffer represented by
068: * the specified InputStream object. Creates a temporary file with the
069: * name pattern sqlunit-lob-*.dat as a side effect if the InputStream
070: * supplied to it is anything other than a FileInputStream. This is
071: * useful in case you have an error and you want to compare with diff
072: * or cmp.
073: * @param istream a File or Memory buffer containing data to be digested.
074: * @return the MD5 Checksum for the data.
075: * @exception SQLUnitException if there was a problem generating MD5.
076: */
077: public static String getMD5CheckSum(final InputStream istream)
078: throws SQLUnitException {
079: LOG.debug("getMD5CheckSum(InputStream)");
080: try {
081: MessageDigest md = MessageDigest.getInstance("MD5");
082: byte[] clearbytes = DigestUtils
083: .readBytesFromStream(istream);
084: byte[] digest = md.digest(clearbytes);
085: StringBuffer buffer = new StringBuffer("md5:");
086: for (int i = 0; i < digest.length; i++) {
087: String hex = Integer.toHexString(digest[i] & HEX_FF);
088: if (hex.length() < 2) {
089: hex = '0' + hex;
090: }
091: buffer.append(hex);
092: }
093: writeTempFile(clearbytes);
094: istream.close();
095: return buffer.toString();
096: } catch (Exception e) {
097: throw new SQLUnitException(IErrorCodes.GENERIC_ERROR,
098: new String[] { "System", e.getClass().getName(),
099: e.getMessage() }, e);
100: }
101: }
102:
103: /**
104: * Takes a String and does the right thing to convert it to MD5 according
105: * to the following rules. If it is prefixed with md5:, it assumes that
106: * this String has already been digested and returns it. If it is prefixed
107: * with file:, it assumes that it is a file whose contents are to be
108: * digested. If it is prefixed with obj:, it assumes that this is an
109: * object representatation and also lets it through. In all other cases,
110: * it will try to digest the String and return the MD5 checksum for it.
111: * @param str the String to convert to MD5.
112: * @return an MD5 digest String.
113: * @exception SQLUnitException if there was a problem with the conversion.
114: */
115: public static String getMD5CheckSum(final String str)
116: throws SQLUnitException {
117: LOG.debug("getMD5CheckSum(String)");
118: if (str.startsWith("md5:") || str.startsWith("obj:")) {
119: // do nothing
120: return str;
121: }
122: if (str.startsWith(FILE_PREFIX)) {
123: // this is a file, digest contents and return
124: try {
125: return DigestUtils.getMD5CheckSum(new FileInputStream(
126: str.substring(FILE_PREFIX_LENGTH)));
127: } catch (FileNotFoundException e) {
128: throw new SQLUnitException(
129: IErrorCodes.ERROR_DIGESTING_DATA,
130: new String[] { "file "
131: + str.substring(FILE_PREFIX_LENGTH)
132: + " not found" }, e);
133: }
134: } else {
135: // this is a string, digest the string and return
136: return DigestUtils.getMD5CheckSum(new ByteArrayInputStream(
137: str.getBytes()));
138: }
139: }
140:
141: /**
142: * Returns the String representation of an Object from an InputStream.
143: * @param istream the InputStream to read.
144: * @return the String representation of the object.
145: * @exception SQLUnitException if there was a problem with the conversion.
146: */
147: public static String getStringifiedObject(final InputStream istream)
148: throws SQLUnitException {
149: LOG.debug("getStringifiedObject(InputStream)");
150: try {
151: byte[] bytes = DigestUtils.readBytesFromStream(istream);
152: if (isSerializedJavaObject(bytes)) {
153: return getStringifiedObject(bytes);
154: } else {
155: throw new SQLUnitException(
156: IErrorCodes.ERROR_DIGESTING_DATA,
157: new String[] { "InputStream does not contain a serialized "
158: + "Java Object" });
159: }
160: } catch (Exception e) {
161: throw new SQLUnitException(
162: IErrorCodes.ERROR_DIGESTING_DATA, new String[] { e
163: .getMessage() }, e);
164: }
165: }
166:
167: /**
168: * Returns the String representation of an Object which is supplied to
169: * the method as a byte array.
170: * @param bytes the bytes representing the Object to stringify.
171: * @return the String representation of the object.
172: * @exception SQLUnitException if there was a problem with the conversion.
173: */
174: public static String getStringifiedObject(final byte[] bytes)
175: throws SQLUnitException {
176: LOG.debug("getStringifiedObject(bytes)");
177: try {
178: ObjectInputStream ois = new ObjectInputStream(
179: new ByteArrayInputStream(bytes));
180: Object obj = ois.readObject();
181: ois.close();
182: return "obj:" + obj.toString();
183: } catch (Exception e) {
184: throw new SQLUnitException(
185: IErrorCodes.ERROR_DIGESTING_DATA, new String[] { e
186: .getMessage() }, e);
187: }
188: }
189:
190: /**
191: * Reads an InputStream and returns an array of bytes.
192: * @param istream the InputStream to read.
193: * @return a byte array.
194: * @exception SQLUnitException if there was a problem with reading.
195: */
196: public static byte[] readBytesFromStream(final InputStream istream)
197: throws SQLUnitException {
198: LOG.debug("readBytesFromStream(InputStream)");
199: try {
200: ByteArrayOutputStream bos = new ByteArrayOutputStream();
201: byte[] buf = new byte[BYTES_IN_KB];
202: int len = 0;
203: while ((len = istream.read(buf, 0, BYTES_IN_KB)) != -1) {
204: bos.write(buf, 0, len);
205: }
206: istream.close();
207: byte[] bytes = bos.toByteArray();
208: return bytes;
209: } catch (IOException e) {
210: throw new SQLUnitException(IErrorCodes.GENERIC_ERROR,
211: new String[] { "I/O", e.getClass().getName(),
212: e.getMessage() }, e);
213: }
214: }
215:
216: /**
217: * Converts an object into an array of bytes.
218: * @param obj the Object to get bytes for.
219: * @return an array of bytes.
220: * @exception SQLUnitException if there was a problem generating bytecode.
221: */
222: public static byte[] getByteCodeForObject(final Object obj)
223: throws SQLUnitException {
224: LOG.debug("getByteCodeForObject("
225: + (obj == null ? "NULL" : obj.getClass().getName())
226: + ")");
227: try {
228: ByteArrayOutputStream bos = new ByteArrayOutputStream();
229: ObjectOutputStream oos = new ObjectOutputStream(bos);
230: oos.writeObject(obj);
231: byte[] bytes = bos.toByteArray();
232: return bytes;
233: } catch (Exception e) {
234: throw new SQLUnitException(IErrorCodes.GENERIC_ERROR,
235: new String[] {
236: "I/O",
237: e.getClass().getName(),
238: "Could not convert Object "
239: + (obj == null ? "NULL" : obj
240: .getClass().getName())
241: + " to byte array" }, e);
242: }
243: }
244:
245: /**
246: * Checks the byte header for the presence of the magic number "aced"
247: * and returns true if it finds it. This is not totally reliable and
248: * depends on the convention that this is the magic number for a
249: * serialized Java object.
250: * @param bytes the byte code to check.
251: * @return true if the magic string is found, else false.
252: */
253: public static boolean isSerializedJavaObject(final byte[] bytes) {
254: LOG.debug("isSerializedJavaObject(bytes)");
255: if (bytes == null || bytes.length < 2) {
256: return false;
257: }
258: StringBuffer check = new StringBuffer();
259: for (int i = 0; i < 2; i++) {
260: String hex = Integer.toHexString(bytes[i] & HEX_FF);
261: check.append(hex);
262: }
263: return (("aced").equals(check.toString()));
264: }
265:
266: /**
267: * Writes the bytes into a temporary file and adds the mapping of
268: * the the location where it was written to the actual file name
269: * in the SymbolTable.
270: * @param bytes the bytes to write to the temporary file.
271: * @exception Exception if there was a problem writing the file.
272: */
273: public static void writeTempFile(final byte[] bytes)
274: throws Exception {
275: File tempFile = File.createTempFile("sqlunit-lob-", ".dat");
276: String tempFileName = tempFile.getCanonicalPath();
277: LOG.debug("writeTempFile(bytes) into file:" + tempFileName);
278: FileOutputStream tstream = new FileOutputStream(tempFileName);
279: tstream.write(bytes);
280: tstream.flush();
281: tstream.close();
282: String key = SymbolTable.getCurrentResultKey();
283: SymbolTable.setValue(key, tempFileName);
284: }
285:
286: /**
287: * Gets the temporary file mappings from the Symbol Table.
288: * @return a Map of locations to the temporary file names.
289: */
290: public static Map getTempFileMappings() {
291: Map tempFileMappings = new HashMap();
292: Iterator iter = SymbolTable.getSymbols();
293: while (iter.hasNext()) {
294: String key = (String) iter.next();
295: if (key.startsWith("${result[") && key.endsWith("]}")) {
296: tempFileMappings.put(key, SymbolTable.getValue(key));
297: }
298: }
299: return tempFileMappings;
300: }
301: }
|