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:
019: package com.noelios.restlet.http;
020:
021: import java.util.Date;
022: import java.util.List;
023: import java.util.Set;
024: import java.util.logging.Level;
025:
026: import org.restlet.Context;
027: import org.restlet.data.CookieSetting;
028: import org.restlet.data.Dimension;
029: import org.restlet.data.Encoding;
030: import org.restlet.data.Method;
031: import org.restlet.data.Parameter;
032: import org.restlet.data.Status;
033: import org.restlet.resource.Representation;
034: import org.restlet.util.DateUtils;
035: import org.restlet.util.Series;
036:
037: import com.noelios.restlet.util.CookieUtils;
038: import com.noelios.restlet.util.SecurityUtils;
039:
040: /**
041: * Converter of low-level HTTP server calls into high-level uniform calls.
042: *
043: * @author Jerome Louvel (contact@noelios.com)
044: */
045: public class HttpServerConverter extends HttpConverter {
046: /**
047: * Constructor.
048: *
049: * @param context
050: * The client context.
051: */
052: public HttpServerConverter(Context context) {
053: super (context);
054: }
055:
056: /**
057: * Converts a low-level HTTP call into a high-level uniform request.
058: *
059: * @param httpCall
060: * The low-level HTTP call.
061: * @return A new high-level uniform request.
062: */
063: public HttpRequest toRequest(HttpServerCall httpCall) {
064: HttpRequest result = new HttpRequest(getContext(), httpCall);
065: result.getAttributes().put(HttpConstants.ATTRIBUTE_HEADERS,
066: httpCall.getRequestHeaders());
067:
068: if (httpCall.getVersion() != null) {
069: result.getAttributes().put(HttpConstants.ATTRIBUTE_VERSION,
070: httpCall.getVersion());
071: }
072:
073: return result;
074: }
075:
076: /**
077: * Commits the changes to a handled uniform call back into the original HTTP
078: * call. The default implementation first invokes the "addResponseHeaders"
079: * then asks the "htppCall" to send the response back to the client.
080: *
081: * @param response
082: * The high-level response.
083: */
084: public void commit(HttpResponse response) {
085: try {
086: // Add the response headers
087: addResponseHeaders(response);
088:
089: // Send the response to the client
090: response.getHttpCall().sendResponse(response);
091: } catch (Exception e) {
092: getLogger().log(Level.INFO, "Exception intercepted", e);
093: response.getHttpCall().setStatusCode(
094: Status.SERVER_ERROR_INTERNAL.getCode());
095: response.getHttpCall().setReasonPhrase(
096: "An unexpected exception occured");
097: }
098: }
099:
100: /**
101: * Adds the response headers for the handled uniform call.
102: *
103: * @param response
104: * The response returned.
105: */
106: @SuppressWarnings("unchecked")
107: protected void addResponseHeaders(HttpResponse response) {
108: try {
109: // Add all the necessary response headers
110: Series<Parameter> responseHeaders = response.getHttpCall()
111: .getResponseHeaders();
112:
113: if (response.getStatus().equals(
114: Status.CLIENT_ERROR_METHOD_NOT_ALLOWED)
115: || Method.OPTIONS.equals(response.getRequest()
116: .getMethod())) {
117: // Format the "Allow" header
118: StringBuilder sb = new StringBuilder();
119: boolean first = true;
120: for (Method method : response.getAllowedMethods()) {
121: if (first) {
122: first = false;
123: } else {
124: sb.append(", ");
125: }
126:
127: sb.append(method.getName());
128: }
129:
130: responseHeaders.add(HttpConstants.HEADER_ALLOW, sb
131: .toString());
132: }
133:
134: // Add the date
135: responseHeaders.add(HttpConstants.HEADER_DATE, DateUtils
136: .format(new Date(), DateUtils.FORMAT_RFC_1123
137: .get(0)));
138:
139: // Add the cookie settings
140: List<CookieSetting> cookies = response.getCookieSettings();
141: for (int i = 0; i < cookies.size(); i++) {
142: responseHeaders.add(HttpConstants.HEADER_SET_COOKIE,
143: CookieUtils.format(cookies.get(i)));
144: }
145:
146: // Set the redirection URI
147: if (response.getRedirectRef() != null) {
148: responseHeaders.add(HttpConstants.HEADER_LOCATION,
149: response.getRedirectRef().toString());
150: }
151:
152: // Set the security data
153: if (response.getChallengeRequest() != null) {
154: responseHeaders.add(
155: HttpConstants.HEADER_WWW_AUTHENTICATE,
156: SecurityUtils.format(response
157: .getChallengeRequest()));
158: }
159:
160: // Set the server name again
161: response.getHttpCall().getResponseHeaders().add(
162: HttpConstants.HEADER_SERVER,
163: response.getServerInfo().getAgent());
164:
165: // Set the status code in the response
166: if (response.getStatus() != null) {
167: response.getHttpCall().setStatusCode(
168: response.getStatus().getCode());
169: response.getHttpCall().setReasonPhrase(
170: response.getStatus().getDescription());
171: }
172:
173: // If an entity was set during the call, copy it to the output
174: // stream;
175: if (response.getEntity() != null) {
176: Representation entity = response.getEntity();
177:
178: if (entity.getExpirationDate() != null) {
179: responseHeaders.add(HttpConstants.HEADER_EXPIRES,
180: response.getHttpCall().formatDate(
181: entity.getExpirationDate(), false));
182: }
183:
184: if (!entity.getEncodings().isEmpty()) {
185: StringBuilder value = new StringBuilder();
186: for (Encoding encoding : entity.getEncodings()) {
187: if (!encoding.equals(Encoding.IDENTITY)) {
188: if (value.length() > 0)
189: value.append(", ");
190: value.append(encoding.getName());
191: }
192: responseHeaders.add(
193: HttpConstants.HEADER_CONTENT_ENCODING,
194: value.toString());
195: }
196: }
197:
198: if (!entity.getLanguages().isEmpty()) {
199: StringBuilder value = new StringBuilder();
200: for (int i = 0; i < entity.getLanguages().size(); i++) {
201: if (i > 0)
202: value.append(", ");
203: value.append(entity.getLanguages().get(i)
204: .getName());
205: }
206: responseHeaders.add(
207: HttpConstants.HEADER_CONTENT_LANGUAGE,
208: value.toString());
209: }
210:
211: if (entity.getMediaType() != null) {
212: StringBuilder contentType = new StringBuilder(
213: entity.getMediaType().getName());
214:
215: if (entity.getCharacterSet() != null) {
216: // Specify the character set parameter
217: contentType.append("; charset=").append(
218: entity.getCharacterSet().getName());
219: }
220:
221: responseHeaders.add(
222: HttpConstants.HEADER_CONTENT_TYPE,
223: contentType.toString());
224: }
225:
226: if (entity.getModificationDate() != null) {
227: responseHeaders
228: .add(
229: HttpConstants.HEADER_LAST_MODIFIED,
230: response
231: .getHttpCall()
232: .formatDate(
233: entity
234: .getModificationDate(),
235: false));
236: }
237:
238: if (entity.getTag() != null) {
239: responseHeaders.add(HttpConstants.HEADER_ETAG,
240: entity.getTag().format());
241: }
242:
243: if (response.getEntity().getSize() != Representation.UNKNOWN_SIZE) {
244: responseHeaders.add(
245: HttpConstants.HEADER_CONTENT_LENGTH, Long
246: .toString(response.getEntity()
247: .getSize()));
248: }
249:
250: if (response.getEntity().getIdentifier() != null) {
251: responseHeaders.add(
252: HttpConstants.HEADER_CONTENT_LOCATION,
253: response.getEntity().getIdentifier()
254: .toString());
255: }
256: }
257:
258: // Send the Vary header only to none-MSIE user agents as MSIE seems
259: // to support partially and badly this header (cf issue 261).
260: if (!(response.getRequest().getClientInfo().getAgent() != null && response
261: .getRequest().getClientInfo().getAgent().contains(
262: "MSIE"))) {
263: // Add the Vary header if content negotiation was used
264: Set<Dimension> dimensions = response.getDimensions();
265: if (!dimensions.isEmpty()) {
266: StringBuilder sb = new StringBuilder();
267: boolean first = true;
268:
269: if (dimensions.contains(Dimension.CLIENT_ADDRESS)
270: || dimensions.contains(Dimension.TIME)
271: || dimensions
272: .contains(Dimension.UNSPECIFIED)) {
273: // From an HTTP point of view the representations can
274: // vary in unspecified ways
275: responseHeaders.add(HttpConstants.HEADER_VARY,
276: "*");
277: } else {
278: for (Dimension dim : response.getDimensions()) {
279: if (first) {
280: first = false;
281: } else {
282: sb.append(", ");
283: }
284:
285: if (dim == Dimension.CHARACTER_SET) {
286: sb
287: .append(HttpConstants.HEADER_ACCEPT_CHARSET);
288: } else if (dim == Dimension.CLIENT_AGENT) {
289: sb
290: .append(HttpConstants.HEADER_USER_AGENT);
291: } else if (dim == Dimension.ENCODING) {
292: sb
293: .append(HttpConstants.HEADER_ACCEPT_ENCODING);
294: } else if (dim == Dimension.LANGUAGE) {
295: sb
296: .append(HttpConstants.HEADER_ACCEPT_LANGUAGE);
297: } else if (dim == Dimension.MEDIA_TYPE) {
298: sb.append(HttpConstants.HEADER_ACCEPT);
299: }
300: }
301:
302: responseHeaders.add(HttpConstants.HEADER_VARY,
303: sb.toString());
304: }
305: }
306: }
307:
308: // Add user-defined extension headers
309: Series<Parameter> additionalHeaders = (Series<Parameter>) response
310: .getAttributes().get(
311: HttpConstants.ATTRIBUTE_HEADERS);
312: addAdditionalHeaders(responseHeaders, additionalHeaders);
313: } catch (Exception e) {
314: getLogger()
315: .log(
316: Level.INFO,
317: "Exception intercepted while adding the response headers",
318: e);
319: response.getHttpCall().setStatusCode(
320: Status.SERVER_ERROR_INTERNAL.getCode());
321: response.getHttpCall().setReasonPhrase(
322: Status.SERVER_ERROR_INTERNAL.getDescription());
323: }
324: }
325: }
|