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.io.IOException;
022: import java.util.Set;
023: import java.util.logging.Level;
024:
025: import org.restlet.Context;
026: import org.restlet.data.ChallengeRequest;
027: import org.restlet.data.ChallengeResponse;
028: import org.restlet.data.ClientInfo;
029: import org.restlet.data.Conditions;
030: import org.restlet.data.Dimension;
031: import org.restlet.data.MediaType;
032: import org.restlet.data.Method;
033: import org.restlet.data.Parameter;
034: import org.restlet.data.Reference;
035: import org.restlet.data.Request;
036: import org.restlet.data.Response;
037: import org.restlet.data.Status;
038: import org.restlet.util.DateUtils;
039: import org.restlet.util.Series;
040:
041: import com.noelios.restlet.Engine;
042: import com.noelios.restlet.util.CookieReader;
043: import com.noelios.restlet.util.CookieUtils;
044: import com.noelios.restlet.util.HeaderReader;
045: import com.noelios.restlet.util.PreferenceUtils;
046: import com.noelios.restlet.util.SecurityUtils;
047:
048: /**
049: * Converter of high-level uniform calls into low-level HTTP client calls.
050: *
051: * @author Jerome Louvel (contact@noelios.com)
052: */
053: public class HttpClientConverter extends HttpConverter {
054: /**
055: * Constructor.
056: *
057: * @param context
058: * The context to use.
059: */
060: public HttpClientConverter(Context context) {
061: super (context);
062: }
063:
064: /**
065: * Converts a low-level HTTP call into a high-level uniform call.
066: *
067: * @param client
068: * The HTTP client that will handle the call.
069: * @param request
070: * The high-level request.
071: * @return A new high-level uniform call.
072: */
073: public HttpClientCall toSpecific(HttpClientHelper client,
074: Request request) {
075: // Create the low-level HTTP client call
076: HttpClientCall result = client.create(request);
077:
078: // Add the request headers
079: addRequestHeaders(result, request);
080:
081: return result;
082: }
083:
084: /**
085: * Commits the changes to a handled HTTP client call back into the original
086: * uniform call. The default implementation first invokes the
087: * "addResponseHeaders" then asks the "htppCall" to send the response back
088: * to the client.
089: *
090: * @param httpCall
091: * The original HTTP call.
092: * @param request
093: * The high-level request.
094: * @param response
095: * The high-level response.
096: */
097: public void commit(HttpClientCall httpCall, Request request,
098: Response response) {
099: if (httpCall != null) {
100: // Send the request to the client
101: response.setStatus(httpCall.sendRequest(request));
102:
103: // Get the server address
104: response.getServerInfo().setAddress(
105: httpCall.getServerAddress());
106: response.getServerInfo().setPort(httpCall.getServerPort());
107:
108: // Read the response headers
109: readResponseHeaders(httpCall, response);
110:
111: // Set the entity
112: response.setEntity(httpCall.getResponseEntity());
113: }
114: }
115:
116: /**
117: * Adds the request headers of a uniform call to a HTTP client call.
118: *
119: * @param httpCall
120: * The HTTP client call.
121: * @param request
122: * The high-level request.
123: */
124: @SuppressWarnings("unchecked")
125: protected void addRequestHeaders(HttpClientCall httpCall,
126: Request request) {
127: if (httpCall != null) {
128: Series<Parameter> requestHeaders = httpCall
129: .getRequestHeaders();
130:
131: // Manually add the host name and port when it is potentially
132: // different from the one specified in the target resource
133: // reference.
134: Reference hostRef = (request.getResourceRef().getBaseRef() != null) ? request
135: .getResourceRef().getBaseRef()
136: : request.getResourceRef();
137:
138: if (hostRef.getHostDomain() != null) {
139: String host = hostRef.getHostDomain();
140: int hostRefPortValue = hostRef.getHostPort();
141:
142: if ((hostRefPortValue != -1)
143: && (hostRefPortValue != request.getProtocol()
144: .getDefaultPort())) {
145: host = host + ':' + hostRefPortValue;
146: }
147:
148: requestHeaders.add(HttpConstants.HEADER_HOST, host);
149: }
150:
151: // Add the user agent header
152: if (request.getClientInfo().getAgent() != null) {
153: requestHeaders.add(HttpConstants.HEADER_USER_AGENT,
154: request.getClientInfo().getAgent());
155: } else {
156: requestHeaders.add(HttpConstants.HEADER_USER_AGENT,
157: Engine.VERSION_HEADER);
158: }
159:
160: // Add the conditions
161: Conditions condition = request.getConditions();
162: if (condition.getMatch() != null) {
163: StringBuilder value = new StringBuilder();
164:
165: for (int i = 0; i < condition.getMatch().size(); i++) {
166: if (i > 0)
167: value.append(", ");
168: value.append(condition.getMatch().get(i).format());
169: }
170:
171: httpCall.getRequestHeaders()
172: .add(HttpConstants.HEADER_IF_MATCH,
173: value.toString());
174: }
175:
176: if (condition.getModifiedSince() != null) {
177: String imsDate = DateUtils.format(condition
178: .getModifiedSince(), DateUtils.FORMAT_RFC_1123
179: .get(0));
180: requestHeaders
181: .add(HttpConstants.HEADER_IF_MODIFIED_SINCE,
182: imsDate);
183: }
184:
185: if (condition.getNoneMatch() != null) {
186: StringBuilder value = new StringBuilder();
187:
188: for (int i = 0; i < condition.getNoneMatch().size(); i++) {
189: if (i > 0)
190: value.append(", ");
191: value.append(condition.getNoneMatch().get(i)
192: .format());
193: }
194:
195: requestHeaders.add(HttpConstants.HEADER_IF_NONE_MATCH,
196: value.toString());
197: }
198:
199: if (condition.getUnmodifiedSince() != null) {
200: String iusDate = DateUtils.format(condition
201: .getUnmodifiedSince(),
202: DateUtils.FORMAT_RFC_1123.get(0));
203: requestHeaders.add(
204: HttpConstants.HEADER_IF_UNMODIFIED_SINCE,
205: iusDate);
206: }
207:
208: // Add the cookies
209: if (request.getCookies().size() > 0) {
210: String cookies = CookieUtils.format(request
211: .getCookies());
212: requestHeaders
213: .add(HttpConstants.HEADER_COOKIE, cookies);
214: }
215:
216: // Add the referrer header
217: if (request.getReferrerRef() != null) {
218: requestHeaders.add(HttpConstants.HEADER_REFERRER,
219: request.getReferrerRef().toString());
220: }
221:
222: // Add the preferences
223: ClientInfo client = request.getClientInfo();
224: if (client.getAcceptedMediaTypes().size() > 0) {
225: try {
226: requestHeaders.add(HttpConstants.HEADER_ACCEPT,
227: PreferenceUtils.format(client
228: .getAcceptedMediaTypes()));
229: } catch (IOException ioe) {
230: getLogger().log(Level.WARNING,
231: "Unable to format the HTTP Accept header",
232: ioe);
233: }
234: } else {
235: requestHeaders.add(HttpConstants.HEADER_ACCEPT,
236: MediaType.ALL.getName());
237: }
238:
239: if (client.getAcceptedCharacterSets().size() > 0) {
240: try {
241: requestHeaders.add(
242: HttpConstants.HEADER_ACCEPT_CHARSET,
243: PreferenceUtils.format(client
244: .getAcceptedCharacterSets()));
245: } catch (IOException ioe) {
246: getLogger().log(Level.WARNING,
247: "Unable to format the HTTP Accept header",
248: ioe);
249: }
250: }
251:
252: if (client.getAcceptedEncodings().size() > 0) {
253: try {
254: requestHeaders.add(
255: HttpConstants.HEADER_ACCEPT_ENCODING,
256: PreferenceUtils.format(client
257: .getAcceptedEncodings()));
258: } catch (IOException ioe) {
259: getLogger().log(Level.WARNING,
260: "Unable to format the HTTP Accept header",
261: ioe);
262: }
263: }
264:
265: if (client.getAcceptedLanguages().size() > 0) {
266: try {
267: requestHeaders.add(
268: HttpConstants.HEADER_ACCEPT_LANGUAGE,
269: PreferenceUtils.format(client
270: .getAcceptedLanguages()));
271: } catch (IOException ioe) {
272: getLogger().log(Level.WARNING,
273: "Unable to format the HTTP Accept header",
274: ioe);
275: }
276: }
277:
278: // Add entity headers
279: if (request.getEntity() != null) {
280: if (request.getEntity().getMediaType() != null) {
281: String contentType = request.getEntity()
282: .getMediaType().toString();
283:
284: // Specify the character set parameter if required
285: if ((request.getEntity().getMediaType()
286: .getParameters().getFirstValue("charset") == null)
287: && (request.getEntity().getCharacterSet() != null)) {
288: contentType = contentType
289: + "; charset="
290: + request.getEntity().getCharacterSet()
291: .getName();
292: }
293:
294: requestHeaders.add(
295: HttpConstants.HEADER_CONTENT_TYPE,
296: contentType);
297: }
298:
299: if (!request.getEntity().getEncodings().isEmpty()) {
300: StringBuilder value = new StringBuilder();
301: for (int i = 0; i < request.getEntity()
302: .getEncodings().size(); i++) {
303: if (i > 0)
304: value.append(", ");
305: value.append(request.getEntity().getEncodings()
306: .get(i).getName());
307: }
308: requestHeaders.add(
309: HttpConstants.HEADER_CONTENT_ENCODING,
310: value.toString());
311: }
312:
313: if (!request.getEntity().getLanguages().isEmpty()) {
314: StringBuilder value = new StringBuilder();
315: for (int i = 0; i < request.getEntity()
316: .getLanguages().size(); i++) {
317: if (i > 0)
318: value.append(", ");
319: value.append(request.getEntity().getLanguages()
320: .get(i).getName());
321: }
322: requestHeaders.add(
323: HttpConstants.HEADER_CONTENT_LANGUAGE,
324: value.toString());
325: }
326: }
327:
328: // Add user-defined extension headers
329: Series<Parameter> additionalHeaders = (Series<Parameter>) request
330: .getAttributes().get(
331: HttpConstants.ATTRIBUTE_HEADERS);
332: addAdditionalHeaders(requestHeaders, additionalHeaders);
333:
334: // Add the security headers. NOTE: This must stay at the end because
335: // the AWS challenge scheme requires access to all HTTP headers
336: ChallengeResponse challengeResponse = request
337: .getChallengeResponse();
338: if (challengeResponse != null) {
339: requestHeaders.add(HttpConstants.HEADER_AUTHORIZATION,
340: SecurityUtils.format(challengeResponse,
341: request, requestHeaders));
342: }
343: }
344: }
345:
346: /**
347: * Reads the response headers of a handled HTTP client call to update the
348: * original uniform call.
349: *
350: * @param httpCall
351: * The handled HTTP client call.
352: * @param response
353: * The high-level response to update.
354: */
355: protected void readResponseHeaders(HttpClientCall httpCall,
356: Response response) {
357: try {
358: // Put the response headers in the call's attributes map
359: response.getAttributes().put(
360: HttpConstants.ATTRIBUTE_HEADERS,
361: httpCall.getResponseHeaders());
362:
363: // Read info from headers
364: for (Parameter header : httpCall.getResponseHeaders()) {
365: if (header.getName().equalsIgnoreCase(
366: HttpConstants.HEADER_LOCATION)) {
367: response.setRedirectRef(header.getValue());
368: } else if ((header.getName()
369: .equalsIgnoreCase(HttpConstants.HEADER_SET_COOKIE))
370: || (header.getName()
371: .equalsIgnoreCase(HttpConstants.HEADER_SET_COOKIE2))) {
372: try {
373: CookieReader cr = new CookieReader(getLogger(),
374: header.getValue());
375: response.getCookieSettings().add(
376: cr.readCookieSetting());
377: } catch (Exception e) {
378: getLogger().log(
379: Level.WARNING,
380: "Error during cookie setting parsing. Header: "
381: + header.getValue(), e);
382: }
383: } else if (header.getName().equalsIgnoreCase(
384: HttpConstants.HEADER_WWW_AUTHENTICATE)) {
385: ChallengeRequest request = SecurityUtils
386: .parseRequest(header.getValue());
387: response.setChallengeRequest(request);
388: } else if (header.getName().equalsIgnoreCase(
389: HttpConstants.HEADER_SERVER)) {
390: response.getServerInfo()
391: .setAgent(header.getValue());
392: } else if (header.getName().equalsIgnoreCase(
393: HttpConstants.HEADER_ALLOW)) {
394: HeaderReader hr = new HeaderReader(header
395: .getValue());
396: String value = hr.readValue();
397: Set<Method> allowedMethods = response
398: .getAllowedMethods();
399: while (value != null) {
400: allowedMethods.add(Method.valueOf(value));
401: value = hr.readValue();
402: }
403: } else if (header.getName().equalsIgnoreCase(
404: HttpConstants.HEADER_VARY)) {
405: HeaderReader hr = new HeaderReader(header
406: .getValue());
407: String value = hr.readValue();
408: Set<Dimension> dimensions = response
409: .getDimensions();
410: while (value != null) {
411: if (value
412: .equalsIgnoreCase(HttpConstants.HEADER_ACCEPT)) {
413: dimensions.add(Dimension.MEDIA_TYPE);
414: } else if (value
415: .equalsIgnoreCase(HttpConstants.HEADER_ACCEPT_CHARSET)) {
416: dimensions.add(Dimension.CHARACTER_SET);
417: } else if (value
418: .equalsIgnoreCase(HttpConstants.HEADER_ACCEPT_ENCODING)) {
419: dimensions.add(Dimension.ENCODING);
420: } else if (value
421: .equalsIgnoreCase(HttpConstants.HEADER_ACCEPT_LANGUAGE)) {
422: dimensions.add(Dimension.LANGUAGE);
423: } else if (value
424: .equalsIgnoreCase(HttpConstants.HEADER_USER_AGENT)) {
425: dimensions.add(Dimension.CLIENT_AGENT);
426: } else if (value.equals("*")) {
427: dimensions.add(Dimension.UNSPECIFIED);
428: }
429:
430: value = hr.readValue();
431: }
432: }
433: }
434: } catch (Exception e) {
435: getLogger()
436: .log(
437: Level.FINE,
438: "An error occured during the processing of the HTTP response.",
439: e);
440: response
441: .setStatus(new Status(
442: Status.CONNECTOR_ERROR_INTERNAL,
443: "Unable to process the response. "
444: + e.getMessage()));
445: }
446: }
447:
448: }
|