| /** |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.camel.component.restlet; |
| |
| import java.io.File; |
| import java.io.InputStream; |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.text.ParseException; |
| import java.text.SimpleDateFormat; |
| import java.util.ArrayList; |
| import java.util.Calendar; |
| import java.util.Collection; |
| import java.util.Date; |
| import java.util.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Set; |
| import javax.xml.transform.dom.DOMSource; |
| |
| import org.apache.camel.Exchange; |
| import org.apache.camel.Message; |
| import org.apache.camel.StringSource; |
| import org.apache.camel.TypeConverter; |
| import org.apache.camel.WrappedFile; |
| import org.apache.camel.component.file.GenericFile; |
| import org.apache.camel.spi.HeaderFilterStrategy; |
| import org.apache.camel.spi.HeaderFilterStrategyAware; |
| import org.apache.camel.util.MessageHelper; |
| import org.apache.camel.util.ObjectHelper; |
| import org.restlet.Request; |
| import org.restlet.Response; |
| import org.restlet.data.CacheDirective; |
| import org.restlet.data.ChallengeResponse; |
| import org.restlet.data.ChallengeScheme; |
| import org.restlet.data.CharacterSet; |
| import org.restlet.data.Form; |
| import org.restlet.data.Header; |
| import org.restlet.data.MediaType; |
| import org.restlet.data.Method; |
| import org.restlet.data.Preference; |
| import org.restlet.data.Status; |
| import org.restlet.engine.application.DecodeRepresentation; |
| import org.restlet.engine.header.HeaderConstants; |
| import org.restlet.representation.ByteArrayRepresentation; |
| import org.restlet.representation.EmptyRepresentation; |
| import org.restlet.representation.FileRepresentation; |
| import org.restlet.representation.InputRepresentation; |
| import org.restlet.representation.Representation; |
| import org.restlet.representation.StreamRepresentation; |
| import org.restlet.representation.StringRepresentation; |
| import org.restlet.util.Series; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * Default Restlet binding implementation |
| */ |
| public class DefaultRestletBinding implements RestletBinding, HeaderFilterStrategyAware { |
| private static final Logger LOG = LoggerFactory.getLogger(DefaultRestletBinding.class); |
| private static final String RFC_2822_DATE_PATTERN = "EEE, dd MMM yyyy HH:mm:ss Z"; |
| private HeaderFilterStrategy headerFilterStrategy; |
| |
| public void populateExchangeFromRestletRequest(Request request, Response response, Exchange exchange) throws Exception { |
| Message inMessage = exchange.getIn(); |
| |
| inMessage.setHeader(RestletConstants.RESTLET_REQUEST, request); |
| inMessage.setHeader(RestletConstants.RESTLET_RESPONSE, response); |
| |
| // extract headers from restlet |
| for (Map.Entry<String, Object> entry : request.getAttributes().entrySet()) { |
| if (!headerFilterStrategy.applyFilterToExternalHeaders(entry.getKey(), entry.getValue(), exchange)) { |
| String key = entry.getKey(); |
| Object value = entry.getValue(); |
| inMessage.setHeader(key, value); |
| LOG.debug("Populate exchange from Restlet request header: {} value: {}", key, value); |
| } |
| } |
| |
| // we need to dig a bit to grab the content-type |
| Series<Header> series = (Series<Header>) request.getAttributes().get(HeaderConstants.ATTRIBUTE_HEADERS); |
| if (series != null) { |
| String type = series.getFirstValue(Exchange.CONTENT_TYPE, true); |
| if (type != null) { |
| inMessage.setHeader(Exchange.CONTENT_TYPE, type); |
| } |
| } |
| |
| // copy query string to header |
| String query = request.getResourceRef().getQuery(); |
| if (query != null) { |
| inMessage.setHeader(Exchange.HTTP_QUERY, query); |
| } |
| |
| // copy URI to header |
| inMessage.setHeader(Exchange.HTTP_URI, request.getResourceRef().getIdentifier(true)); |
| |
| // copy HTTP method to header |
| inMessage.setHeader(Exchange.HTTP_METHOD, request.getMethod().toString()); |
| |
| if (!request.isEntityAvailable()) { |
| return; |
| } |
| |
| // only deal with the form if the content type is "application/x-www-form-urlencoded" |
| if (request.getEntity().getMediaType() != null && request.getEntity().getMediaType().equals(MediaType.APPLICATION_WWW_FORM, true)) { |
| Form form = new Form(request.getEntity()); |
| for (String paramName : form.getValuesMap().keySet()) { |
| String[] values = form.getValuesArray(paramName); |
| Object value = null; |
| if (values != null && values.length > 0) { |
| if (values.length == 1) { |
| value = values[0]; |
| } else { |
| value = values; |
| } |
| } |
| if (value == null) { |
| inMessage.setBody(paramName); |
| LOG.debug("Populate exchange from Restlet request body: {}", paramName); |
| } else { |
| if (!headerFilterStrategy.applyFilterToExternalHeaders(paramName, value, exchange)) { |
| inMessage.setHeader(paramName, value); |
| LOG.debug("Populate exchange from Restlet request user header: {} value: {}", paramName, value); |
| } |
| } |
| } |
| } else { |
| InputStream is = request.getEntity().getStream(); |
| Object body = RestletHelper.readResponseBodyFromInputStream(is, exchange); |
| inMessage.setBody(body); |
| } |
| |
| } |
| |
| public void populateRestletRequestFromExchange(Request request, Exchange exchange) { |
| request.setReferrerRef("camel-restlet"); |
| |
| final Method method = request.getMethod(); |
| |
| MediaType mediaType = exchange.getIn().getHeader(Exchange.CONTENT_TYPE, MediaType.class); |
| if (mediaType == null) { |
| mediaType = MediaType.APPLICATION_WWW_FORM; |
| } |
| |
| Form form = null; |
| // Use forms only for PUT, POST and x-www-form-urlencoded |
| if ((Method.PUT == method || Method.POST == method) && MediaType.APPLICATION_WWW_FORM.equals(mediaType, true)) { |
| form = new Form(); |
| // must use string based for forms |
| String body = exchange.getIn().getBody(String.class); |
| if (body != null) { |
| form.add(body, null); |
| } |
| } |
| |
| // login and password are filtered by header filter strategy |
| String login = exchange.getIn().getHeader(RestletConstants.RESTLET_LOGIN, String.class); |
| String password = exchange.getIn().getHeader(RestletConstants.RESTLET_PASSWORD, String.class); |
| |
| if (login != null && password != null) { |
| ChallengeResponse authentication = new ChallengeResponse(ChallengeScheme.HTTP_BASIC, login, password); |
| request.setChallengeResponse(authentication); |
| LOG.debug("Basic HTTP Authentication has been applied"); |
| } |
| |
| for (Map.Entry<String, Object> entry : exchange.getIn().getHeaders().entrySet()) { |
| String key = entry.getKey(); |
| Object value = entry.getValue(); |
| if (!headerFilterStrategy.applyFilterToCamelHeaders(key, value, exchange)) { |
| // Use forms only for PUT, POST and x-www-form-urlencoded |
| if (form != null) { |
| if (key.startsWith("org.restlet.")) { |
| // put the org.restlet headers in attributes |
| request.getAttributes().put(key, value); |
| } else { |
| // put the user stuff in the form |
| if (value instanceof Collection) { |
| for (Object v : (Collection<?>) value) { |
| form.add(key, v.toString()); |
| } |
| } else { |
| form.add(key, value.toString()); |
| } |
| } |
| } else { |
| // For non-form post put all the headers in attributes |
| request.getAttributes().put(key, value); |
| } |
| LOG.debug("Populate Restlet request from exchange header: {} value: {}", key, value); |
| } |
| } |
| |
| if (form != null) { |
| request.setEntity(form.getWebRepresentation()); |
| LOG.debug("Populate Restlet {} request from exchange body as form using media type {}", method, mediaType); |
| } else { |
| // include body if PUT or POST |
| if (request.getMethod() == Method.PUT || request.getMethod() == Method.POST) { |
| Representation body = createRepresentationFromBody(exchange, mediaType); |
| request.setEntity(body); |
| LOG.debug("Populate Restlet {} request from exchange body: {} using media type {}", method, body, mediaType); |
| } else { |
| // no body |
| LOG.debug("Populate Restlet {} request from exchange using media type {}", method, mediaType); |
| request.setEntity(new EmptyRepresentation()); |
| } |
| } |
| |
| // accept |
| String accept = exchange.getIn().getHeader("Accept", String.class); |
| if (accept != null) { |
| MediaType acceptedMediaType = exchange.getContext().getTypeConverter().tryConvertTo(MediaType.class, exchange, accept); |
| if (acceptedMediaType != null) { |
| request.getClientInfo().getAcceptedMediaTypes().add(new Preference<MediaType>(acceptedMediaType)); |
| } |
| } |
| MediaType acceptedMediaType = exchange.getIn().getHeader(Exchange.ACCEPT_CONTENT_TYPE, MediaType.class); |
| if (acceptedMediaType != null) { |
| request.getClientInfo().getAcceptedMediaTypes().add(new Preference<MediaType>(acceptedMediaType)); |
| } |
| |
| } |
| |
| public void populateRestletResponseFromExchange(Exchange exchange, Response response) throws Exception { |
| Message out; |
| if (exchange.isFailed()) { |
| // 500 for internal server error which can be overridden by response code in header |
| response.setStatus(Status.valueOf(500)); |
| Message msg = exchange.hasOut() ? exchange.getOut() : exchange.getIn(); |
| if (msg.isFault()) { |
| out = msg; |
| } else { |
| // print exception as message and stacktrace |
| Exception t = exchange.getException(); |
| StringWriter sw = new StringWriter(); |
| PrintWriter pw = new PrintWriter(sw); |
| t.printStackTrace(pw); |
| response.setEntity(sw.toString(), MediaType.TEXT_PLAIN); |
| return; |
| } |
| } else { |
| out = exchange.hasOut() ? exchange.getOut() : exchange.getIn(); |
| } |
| |
| // get content type |
| MediaType mediaType = out.getHeader(Exchange.CONTENT_TYPE, MediaType.class); |
| if (mediaType == null) { |
| Object body = out.getBody(); |
| mediaType = MediaType.TEXT_PLAIN; |
| if (body instanceof String) { |
| mediaType = MediaType.TEXT_PLAIN; |
| } else if (body instanceof StringSource || body instanceof DOMSource) { |
| mediaType = MediaType.TEXT_XML; |
| } |
| } |
| |
| // get response code |
| Integer responseCode = out.getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class); |
| if (responseCode != null) { |
| response.setStatus(Status.valueOf(responseCode)); |
| } |
| |
| // set response body according to the message body |
| Object body = out.getBody(); |
| if (body instanceof WrappedFile) { |
| // grab body from generic file holder |
| GenericFile<?> gf = (GenericFile<?>) body; |
| body = gf.getBody(); |
| } |
| |
| if (body == null) { |
| // empty response |
| response.setEntity("", MediaType.TEXT_PLAIN); |
| } else if (body instanceof Response) { |
| // its already a restlet response, so dont do anything |
| LOG.debug("Using existing Restlet Response from exchange body: {}", body); |
| } else if (body instanceof Representation) { |
| response.setEntity(out.getBody(Representation.class)); |
| } else if (body instanceof InputStream) { |
| response.setEntity(new InputRepresentation(out.getBody(InputStream.class), mediaType)); |
| } else if (body instanceof File) { |
| response.setEntity(new FileRepresentation(out.getBody(File.class), mediaType)); |
| } else if (body instanceof byte[]) { |
| byte[] bytes = out.getBody(byte[].class); |
| response.setEntity(new ByteArrayRepresentation(bytes, mediaType, bytes.length)); |
| } else { |
| // fallback and use string |
| String text = out.getBody(String.class); |
| response.setEntity(text, mediaType); |
| } |
| LOG.debug("Populate Restlet response from exchange body: {}", body); |
| |
| if (exchange.getProperty(Exchange.CHARSET_NAME) != null) { |
| CharacterSet cs = CharacterSet.valueOf(exchange.getProperty(Exchange.CHARSET_NAME, String.class)); |
| response.getEntity().setCharacterSet(cs); |
| } |
| |
| // set headers at the end, as the entity must be set first |
| // NOTE: setting HTTP headers on restlet is cumbersome and its API is "weird" and has some flaws |
| // so we need to headers two times, and the 2nd time we add the non-internal headers once more |
| Series<Header> series = new Series<Header>(Header.class); |
| for (Map.Entry<String, Object> entry : out.getHeaders().entrySet()) { |
| String key = entry.getKey(); |
| Object value = entry.getValue(); |
| if (!headerFilterStrategy.applyFilterToCamelHeaders(key, value, exchange)) { |
| boolean added = setResponseHeader(exchange, response, key, value); |
| if (!added) { |
| // we only want non internal headers |
| if (!key.startsWith("Camel") && !key.startsWith("org.restlet")) { |
| String text = exchange.getContext().getTypeConverter().tryConvertTo(String.class, exchange, value); |
| if (text != null) { |
| series.add(key, text); |
| } |
| } |
| } |
| } |
| } |
| |
| // set HTTP headers so we return these in the response |
| if (!series.isEmpty()) { |
| response.getAttributes().put(HeaderConstants.ATTRIBUTE_HEADERS, series); |
| } |
| } |
| |
| public void populateExchangeFromRestletResponse(Exchange exchange, Response response) throws Exception { |
| for (Map.Entry<String, Object> entry : response.getAttributes().entrySet()) { |
| String key = entry.getKey(); |
| Object value = entry.getValue(); |
| if (!headerFilterStrategy.applyFilterToExternalHeaders(key, value, exchange)) { |
| exchange.getOut().setHeader(key, value); |
| LOG.debug("Populate exchange from Restlet response header: {} value: {}", key, value); |
| } |
| } |
| |
| // set response code |
| int responseCode = response.getStatus().getCode(); |
| exchange.getOut().setHeader(Exchange.HTTP_RESPONSE_CODE, responseCode); |
| |
| // set restlet response as header so end user have access to it if needed |
| exchange.getOut().setHeader(RestletConstants.RESTLET_RESPONSE, response); |
| |
| if (response.getEntity() != null) { |
| // get content type |
| MediaType mediaType = response.getEntity().getMediaType(); |
| if (mediaType != null) { |
| LOG.debug("Setting the Content-Type to be {}", mediaType.toString()); |
| exchange.getOut().setHeader(Exchange.CONTENT_TYPE, mediaType.toString()); |
| } |
| if (response.getEntity() instanceof StreamRepresentation) { |
| Representation representationDecoded = new DecodeRepresentation(response.getEntity()); |
| exchange.getOut().setBody(representationDecoded.getStream()); |
| } else if (response.getEntity() instanceof Representation) { |
| Representation representationDecoded = new DecodeRepresentation(response.getEntity()); |
| exchange.getOut().setBody(representationDecoded.getText()); |
| } else { |
| // get content text by default |
| String text = response.getEntity().getText(); |
| LOG.debug("Populate exchange from Restlet response: {}", text); |
| exchange.getOut().setBody(text); |
| } |
| } |
| |
| // preserve headers from in by copying any non existing headers |
| // to avoid overriding existing headers with old values |
| MessageHelper.copyHeaders(exchange.getIn(), exchange.getOut(), false); |
| } |
| |
| @SuppressWarnings("unchecked") |
| protected boolean setResponseHeader(Exchange exchange, org.restlet.Response message, String header, Object value) { |
| // there must be a value going forward |
| if (value == null) { |
| return true; |
| } |
| |
| // must put to attributes |
| message.getAttributes().put(header, value); |
| |
| // special for certain headers |
| if (message.getEntity() != null) { |
| // arfg darn restlet you make using your api harder for end users with all this trick just to set those ACL headers |
| if (header.equalsIgnoreCase(HeaderConstants.HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS)) { |
| Boolean bool = exchange.getContext().getTypeConverter().tryConvertTo(Boolean.class, value); |
| if (bool != null) { |
| message.setAccessControlAllowCredentials(bool); |
| } |
| return true; |
| } |
| if (header.equalsIgnoreCase(HeaderConstants.HEADER_ACCESS_CONTROL_ALLOW_HEADERS)) { |
| Set<String> set = convertToStringSet(value, exchange.getContext().getTypeConverter()); |
| message.setAccessControlAllowHeaders(set); |
| return true; |
| } |
| if (header.equalsIgnoreCase(HeaderConstants.HEADER_ACCESS_CONTROL_ALLOW_METHODS)) { |
| Set<Method> set = convertToMethodSet(value, exchange.getContext().getTypeConverter()); |
| message.setAccessControlAllowMethods(set); |
| return true; |
| } |
| if (header.equalsIgnoreCase(HeaderConstants.HEADER_ACCESS_CONTROL_ALLOW_ORIGIN)) { |
| String text = exchange.getContext().getTypeConverter().tryConvertTo(String.class, value); |
| if (text != null) { |
| message.setAccessControlAllowOrigin(text); |
| } |
| return true; |
| } |
| if (header.equalsIgnoreCase(HeaderConstants.HEADER_ACCESS_CONTROL_EXPOSE_HEADERS)) { |
| Set<String> set = convertToStringSet(value, exchange.getContext().getTypeConverter()); |
| message.setAccessControlExposeHeaders(set); |
| return true; |
| } |
| if (header.equalsIgnoreCase(HeaderConstants.HEADER_CACHE_CONTROL)) { |
| if (value instanceof List) { |
| message.setCacheDirectives((List<CacheDirective>) value); |
| } |
| if (value instanceof String) { |
| List<CacheDirective> list = new ArrayList<CacheDirective>(); |
| // set the cache control value directive |
| list.add(new CacheDirective((String) value)); |
| message.setCacheDirectives(list); |
| } |
| return true; |
| } |
| if (header.equalsIgnoreCase(HeaderConstants.HEADER_LOCATION)) { |
| String text = exchange.getContext().getTypeConverter().tryConvertTo(String.class, value); |
| if (text != null) { |
| message.setLocationRef(text); |
| } |
| return true; |
| } |
| if (header.equalsIgnoreCase(HeaderConstants.HEADER_EXPIRES)) { |
| if (value instanceof Calendar) { |
| message.getEntity().setExpirationDate(((Calendar) value).getTime()); |
| } else if (value instanceof Date) { |
| message.getEntity().setExpirationDate((Date) value); |
| } else if (value instanceof String) { |
| SimpleDateFormat format = new SimpleDateFormat(RFC_2822_DATE_PATTERN, Locale.ENGLISH); |
| try { |
| Date date = format.parse((String) value); |
| message.getEntity().setExpirationDate(date); |
| } catch (ParseException e) { |
| LOG.debug("Header {} with value {} cannot be converted as a Date. The value will be ignored.", HeaderConstants.HEADER_EXPIRES, value); |
| } |
| } |
| return true; |
| } |
| |
| if (header.equalsIgnoreCase(HeaderConstants.HEADER_LAST_MODIFIED)) { |
| if (value instanceof Calendar) { |
| message.getEntity().setModificationDate(((Calendar) value).getTime()); |
| } else if (value instanceof Date) { |
| message.getEntity().setModificationDate((Date) value); |
| } else if (value instanceof String) { |
| SimpleDateFormat format = new SimpleDateFormat(RFC_2822_DATE_PATTERN, Locale.ENGLISH); |
| try { |
| Date date = format.parse((String) value); |
| message.getEntity().setModificationDate(date); |
| } catch (ParseException e) { |
| LOG.debug("Header {} with value {} cannot be converted as a Date. The value will be ignored.", HeaderConstants.HEADER_LAST_MODIFIED, value); |
| } |
| } |
| return true; |
| } |
| |
| if (header.equalsIgnoreCase(HeaderConstants.HEADER_CONTENT_LENGTH)) { |
| if (value instanceof Long) { |
| message.getEntity().setSize((Long) value); |
| } else if (value instanceof Integer) { |
| message.getEntity().setSize((Integer) value); |
| } else { |
| Long num = exchange.getContext().getTypeConverter().tryConvertTo(Long.class, value); |
| if (num != null) { |
| message.getEntity().setSize(num); |
| } else { |
| LOG.debug("Header {} with value {} cannot be converted as a Long. The value will be ignored.", HeaderConstants.HEADER_CONTENT_LENGTH, value); |
| } |
| } |
| return true; |
| } |
| |
| if (header.equalsIgnoreCase(HeaderConstants.HEADER_CONTENT_TYPE)) { |
| if (value instanceof MediaType) { |
| message.getEntity().setMediaType((MediaType) value); |
| } else { |
| String type = value.toString(); |
| MediaType media = MediaType.valueOf(type); |
| if (media != null) { |
| message.getEntity().setMediaType(media); |
| } else { |
| LOG.debug("Header {} with value {} cannot be converted as a MediaType. The value will be ignored.", HeaderConstants.HEADER_CONTENT_TYPE, value); |
| } |
| } |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private Set<String> convertToStringSet(Object value, TypeConverter typeConverter) { |
| if (value instanceof Set) { |
| return (Set<String>) value; |
| } |
| Set<String> set = new LinkedHashSet<>(); |
| Iterator it = ObjectHelper.createIterator(value); |
| while (it.hasNext()) { |
| Object next = it.next(); |
| String text = typeConverter.tryConvertTo(String.class, next); |
| if (text != null) { |
| set.add(text.trim()); |
| } |
| } |
| return set; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private Set<Method> convertToMethodSet(Object value, TypeConverter typeConverter) { |
| if (value instanceof Set) { |
| return (Set<Method>) value; |
| } |
| Set<Method> set = new LinkedHashSet<>(); |
| Iterator it = ObjectHelper.createIterator(value); |
| while (it.hasNext()) { |
| Object next = it.next(); |
| String text = typeConverter.tryConvertTo(String.class, next); |
| if (text != null) { |
| Method method = Method.valueOf(text.trim()); // creates new instance only if no matching instance exists |
| set.add(method); |
| } |
| } |
| return set; |
| } |
| |
| protected Representation createRepresentationFromBody(Exchange exchange, MediaType mediaType) { |
| Object body = exchange.getIn().getBody(); |
| if (body == null) { |
| return new EmptyRepresentation(); |
| } |
| |
| // unwrap file |
| if (body instanceof WrappedFile) { |
| body = ((WrappedFile) body).getFile(); |
| } |
| |
| if (body instanceof InputStream) { |
| return new InputRepresentation((InputStream) body, mediaType); |
| } else if (body instanceof File) { |
| return new FileRepresentation((File) body, mediaType); |
| } else if (body instanceof byte[]) { |
| return new ByteArrayRepresentation((byte[]) body, mediaType); |
| } else if (body instanceof String) { |
| return new StringRepresentation((CharSequence) body, mediaType); |
| } |
| |
| // fallback as string |
| body = exchange.getIn().getBody(String.class); |
| if (body != null) { |
| return new StringRepresentation((CharSequence) body, mediaType); |
| } else { |
| return new EmptyRepresentation(); |
| } |
| } |
| |
| public HeaderFilterStrategy getHeaderFilterStrategy() { |
| return headerFilterStrategy; |
| } |
| |
| public void setHeaderFilterStrategy(HeaderFilterStrategy strategy) { |
| headerFilterStrategy = strategy; |
| } |
| } |