Coverage Report - org.apache.tapestry.record.PersistentPropertyDataEncoderImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
PersistentPropertyDataEncoderImpl
0%
0/59
0%
0/26
5
 
 1  
 // Copyright 2005 The Apache Software Foundation
 2  
 //
 3  
 // Licensed under the Apache License, Version 2.0 (the "License");
 4  
 // you may not use this file except in compliance with the License.
 5  
 // You may obtain a copy of the License at
 6  
 //
 7  
 //     http://www.apache.org/licenses/LICENSE-2.0
 8  
 //
 9  
 // Unless required by applicable law or agreed to in writing, software
 10  
 // distributed under the License is distributed on an "AS IS" BASIS,
 11  
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  
 // See the License for the specific language governing permissions and
 13  
 // limitations under the License.
 14  
 
 15  
 package org.apache.tapestry.record;
 16  
 
 17  
 import org.apache.commons.codec.binary.Base64;
 18  
 import org.apache.hivemind.ApplicationRuntimeException;
 19  
 import org.apache.hivemind.ClassResolver;
 20  
 import org.apache.hivemind.HiveMind;
 21  
 import org.apache.hivemind.util.Defense;
 22  
 import org.apache.tapestry.util.io.ResolvingObjectInputStream;
 23  
 import org.apache.tapestry.util.io.TeeOutputStream;
 24  
 
 25  
 import java.io.*;
 26  
 import java.util.ArrayList;
 27  
 import java.util.Collections;
 28  
 import java.util.Iterator;
 29  
 import java.util.List;
 30  
 import java.util.zip.GZIPInputStream;
 31  
 import java.util.zip.GZIPOutputStream;
 32  
 
 33  
 /**
 34  
  * Responsible for converting lists of {@link org.apache.tapestry.record.PropertyChange}s back and
 35  
  * forth to a URL safe encoded string.
 36  
  * <p/>
 37  
  * A possible improvement would be to encode the binary data with encryption both on and off, and
 38  
  * select the shortest (prefixing with a character that identifies whether encryption should be used
 39  
  * to decode).
 40  
  * </p>
 41  
  */
 42  0
 public class PersistentPropertyDataEncoderImpl implements PersistentPropertyDataEncoder {
 43  
     /**
 44  
      * Prefix on the MIME encoding that indicates that the encoded data is not encoded.
 45  
      */
 46  
 
 47  
     public static final String BYTESTREAM_PREFIX = "B";
 48  
 
 49  
     /**
 50  
      * Prefix on the MIME encoding that indicates that the encoded data is encoded with GZIP.
 51  
      */
 52  
 
 53  
     public static final String GZIP_BYTESTREAM_PREFIX = "Z";
 54  
 
 55  
     protected ClassResolver _classResolver;
 56  
 
 57  
     public String encodePageChanges(List changes)
 58  
     {
 59  0
         Defense.notNull(changes, "changes");
 60  
 
 61  0
         if (changes.isEmpty())
 62  0
             return "";
 63  
 
 64  
         try {
 65  0
             ByteArrayOutputStream bosPlain = new ByteArrayOutputStream();
 66  0
             ByteArrayOutputStream bosCompressed = new ByteArrayOutputStream();
 67  
 
 68  0
             GZIPOutputStream gos = new GZIPOutputStream(bosCompressed);
 69  
 
 70  0
             TeeOutputStream tos = new TeeOutputStream(bosPlain, gos);
 71  
 
 72  0
             ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(tos));
 73  
 
 74  0
             writeChangesToStream(changes, oos);
 75  
 
 76  0
             oos.close();
 77  
 
 78  0
             boolean useCompressed = bosCompressed.size() < bosPlain.size();
 79  
 
 80  0
             byte[] data = useCompressed ? bosCompressed.toByteArray() : bosPlain.toByteArray();
 81  
 
 82  0
             byte[] encoded = Base64.encodeBase64(data);
 83  
 
 84  0
             String prefix = useCompressed ? GZIP_BYTESTREAM_PREFIX : BYTESTREAM_PREFIX;
 85  
 
 86  0
             return prefix + new String(encoded);
 87  
         }
 88  0
         catch (Exception ex) {
 89  0
             throw new ApplicationRuntimeException(RecordMessages.encodeFailure(ex), ex);
 90  
         }
 91  
     }
 92  
 
 93  
     public List decodePageChanges(String encoded)
 94  
     {
 95  0
         if (HiveMind.isBlank(encoded))
 96  0
             return Collections.EMPTY_LIST;
 97  
 
 98  0
         String prefix = encoded.substring(0, 1);
 99  
 
 100  0
         if (!(prefix.equals(BYTESTREAM_PREFIX) || prefix.equals(GZIP_BYTESTREAM_PREFIX)))
 101  0
             throw new ApplicationRuntimeException(RecordMessages.unknownPrefix(prefix));
 102  
 
 103  
         try {
 104  
             // Strip off the prefix, feed that in as a MIME stream.
 105  
 
 106  0
             byte[] decoded = Base64.decodeBase64(encoded.substring(1).getBytes());
 107  
 
 108  0
             InputStream is = new ByteArrayInputStream(decoded);
 109  
 
 110  0
             if (prefix.equals(GZIP_BYTESTREAM_PREFIX))
 111  0
                 is = new GZIPInputStream(is);
 112  
 
 113  
             // I believe this is more efficient; the buffered input stream should ask the
 114  
             // GZIP stream for large blocks of un-gzipped bytes, with should be more efficient.
 115  
             // The object input stream will probably be looking for just a few bytes at
 116  
             // a time. We use a resolving object input stream that knows how to find
 117  
             // classes not normally acessible.
 118  
 
 119  0
             ObjectInputStream ois = new ResolvingObjectInputStream(_classResolver, new BufferedInputStream(is));
 120  
 
 121  0
             List result = readChangesFromStream(ois);
 122  
 
 123  0
             ois.close();
 124  
 
 125  0
             return result;
 126  
         }
 127  0
         catch (Exception ex) {
 128  0
             throw new ApplicationRuntimeException(RecordMessages.decodeFailure(ex), ex);
 129  
         }
 130  
     }
 131  
 
 132  
     protected void writeChangesToStream(List changes, ObjectOutputStream oos)
 133  
             throws IOException
 134  
     {
 135  0
         oos.writeInt(changes.size());
 136  
 
 137  0
         Iterator i = changes.iterator();
 138  0
         while (i.hasNext()) {
 139  0
             PropertyChange pc = (PropertyChange) i.next();
 140  
 
 141  0
             String componentPath = pc.getComponentPath();
 142  0
             String propertyName = pc.getPropertyName();
 143  0
             Object value = pc.getNewValue();
 144  
 
 145  0
             oos.writeBoolean(componentPath != null);
 146  
 
 147  0
             if (componentPath != null)
 148  0
                 oos.writeUTF(componentPath);
 149  
 
 150  0
             oos.writeUTF(propertyName);
 151  
 
 152  0
             oos.writeObject(value);
 153  0
         }
 154  0
     }
 155  
 
 156  
     protected List readChangesFromStream(ObjectInputStream ois)
 157  
             throws IOException, ClassNotFoundException
 158  
     {
 159  0
         List result = new ArrayList();
 160  
 
 161  0
         int count = ois.readInt();
 162  
 
 163  0
         for (int i = 0; i < count; i++) {
 164  0
             boolean hasPath = ois.readBoolean();
 165  0
             String componentPath = hasPath ? ois.readUTF() : null;
 166  0
             String propertyName = ois.readUTF();
 167  0
             Object value = ois.readObject();
 168  
 
 169  0
             PropertyChangeImpl pc = new PropertyChangeImpl(componentPath, propertyName, value);
 170  
 
 171  0
             result.add(pc);
 172  
         }
 173  
 
 174  0
         return result;
 175  
     }
 176  
 
 177  
     public void setClassResolver(ClassResolver resolver)
 178  
     {
 179  0
         _classResolver = resolver;
 180  0
     }
 181  
 }