View Javadoc

1   /**
2    * This file Copyright (c) 2008-2010 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.module.cache.filter;
35  
36  import info.magnolia.module.cache.util.GZipUtil;
37  
38  import java.io.IOException;
39  import java.io.ObjectInputStream;
40  import java.io.ObjectOutputStream;
41  import java.io.Serializable;
42  import java.util.ArrayList;
43  import java.util.Collection;
44  import java.util.HashMap;
45  import java.util.Iterator;
46  import java.util.Map;
47  import java.util.Map.Entry;
48  
49  import org.apache.commons.collections.MultiMap;
50  import org.apache.commons.collections.map.MultiValueMap;
51  import org.apache.commons.lang.builder.ToStringBuilder;
52  import org.apache.commons.lang.builder.ToStringStyle;
53  
54  /**
55   * Wraps a page response. It is assumed that the given content is gzipped
56   * if appropriate (i.e if the gzip filter is in the chain) and this class
57   * thus ungzips it to be able to serve both contents.
58   *
59   * @author gjoseph
60   * @version $Revision: $ ($Author: $)
61   */
62  public class CachedPage implements CachedEntry, Serializable {
63  
64      private static final ToStringStyle BYTE_ARRAY_SIZE_STYLE = new ToStringStyle() {
65          protected void appendDetail(StringBuffer buffer, String fieldName,
66                  byte[] array) {
67              super.appendDetail(buffer, fieldName, array.length + " bytes");
68          }
69      };
70  
71      // TODO : headers and cookies ?
72      private final byte[] defaultContent;
73      private final byte[] ungzippedContent;
74      private final String contentType;
75      private final String characterEncoding;
76      private final int statusCode;
77      private transient MultiMap headers;
78      private Map serializableHeadersBackingList;
79      private final long lastModificationTime;
80      // slightly fishy, but other executors needs to know if this is freshly created CachedPage and StatusCode have been manipulated or not.
81      private transient int preCacheStatusCode;
82  
83      /**
84       * @deprecated not used, since 3.6.2, as it will compress all not gzipped content of every entry created using this constructor which is not desirable for already compressed content (e.g. jpg & tif images)
85       */
86      public CachedPage(byte[] out, String contentType, String characterEncoding, int statusCode, MultiMap headers, long modificationDate) throws IOException {
87          this(out, contentType, characterEncoding, statusCode, headers, modificationDate, true);
88      }
89  
90      /**
91       * @param out Cached content.
92       * @param contentType MIME type of the cached content.
93       * @param characterEncoding Character encoding of the cached content.
94       * @param statusCode HTTP response status code (E.g. 200 - OK);
95       * @param headers Additional HTTP headers to be sent when serving this cached content.
96       * @param modificationDate Content modification date to set in the response.
97       * @param shouldCompress Flag marking this content as desirable to be sent in compressed form (should the client support such compression). Setting this to true means cache entry will contain both, compressed and flat version of the content. Compression is applied here only if content is not gzipped already.
98       * @throws IOException when failing to compress the content.
99       */
100     public CachedPage(byte[] out, String contentType, String characterEncoding, int statusCode, MultiMap headers, long modificationDate, boolean shouldCompress) throws IOException {
101         // content which is actually of a compressed type must stay that way
102         if (GZipUtil.isGZipped(out) || !shouldCompress) {
103             this.defaultContent = out;
104             this.ungzippedContent = null;
105         } else {
106             this.defaultContent = GZipUtil.gzip(out);
107             this.ungzippedContent = out;
108         }
109         this.contentType = contentType;
110         this.characterEncoding = characterEncoding;
111         this.statusCode = statusCode;
112         this.headers = headers;
113         this.lastModificationTime = modificationDate;
114     }
115 
116     // TODO : replacing getOut() with streamTo(OutputStream out) could help subclasses stream content
117     // TODO : from a File buffer for example, instead of holding byte[]s.
118     // TODO : but this would require pushing a dependency on servlet api in here - because we need
119     // TODO : to know if we can push gzipped content... or this would need to be passed as an explicit
120     // TODO : parameter, which isn't too exciting either...
121 
122 
123     public byte[] getUngzippedContent() {
124         return ungzippedContent;
125     }
126 
127     public byte[] getDefaultContent() {
128         return defaultContent;
129     }
130 
131     public String getContentType() {
132         return contentType;
133     }
134 
135     public String getCharacterEncoding() {
136         return characterEncoding;
137     }
138 
139     public int getStatusCode() {
140         return statusCode;
141     }
142 
143     public MultiMap getHeaders() {
144         return headers;
145     }
146 
147     public long getLastModificationTime() {
148         return lastModificationTime;
149     }
150 
151     public String toString() {
152         return ToStringBuilder.reflectionToString(this, BYTE_ARRAY_SIZE_STYLE);
153     }
154 
155     // serialization support until commons  collection 3.3 is released
156     private void writeObject(ObjectOutputStream out) throws IOException {
157         serializableHeadersBackingList = new HashMap();
158         Iterator iter = headers.entrySet().iterator();
159         while (iter.hasNext()) {
160             Map.Entry entry = (Entry) iter.next();
161             serializableHeadersBackingList.put(entry.getKey(), new ArrayList((Collection)entry.getValue()));
162         }
163         out.defaultWriteObject();
164     }
165 
166     private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
167         in.defaultReadObject();
168         headers = new MultiValueMap();
169         Iterator iter = serializableHeadersBackingList.entrySet().iterator();
170         while (iter.hasNext()) {
171             Map.Entry entry = (Entry) iter.next();
172             Collection c = (Collection) entry.getValue();
173             for (Iterator ic = c.iterator(); ic.hasNext();) {
174                 headers.put(entry.getKey(), ic.next());
175             }
176         }
177         serializableHeadersBackingList = null;
178    }
179     
180     public int getPreCacheStatusCode() {
181         // preCached is transient and will be 0 after deserialization (or after going through UseCache for that matter)
182         if (preCacheStatusCode == 0) {
183             return statusCode;
184         }
185         return preCacheStatusCode;
186     }
187 
188     public void setPreCacheStatusCode(int preCacheStatusCode) {
189         this.preCacheStatusCode = preCacheStatusCode;
190     }
191 
192 }