1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.myfaces.orchestra.dynaForm.lib;
20
21 import org.apache.commons.codec.binary.Base64;
22
23 import javax.faces.component.UIComponent;
24 import javax.faces.context.FacesContext;
25 import javax.faces.convert.Converter;
26 import javax.faces.convert.ConverterException;
27 import java.io.ByteArrayInputStream;
28 import java.io.ByteArrayOutputStream;
29 import java.io.IOException;
30 import java.io.ObjectInputStream;
31 import java.io.ObjectOutputStream;
32 import java.io.Serializable;
33
34 /**
35 * Converter that can map a Serializable object into a plain java String.
36 * <p>
37 * This works like the standard java Serializable process, except that the resulting byte-array is
38 * then base64-encoded, ie is a String containing only ascii characters. The resulting value can
39 * safely be passed around as the "value" of a select-list, or a value embedded in a hidden field etc.
40 * <p>
41 * In particular, this allows the value of a SelectItem to be an object rather than a String, and the
42 * serialized form of a <i>small</i> object (such as the key class of a persistent entity) to be passed
43 * as a url query parameter.
44 * <p>
45 * Of course the object should not be too complicated, otherwise the string representation will
46 * be rather large!
47 */
48 public class ObjectSerializationConverter implements Converter
49 {
50 public static final NullObject SELECT_NULL_OBJECT = new NullObject();
51
52 /**
53 * Given a String that was previously created by the getAsString method on this class,
54 * "deserialize" the content of the string into a new Object instance.
55 * <p>
56 * See method getAsString for more details.
57 */
58 public Object getAsObject(FacesContext context, UIComponent component, String value) throws ConverterException
59 {
60 if (value == null || value.length() < 1 || SELECT_NULL_OBJECT.equals(value))
61 {
62 return null;
63 }
64
65 ObjectInputStream ois = null;
66 Serializable objectIdent;
67 try
68 {
69 // The string will have been created by getAsString. Therefore it will only ever
70 // contain 16-bit characters whose high 9 bits are zero. Every one of these chars
71 // will map fine to us-ascii, and will give us back the original base64-encoded
72 // byte-array form from the getAsString method. Note that UTF-8 should also work
73 // fine here; when the high 9 bits of a char are zero then the utf-8 representation
74 // of the char is a single byte that has its high bit set to zero, which is exactly
75 // what the original base64-encoded data had.
76 byte[] base64 = value.getBytes("US-ASCII");
77 byte[] rawData = Base64.decodeBase64(base64);
78 ois = new ObjectInputStream(new ByteArrayInputStream(rawData));
79 objectIdent = (Serializable) ois.readObject();
80 ois.close();
81 }
82 catch (IOException e)
83 {
84 throw new ConverterException(e);
85 }
86 catch (ClassNotFoundException e)
87 {
88 throw new ConverterException(e);
89 }
90 finally
91 {
92 if (ois != null)
93 {
94 try
95 {
96 ois.close();
97 }
98 catch (IOException e)
99 {
100 // consume exception, should never happen
101 }
102 }
103 }
104
105 return objectIdent;
106 }
107
108 /**
109 * Return a serialized representation of the provided value, as a String type.
110 * <p>
111 * The provided value is serialized into a byte-array using normal java serialization,
112 * and the array is then base64-encoded. The result is a String that is safe to pass
113 * around in places that expect a String. The returned value can later be passed to the
114 * getAsToObject method of this class to recreate the value passed here.
115 * <p>
116 * The provided object must implement the Serializable interface.
117 */
118 public String getAsString(FacesContext context, UIComponent component, Object value) throws ConverterException
119 {
120 if (value == null || SELECT_NULL_OBJECT.equals(value))
121 {
122 return "";
123 }
124
125 ObjectOutputStream oos = null;
126 try
127 {
128 ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
129 oos = new ObjectOutputStream(baos);
130 oos.writeObject(value);
131 byte[] base64 = Base64.encodeBase64(baos.toByteArray());
132
133 // It doesn't really matter what encoding we use to create a String here, as long as
134 // we use the same encoding to convert back to a byte-array later (and the encoding
135 // chosen is "symmetrical").
136 //
137 // Using UTF-16BE would give us a very dense string representation (essentially no change to the data).
138 // However it does mean that:
139 // * the result will be unreadable
140 // * any attempt to write the string out as utf-8 will waste space
141 // * trying to write it out in an 8-bit encoding will fail, as the string will certainly
142 // hold 'chars' that are not representable in that encoding.
143 //
144 // Using UTF-8 and US-ASCII will result in the same String.
145 // * When interpreting as US-ASCII, the char conversion process will simply create a two-byte
146 // character (0<<8 |input) regardless of the input.
147 // * When interpreting as UTF-8, the char conversion process will look at each byte, and when the
148 // high bit is not set will simply create a two-byte character (0<<8 | input). Processing is
149 // more complicated when the high bit is set on a byte; however as the base64 encoding results
150 // in an array of bytes whose high bit is never set, the effect is identical to US-ASCII in this case.
151 //
152 // Because of this padding with zero bytes, the String takes up twice as much memory as the
153 // original byte array. So this method should only be used when simply passing around byte[]
154 // is not possible.
155 String objectString = new String(base64, "US-ASCII");
156 return objectString;
157 }
158 catch (IOException e)
159 {
160 throw new ConverterException(e);
161 }
162 finally
163 {
164 if (oos != null)
165 {
166 try
167 {
168 oos.close();
169 }
170 catch (IOException e)
171 {
172 // consume exception, should never happen
173 }
174 }
175 }
176 }
177 }