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 }