001: /*
002: * Copyright 2005-2007 Noelios Consulting.
003: *
004: * The contents of this file are subject to the terms of the Common Development
005: * and Distribution License (the "License"). You may not use this file except in
006: * compliance with the License.
007: *
008: * You can obtain a copy of the license at
009: * http://www.opensource.org/licenses/cddl1.txt See the License for the specific
010: * language governing permissions and limitations under the License.
011: *
012: * When distributing Covered Code, include this CDDL HEADER in each file and
013: * include the License file at http://www.opensource.org/licenses/cddl1.txt If
014: * applicable, add the following below this CDDL HEADER, with the fields
015: * enclosed by brackets "[]" replaced with your own identifying information:
016: * Portions Copyright [yyyy] [name of copyright owner]
017: *
018: * Portions Copyright 2006 Lars Heuer (heuer[at]semagia.com)
019: */
020:
021: package com.noelios.restlet.application;
022:
023: import java.util.ArrayList;
024: import java.util.Arrays;
025: import java.util.Iterator;
026: import java.util.List;
027:
028: import org.restlet.Context;
029: import org.restlet.Filter;
030: import org.restlet.data.ClientInfo;
031: import org.restlet.data.Encoding;
032: import org.restlet.data.MediaType;
033: import org.restlet.data.Preference;
034: import org.restlet.data.Request;
035: import org.restlet.data.Response;
036: import org.restlet.resource.Representation;
037:
038: /**
039: * Filter compressing entities. The best encoding is automatically selected
040: * based on the preferences of the client and on the encoding supported by NRE:
041: * GZip, Zip and Deflate.<br/> If the {@link org.restlet.resource.Representation}
042: * has an unknown size, it will always be a candidate for encoding. Candidate
043: * representations need to respect media type criteria by the lists of accepted
044: * and ignored media types.
045: *
046: * @author Lars Heuer (heuer[at]semagia.com) <a
047: * href="http://semagia.com/">Semagia</a>
048: * @author Jerome Louvel (contact@noelios.com) <a
049: * href="http://www.noelios.com">Noelios Consulting</a>
050: */
051: public class Encoder extends Filter {
052: /**
053: * Indicates if the encoding should always occur, regardless of the size.
054: */
055: public static final int ENCODE_ALL_SIZES = -1;
056:
057: /**
058: * Indicates if the request entity should be encoded.
059: */
060: private boolean encodeRequest;
061:
062: /**
063: * Indicates if the response entity should be encoded.
064: */
065: private boolean encodeResponse;
066:
067: /**
068: * The minimal size necessary for encoding.
069: */
070: private long mininumSize;
071:
072: /**
073: * The media types that should be encoded.
074: */
075: private List<MediaType> acceptedMediaTypes;
076:
077: /**
078: * The media types that should be ignored.
079: */
080: private List<MediaType> ignoredMediaTypes;
081:
082: /**
083: * Constructor using the default media types and with
084: * {@link #ENCODE_ALL_SIZES} setting. This constructor will only encode
085: * response entities after call handling.
086: *
087: * @param context
088: * The context.
089: */
090: public Encoder(Context context) {
091: this (context, false, true, ENCODE_ALL_SIZES,
092: getDefaultAcceptedMediaTypes(),
093: getDefaultIgnoredMediaTypes());
094: }
095:
096: /**
097: * Constructor.
098: *
099: * @param context
100: * The context.
101: * @param encodeInput
102: * Indicates if the request entities should be encoded.
103: * @param encodeOutput
104: * Indicates if the response entities should be encoded.
105: * @param minimumSize
106: * The minimal size of the representation where compression
107: * should be used.
108: * @param acceptedMediaTypes
109: * The media types that should be encoded.
110: * @param ignoredMediaTypes
111: * The media types that should be ignored.
112: */
113: public Encoder(Context context, boolean encodeInput,
114: boolean encodeOutput, long minimumSize,
115: List<MediaType> acceptedMediaTypes,
116: List<MediaType> ignoredMediaTypes) {
117: super (context);
118: this .encodeRequest = encodeInput;
119: this .encodeResponse = encodeOutput;
120: this .mininumSize = minimumSize;
121: this .acceptedMediaTypes = acceptedMediaTypes;
122: this .ignoredMediaTypes = ignoredMediaTypes;
123: }
124:
125: /**
126: * Returns the list of default encoded media types. This can be overriden by
127: * subclasses. By default, all media types are encoded (except those
128: * explicitely ignored).
129: *
130: * @return The list of default encoded media types.
131: */
132: public static List<MediaType> getDefaultAcceptedMediaTypes() {
133: List<MediaType> result = new ArrayList<MediaType>();
134: result.add(MediaType.ALL);
135: return result;
136: }
137:
138: /**
139: * Returns the list of default ignored media types. This can be overriden by
140: * subclasses. By default, all archive, audio, image and video media types
141: * are ignored.
142: *
143: * @return The list of default ignored media types.
144: */
145: public static List<MediaType> getDefaultIgnoredMediaTypes() {
146: List<MediaType> result = Arrays.<MediaType> asList(
147: MediaType.APPLICATION_CAB,
148: MediaType.APPLICATION_GNU_ZIP,
149: MediaType.APPLICATION_ZIP,
150: MediaType.APPLICATION_GNU_TAR,
151: MediaType.APPLICATION_JAVA_ARCHIVE,
152: MediaType.APPLICATION_STUFFIT,
153: MediaType.APPLICATION_TAR, MediaType.AUDIO_ALL,
154: MediaType.IMAGE_ALL, MediaType.VIDEO_ALL);
155: return result;
156: }
157:
158: /**
159: * Allows filtering before its handling by the target Restlet. Does nothing
160: * by default.
161: *
162: * @param request
163: * The request to filter.
164: * @param response
165: * The response to filter.
166: */
167: public void beforeHandle(Request request, Response response) {
168: // Check if encoding of the request entity is needed
169: if (isEncodeRequest() && canEncode(request.getEntity())) {
170: request.setEntity(encode(request.getClientInfo(), request
171: .getEntity()));
172: }
173: }
174:
175: /**
176: * Allows filtering after its handling by the target Restlet. Does nothing
177: * by default.
178: *
179: * @param request
180: * The request to filter.
181: * @param response
182: * The response to filter.
183: */
184: public void afterHandle(Request request, Response response) {
185: // Check if encoding of the response entity is needed
186: if (isEncodeResponse() && canEncode(response.getEntity())) {
187: response.setEntity(encode(request.getClientInfo(), response
188: .getEntity()));
189: }
190: }
191:
192: /**
193: * Indicates if a representation can be encoded.
194: *
195: * @param representation
196: * The representation to test.
197: * @return True if the call can be encoded.
198: */
199: public boolean canEncode(Representation representation) {
200: // Test the existence of the representation and that no existing
201: // encoding applies
202: boolean result = false;
203: if (representation != null) {
204: boolean identity = true;
205: for (Iterator<Encoding> iter = representation
206: .getEncodings().iterator(); identity
207: && iter.hasNext();) {
208: identity = (iter.next().equals(Encoding.IDENTITY));
209: }
210: result = identity;
211: }
212:
213: if (result) {
214: // Test the size of the representation
215: result = (getMinimumSize() == ENCODE_ALL_SIZES)
216: || (representation.getSize() == Representation.UNKNOWN_SIZE)
217: || (representation.getSize() >= getMinimumSize());
218: }
219:
220: if (result) {
221: // Test the acceptance of the media type
222: MediaType mediaType = representation.getMediaType();
223: boolean accepted = false;
224: for (Iterator<MediaType> iter = getAcceptedMediaTypes()
225: .iterator(); !accepted && iter.hasNext();) {
226: accepted = iter.next().includes(mediaType);
227: }
228:
229: result = accepted;
230: }
231:
232: if (result) {
233: // Test the rejection of the media type
234: MediaType mediaType = representation.getMediaType();
235: boolean rejected = false;
236: for (Iterator<MediaType> iter = getIgnoredMediaTypes()
237: .iterator(); !rejected && iter.hasNext();) {
238: rejected = iter.next().includes(mediaType);
239: }
240:
241: result = !rejected;
242: }
243:
244: return result;
245: }
246:
247: /**
248: * Encodes a given representation if an encoding is supported by the client.
249: *
250: * @param client
251: * The client preferences to use.
252: * @param representation
253: * The representation to encode.
254: * @return The encoded representation or the original one if no encoding
255: * supported by the client.
256: */
257: public Representation encode(ClientInfo client,
258: Representation representation) {
259: Representation result = representation;
260: Encoding bestEncoding = getBestEncoding(client);
261:
262: if (bestEncoding != null) {
263: result = new EncodeRepresentation(bestEncoding,
264: representation);
265: }
266:
267: return result;
268: }
269:
270: /**
271: * Returns the best supported encoding for a given client.
272: *
273: * @param client
274: * The client preferences to use.
275: * @return The best supported encoding for the given call.
276: */
277: public Encoding getBestEncoding(ClientInfo client) {
278: Encoding bestEncoding = null;
279: Encoding currentEncoding = null;
280: Preference<Encoding> currentPref = null;
281: float bestScore = 0F;
282:
283: for (Iterator<Encoding> iter = EncodeRepresentation
284: .getSupportedEncodings().iterator(); iter.hasNext();) {
285: currentEncoding = iter.next();
286:
287: for (Iterator<Preference<Encoding>> iter2 = client
288: .getAcceptedEncodings().iterator(); iter2.hasNext();) {
289: currentPref = iter2.next();
290:
291: if (currentPref.getMetadata().equals(Encoding.ALL)
292: || currentPref.getMetadata().equals(
293: currentEncoding)) {
294: // A match was found, compute its score
295: if (currentPref.getQuality() > bestScore) {
296: bestScore = currentPref.getQuality();
297: bestEncoding = currentEncoding;
298: }
299: }
300: }
301: }
302:
303: return bestEncoding;
304: }
305:
306: /**
307: * Indicates if the request entity should be encoded.
308: *
309: * @return True if the request entity should be encoded.
310: */
311: public boolean isEncodeRequest() {
312: return this .encodeRequest;
313: }
314:
315: /**
316: * Indicates if the request entity should be encoded.
317: *
318: * @param encodeRequest
319: * True if the request entity should be encoded.
320: */
321: public void setEncodeRequest(boolean encodeRequest) {
322: this .encodeRequest = encodeRequest;
323: }
324:
325: /**
326: * Indicates if the response entity should be encoded.
327: *
328: * @return True if the response entity should be encoded.
329: */
330: public boolean isEncodeResponse() {
331: return this .encodeResponse;
332: }
333:
334: /**
335: * Indicates if the response entity should be encoded.
336: *
337: * @param encodeResponse
338: * True if the response entity should be encoded.
339: */
340: public void setEncodeResponse(boolean encodeResponse) {
341: this .encodeResponse = encodeResponse;
342: }
343:
344: /**
345: * Returns the minimum size a representation must have before compression is
346: * done.
347: *
348: * @return The minimum size a representation must have before compression is
349: * done.
350: */
351: public long getMinimumSize() {
352: return mininumSize;
353: }
354:
355: /**
356: * Sets the minimum size a representation must have before compression is
357: * done.
358: *
359: * @param mininumSize
360: * The minimum size a representation must have before compression
361: * is done.
362: */
363: public void setMinimumSize(long mininumSize) {
364: this .mininumSize = mininumSize;
365: }
366:
367: /**
368: * Returns the media types that should be encoded.
369: *
370: * @return The media types that should be encoded.
371: */
372: public List<MediaType> getAcceptedMediaTypes() {
373: return this .acceptedMediaTypes;
374: }
375:
376: /**
377: * Returns the media types that should be ignored.
378: *
379: * @return The media types that should be ignored.
380: */
381: public List<MediaType> getIgnoredMediaTypes() {
382: return this.ignoredMediaTypes;
383: }
384:
385: }
|