001// Copyright 2004, 2005 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007//     http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.tapestry.util.io;
016
017import java.io.BufferedInputStream;
018import java.io.BufferedOutputStream;
019import java.io.ByteArrayInputStream;
020import java.io.ByteArrayOutputStream;
021import java.io.InputStream;
022import java.io.ObjectInputStream;
023import java.io.ObjectOutputStream;
024import java.io.Serializable;
025import java.util.zip.GZIPInputStream;
026import java.util.zip.GZIPOutputStream;
027
028import org.apache.commons.codec.binary.Base64;
029import org.apache.hivemind.ApplicationRuntimeException;
030import org.apache.hivemind.ClassResolver;
031import org.apache.tapestry.services.DataSqueezer;
032
033/**
034 * The most complicated of the adaptors, this one takes an arbitrary serializable object, serializes
035 * it to binary (possibly compressing the stream along the way), and encodes it in a Base64
036 * encoding. The first character of the squeezed stream indicates whether it is or is not encoded.
037 * 
038 * @author Howard Lewis Ship
039 */
040
041public class SerializableAdaptor implements SqueezeAdaptor
042{
043    private ClassResolver _resolver;
044
045    private static final char BYTESTREAM_PREFIX = 'O';
046
047    private static final char GZIP_BYTESTREAM_PREFIX = 'Z';
048
049    // O is for an object stream rendered as MIME
050    // Z is for on object stream, compressed, rendered as MIME
051
052    private static final String PREFIX = "OZ";
053
054    public String getPrefix()
055    {
056        return PREFIX;
057    }
058
059    public Class getDataClass()
060    {
061        return Serializable.class;
062    }
063
064    public String squeeze(DataSqueezer squeezer, Object data)
065    {
066        try
067        {
068            ByteArrayOutputStream bosPlain = new ByteArrayOutputStream();
069            ByteArrayOutputStream bosCompressed = new ByteArrayOutputStream();
070
071            GZIPOutputStream gos = new GZIPOutputStream(bosCompressed);
072
073            TeeOutputStream tos = new TeeOutputStream(bosPlain, gos);
074
075            ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(tos));
076
077            oos.writeObject(data);
078
079            oos.close();
080
081            boolean useCompressed = bosCompressed.size() < bosPlain.size();
082
083            byte[] byteArray = useCompressed ? bosCompressed.toByteArray() : bosPlain.toByteArray();
084
085            byte[] encoded = Base64.encodeBase64(byteArray);
086
087            String prefix = Character.toString(useCompressed ? GZIP_BYTESTREAM_PREFIX
088                    : BYTESTREAM_PREFIX);
089
090            return prefix + new String(encoded);
091        }
092        catch (Exception ex)
093        {
094            throw new ApplicationRuntimeException(IoMessages.encodeFailure(data, ex), ex);
095        }
096    }
097
098    public Object unsqueeze(DataSqueezer squeezer, String encoded)
099    {
100        char prefix = encoded.charAt(0);
101
102        try
103        {
104            // Strip off the prefix, feed that in as a MIME stream.
105
106            byte[] mimeData = encoded.substring(1).getBytes();
107
108            byte[] decoded = Base64.decodeBase64(mimeData);
109
110            InputStream is = new ByteArrayInputStream(decoded);
111
112            if (prefix == GZIP_BYTESTREAM_PREFIX)
113                is = new GZIPInputStream(is);
114
115            is = new BufferedInputStream(is);
116
117            ObjectInputStream ois = new ResolvingObjectInputStream(_resolver, is);
118
119            Object result = ois.readObject();
120
121            ois.close();
122
123            return result;
124        }
125        catch (Exception ex)
126        {
127            throw new ApplicationRuntimeException(IoMessages.decodeFailure(ex), ex);
128        }
129    }
130
131    public void setResolver(ClassResolver resolver)
132    {
133        _resolver = resolver;
134    }
135
136}