View Javadoc

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 }