View Javadoc
1   /*
2    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
3    * The software in this package is published under the terms of the CPAL v1.0
4    * license, a copy of which has been included with this distribution in the
5    * LICENSE.txt file.
6    */
7   package org.mule.transport.http.transformers;
8   
9   import org.mule.api.MuleMessage;
10  import org.mule.api.config.MuleProperties;
11  import org.mule.api.lifecycle.InitialisationException;
12  import org.mule.api.transformer.TransformerException;
13  import org.mule.config.MuleManifest;
14  import org.mule.transformer.AbstractMessageTransformer;
15  import org.mule.transformer.types.DataTypeFactory;
16  import org.mule.transport.NullPayload;
17  import org.mule.transport.http.CookieHelper;
18  import org.mule.transport.http.HttpConnector;
19  import org.mule.transport.http.HttpConstants;
20  import org.mule.transport.http.HttpResponse;
21  import org.mule.transport.http.i18n.HttpMessages;
22  import org.mule.util.StringUtils;
23  
24  import java.io.IOException;
25  import java.text.SimpleDateFormat;
26  import java.util.Collection;
27  import java.util.Date;
28  import java.util.Locale;
29  import java.util.Map;
30  
31  import org.apache.commons.httpclient.Cookie;
32  import org.apache.commons.httpclient.Header;
33  import org.apache.commons.httpclient.HttpVersion;
34  
35  /**
36   * Converts a {@link MuleMessage} into an Http response.
37   */
38  public class MuleMessageToHttpResponse extends AbstractMessageTransformer
39  {
40  
41      private String server;
42  
43      public MuleMessageToHttpResponse()
44      {
45          registerSourceType(DataTypeFactory.OBJECT);
46          setReturnDataType(DataTypeFactory.create(HttpResponse.class));
47      }
48  
49      @Override
50      public void initialise() throws InitialisationException
51      {
52          // When running with the source code, Meta information is not set
53          // so product name and version are not available, hence we hard code
54          if (MuleManifest.getProductName() == null)
55          {
56              server = "Mule/SNAPSHOT";
57          }
58          else
59          {
60              server = MuleManifest.getProductName() + "/" + MuleManifest.getProductVersion();
61          }
62      }
63  
64      @Override
65      public Object transformMessage(MuleMessage msg, String outputEncoding) throws TransformerException
66      {
67          Object src = msg.getPayload();
68  
69          // Note this transformer excepts Null as we must always return a result
70          // from the Http
71          // connector if a response transformer is present
72          if (src instanceof NullPayload)
73          {
74              src = StringUtils.EMPTY;
75          }
76  
77          try
78          {
79              HttpResponse response;
80              if (src instanceof HttpResponse)
81              {
82                  response = (HttpResponse) src;
83              }
84              else
85              {
86                  response = createResponse(src, outputEncoding, msg);
87              }
88  
89              // Ensure there's a content type header
90              if (!response.containsHeader(HttpConstants.HEADER_CONTENT_TYPE))
91              {
92                  response.addHeader(new Header(HttpConstants.HEADER_CONTENT_TYPE,
93                          HttpConstants.DEFAULT_CONTENT_TYPE));
94              }
95  
96              // Ensure there's a content length or transfer encoding header
97              if (!response.containsHeader(HttpConstants.HEADER_CONTENT_LENGTH)
98                      && !response.containsHeader(HttpConstants.HEADER_TRANSFER_ENCODING))
99              {
100                 if (response.hasBody())
101                 {
102                     long len = response.getContentLength();
103                     if (len < 0)
104                     {
105                         if (response.getHttpVersion().lessEquals(HttpVersion.HTTP_1_0))
106                         {
107                             // Ensure that we convert the payload to an in memory representation
108                             // so we don't end up with a chunked response
109                             len = msg.getPayloadAsBytes().length;
110 
111                             response.setBody(msg);
112 
113                             Header header = new Header(HttpConstants.HEADER_CONTENT_LENGTH, Long.toString(len));
114                             response.setHeader(header);
115                         }
116                         else
117                         {
118                             Header header = new Header(HttpConstants.HEADER_TRANSFER_ENCODING, "chunked");
119                             response.addHeader(header);
120                         }
121                     }
122                     else
123                     {
124                         Header header = new Header(HttpConstants.HEADER_CONTENT_LENGTH, Long.toString(len));
125                         response.setHeader(header);
126                     }
127                 }
128                 else
129                 {
130                     Header header = new Header(HttpConstants.HEADER_CONTENT_LENGTH, "0");
131                     response.addHeader(header);
132                 }
133             }
134 
135             // See if the the client explicitly handles connection persistence
136             String connHeader = msg.getOutboundProperty(HttpConstants.HEADER_CONNECTION);
137             if (connHeader != null)
138             {
139                 if (connHeader.equalsIgnoreCase("keep-alive"))
140                 {
141                     response.setKeepAlive(true);
142                 }
143                 if (connHeader.equalsIgnoreCase("close"))
144                 {
145                     response.setKeepAlive(false);
146                 }
147             }
148 
149             final String method = msg.getOutboundProperty(HttpConnector.HTTP_METHOD_PROPERTY);
150             if ("HEAD".equalsIgnoreCase(method))
151             {
152                 // this is a head request, we don't want to send the actual content
153                 response.setBody((MuleMessage) null);
154             }
155             return response;
156         }
157         catch (Exception e)
158         {
159             throw new TransformerException(this, e);
160         }
161 
162     }
163 
164     protected HttpResponse createResponse(Object src, String encoding, MuleMessage msg)
165             throws IOException, TransformerException
166     {
167         HttpResponse response = new HttpResponse();
168 
169         Object tmp = msg.getOutboundProperty(HttpConnector.HTTP_STATUS_PROPERTY);
170         int status = HttpConstants.SC_OK;
171 
172         if (tmp != null)
173         {
174             status = Integer.valueOf(tmp.toString());
175         }
176         else if (msg.getExceptionPayload() != null)
177         {
178             status = HttpConstants.SC_INTERNAL_SERVER_ERROR;
179         }
180 
181         String version = msg.getInboundProperty(HttpConnector.HTTP_VERSION_PROPERTY);
182         if (version == null)
183         {
184             version = HttpConstants.HTTP11;
185         }
186         String date = new SimpleDateFormat(HttpConstants.DATE_FORMAT, Locale.US).format(new Date());
187 
188         String contentType = msg.getInboundProperty(HttpConstants.HEADER_CONTENT_TYPE);
189         if (contentType == null)
190         {
191             contentType = msg.getInvocationProperty(HttpConstants.HEADER_CONTENT_TYPE);
192         }
193 
194         // MULE-4047 Don't explicitly set the content-type to a default value here,
195         // otherwise any settings on the servlet/transport will be happily ignored.
196         //if (contentType == null)
197         //{
198         //    contentType = HttpConstants.DEFAULT_CONTENT_TYPE;
199         //
200         //    if (encoding != null)
201         //    {
202         //        contentType += "; charset=" + encoding;
203         //    }
204         //    logger.warn("Content-Type was not set, defaulting to: " + contentType);
205         //}
206 
207         response.setStatusLine(HttpVersion.parse(version), status);
208         if (contentType != null)
209         {
210             response.setHeader(new Header(HttpConstants.HEADER_CONTENT_TYPE, contentType));
211         }
212         response.setHeader(new Header(HttpConstants.HEADER_DATE, date));
213         response.setHeader(new Header(HttpConstants.HEADER_SERVER, server));
214         if (msg.getOutboundProperty(HttpConstants.HEADER_EXPIRES) == null)
215         {
216             response.setHeader(new Header(HttpConstants.HEADER_EXPIRES, date));
217         }
218 
219         String etag = msg.getOutboundProperty(HttpConstants.HEADER_ETAG);
220         if (etag != null)
221         {
222             response.setHeader(new Header(HttpConstants.HEADER_ETAG, etag));
223         }
224         response.setFallbackCharset(encoding);
225 
226         Collection<String> headerNames = HttpConstants.RESPONSE_HEADER_NAMES.values();
227 
228         for (String headerName : headerNames)
229         {
230             if (HttpConstants.HEADER_COOKIE_SET.equals(headerName))
231             {
232                 // TODO This have to be improved. We shouldn't have to look in all
233                 // scopes
234                 Object cookiesObject = msg.getInvocationProperty(headerName);
235                 if (cookiesObject == null)
236                 {
237                     cookiesObject = msg.getOutboundProperty(headerName);
238                 }
239                 if (cookiesObject == null)
240                 {
241                     cookiesObject = msg.getInboundProperty(headerName);
242                 }
243                 Cookie[] arrayOfCookies = CookieHelper.asArrayOfCookies(cookiesObject);
244                 for (Cookie cookie : arrayOfCookies)
245                 {
246                     response.addHeader(new Header(headerName,
247                         CookieHelper.formatCookieForASetCookieHeader(cookie)));
248                 }
249             }
250             else
251             {
252                 String value = msg.getInvocationProperty(headerName);
253                 if (value == null)
254                 {
255                     value = msg.getOutboundProperty(headerName);
256                 }
257                 if (value != null)
258                 {
259                     response.setHeader(new Header(headerName, value));
260                 }
261             }
262         }
263 
264         Map customHeaders = msg.getOutboundProperty(HttpConnector.HTTP_CUSTOM_HEADERS_MAP_PROPERTY);
265         if (customHeaders != null)
266         {
267             throw new TransformerException(HttpMessages.customHeaderMapDeprecated(), this);
268         }
269 
270         //attach the outbound properties to the message
271         for (String headerName : msg.getOutboundPropertyNames())
272         {
273             Object v = msg.getOutboundProperty(headerName);
274             if (v != null)
275             {
276                 if (headerName.startsWith(MuleProperties.PROPERTY_PREFIX))
277                 {
278                     headerName = HttpConstants.CUSTOM_HEADER_PREFIX + headerName;
279                 }
280                 response.setHeader(new Header(headerName, v.toString()));
281             }
282         }
283 
284         if (msg.getCorrelationId() != null)
285         {
286             response.setHeader(new Header(HttpConstants.CUSTOM_HEADER_PREFIX + MuleProperties.MULE_CORRELATION_ID_PROPERTY,
287                     msg.getCorrelationId()));
288             response.setHeader(new Header(HttpConstants.CUSTOM_HEADER_PREFIX
289                     + MuleProperties.MULE_CORRELATION_GROUP_SIZE_PROPERTY,
290                     String.valueOf(msg.getCorrelationGroupSize())));
291             response.setHeader(new Header(HttpConstants.CUSTOM_HEADER_PREFIX
292                     + MuleProperties.MULE_CORRELATION_SEQUENCE_PROPERTY,
293                     String.valueOf(msg.getCorrelationSequence())));
294         }
295         if (msg.getReplyTo() != null)
296         {
297             response.setHeader(new Header(HttpConstants.CUSTOM_HEADER_PREFIX + MuleProperties.MULE_REPLY_TO_PROPERTY,
298                     msg.getReplyTo().toString()));
299         }
300 
301         try
302         {
303             response.setBody(msg);
304         }
305         catch (Exception e)
306         {
307             throw new TransformerException(this, e);
308         }
309 
310         return response;
311     }
312 
313     @Override
314     public boolean isAcceptNull()
315     {
316         return true;
317     }
318 }