package com.waldura.struts; import java.io.*; import org.apache.struts.action.ActionForm; /** * Serialize a form bean to a string suited for inclusion in a Web page. *
* The form bean is first {@link #serialize(ActionForm) serialized}, * then {@link #encode(byte[]) encoded} into hexadecimal. * The resulting string * can safely be included on a Web page, and more importantly, it * can be included in a form submission without loss of integrity. *
* The same procedure is followed in reverse for the mirror operation, * {@link #decode(String)} and * {@link #deserialize(byte[])}. *
* Calling the high-level routines * {@link #serializeAndEncode(ActionForm)} * and * {@link #decodeAndDeserialize(String)} * is the recommended way to use this class. * * @see java.net.URLEncoder * * @author Renaud Waldura */ public class ActionFormSerializer { /** * Array mapping digits to hex chars. */ private static final char HEX_CHAR[] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; /** * @returns the corresponding digit for a hex character. * E.g. return 0 for '0', 10 for 'a' and 15 for 'f'. * * @throws IllegalArgumentException attempt to decode a non-hex digit */ static int hexdigit(char c) { if (c >= '0' && c <= '9') return c - '0'; else if (c >= 'a' && c <= 'f') return c - 'a' + 10; else throw new IllegalArgumentException("Not a valid hex digit: " + c); } /** * Serialize the form bean and return it as a string suited to * be written into a Web page. * * @param form the form bean instance * @return the serialized bean data as an encoded string * * @throws IOException if the serialization failed */ public String serializeAndEncode(ActionForm bean) throws IOException { return encode( serialize(bean) ); } /** * Decode and de-serialize a form bean. * * @param serialized the encoded and serialized form bean data * @return a form bean instance * * @throws IOException if the de-serialization failed * @throws ClassNotFoundException if the de-serialization failed */ public ActionForm decodeAndDeserialize(String serialized) throws IOException, ClassNotFoundException { return deserialize( decode(serialized) ); } /** * Serialize a form bean to a byte array. * * @param form the form bean to be serialized * @return the serialized bean as a byte array * * @throws IOException if the serialization failed */ byte[] serialize(ActionForm form) throws IOException { ObjectOutput out = null; ByteArrayOutputStream temp = new ByteArrayOutputStream(); try { out = new ObjectOutputStream(temp); out.writeObject(form); } finally { if (out != null) out.close(); } return temp.toByteArray(); } /** * De-serialize a form bean from a byte array. * * @param data serialized bytes of the bean * @return a ActionForm instance of the serialized bean * * @throws IOException if the de-serialization failed * @throws ClassNotFoundException if the de-serialization failed */ ActionForm deserialize(byte[] data) throws IOException, ClassNotFoundException { ObjectInput in = null; ActionForm form = null; try { in = new ObjectInputStream( new ByteArrayInputStream(data) ); form = (ActionForm) in.readObject(); } finally { if (in != null) in.close(); } return form; } /** * Convert a byte array to a hexadecimal string. * * @param data to be encoded * @return String with hex characters */ String encode(byte[] data) { char[] out = new char[data.length * 2]; for (int i = 0; i < data.length ; i++) { byte b = data[i]; out[2 * i ] = HEX_CHAR[(b >> 4) & 0xf]; out[2 * i + 1] = HEX_CHAR[ b & 0xf]; } return new String(out); } /** * Convert a hexadecimal string back to a byte array. * * @param String with hex characters * @return decoded data * * @throws IllegalArgumentException if the strings contains * non-hex digits, or an odd number of digits */ byte[] decode(String s) { if (s.length() % 2 != 0) throw new IllegalArgumentException("Must have an even number of characters"); char[] in = new char[ s.length() ]; s.getChars(0, s.length(), in, 0); byte[] out = new byte[ s.length() / 2 ]; for (int i = 0; i < s.length(); i += 2) { int b1 = hexdigit( in[i] ); int b2 = hexdigit( in[i + 1] ); // this cast is safe because we know for a fact that this // number is going to fit into a byte out[i / 2] = (byte) (16 * b1 + b2); } return out; } }