View Javadoc

1   /*
2    * Copyright 2001-2004 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.portletbridge.portlet;
17  
18  import java.io.IOException;
19  import java.io.PrintWriter;
20  import java.net.URI;
21  import java.net.URISyntaxException;
22  import java.text.MessageFormat;
23  import java.util.Arrays;
24  import java.util.Enumeration;
25  import java.util.HashSet;
26  import java.util.PropertyResourceBundle;
27  import java.util.ResourceBundle;
28  import java.util.Set;
29  
30  import javax.servlet.ServletException;
31  import javax.servlet.http.HttpServlet;
32  import javax.servlet.http.HttpServletRequest;
33  import javax.servlet.http.HttpServletResponse;
34  import javax.servlet.http.HttpSession;
35  
36  import org.apache.commons.httpclient.Header;
37  import org.apache.commons.httpclient.HttpMethodBase;
38  import org.apache.commons.httpclient.HttpStatus;
39  import org.apache.commons.httpclient.URIException;
40  import org.apache.commons.httpclient.methods.GetMethod;
41  import org.apache.commons.httpclient.methods.InputStreamRequestEntity;
42  import org.apache.commons.httpclient.methods.PostMethod;
43  import org.portletbridge.PortletBridgeException;
44  import org.portletbridge.ResourceException;
45  
46  /***
47   * @author jmccrindle
48   * @author rickard
49   */
50  public class PortletBridgeServlet extends HttpServlet {
51  
52      /***
53       * default serial version id
54       */
55      private static final long serialVersionUID = 7841139248662925798L;
56  
57      public static final ResourceBundle resourceBundle = PropertyResourceBundle
58              .getBundle("org.portletbridge.portlet.PortletBridgePortlet");
59  
60      private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
61              .getLog(PortletBridgeServlet.class);
62  
63      private PortletBridgeService portletBridgeService = new DefaultPortletBridgeService();
64  
65      private HttpClientTemplate httpClientTemplate = new DefaultHttpClientTemplate();
66  
67      private String mementoSessionKey;
68  
69      private BridgeFunctionsFactory bridgeFunctionsFactory;
70  
71      private Set ignoreRequestHeaders;
72      
73      private Set ignorePostToGetRequestHeaders;
74  
75      /***
76       * Initialise the servlet. Will throw a servlet exception if the
77       * proxyBrowserSessionKey is not set.
78       * 
79       * @see javax.servlet.GenericServlet#init()
80       */
81      public void init() throws ServletException {
82  
83          // get proxyBrowserSessionKey
84          mementoSessionKey = this.getServletConfig().getInitParameter(
85                  "mementoSessionKey");
86  
87          log.debug("init(): mementoSessionKey=" + mementoSessionKey);
88  
89          if (mementoSessionKey == null) {
90              throw new ServletException(resourceBundle
91                      .getString("error.mementoSessionKey"));
92          }
93  
94          // TODO: blow up if these aren't set.
95          String cssRegex = this.getServletConfig().getInitParameter("cssRegex");
96          String javascriptRegex = this.getServletConfig().getInitParameter(
97                  "jsRegex");
98  
99          ContentRewriter javascriptRewriter = new RegexContentRewriter(
100                 javascriptRegex);
101         ContentRewriter cssRewriter = new RegexContentRewriter(cssRegex);
102 
103         bridgeFunctionsFactory = new BridgeFunctionsFactory(DefaultIdGenerator
104                 .getInstance(), javascriptRewriter, cssRewriter);
105 
106         // TODO: blow up if these aren't set.
107         ignoreRequestHeaders = new HashSet(Arrays.asList(getInitParameter(
108         "ignoreRequestHeaders").split(",")));
109 
110         ignorePostToGetRequestHeaders = new HashSet(Arrays.asList(getInitParameter(
111         "ignorePostToGetRequestHeaders").split(",")));
112 
113     }
114 
115     /***
116      * url pattern should be: http://host:port/context/servlet/id
117      */
118     protected void doGet(final HttpServletRequest request,
119             final HttpServletResponse response) throws ServletException,
120             IOException {
121         // get the id
122         final String id = portletBridgeService.getIdFromRequestUri(request
123                 .getContextPath(), request.getRequestURI());
124         // look up the data associated with that id from the session
125         HttpSession session = request.getSession();
126         if (session == null) {
127             throw new ServletException(resourceBundle
128                     .getString("error.nosession")
129                     + ", URL=" + request.getRequestURI());
130         }
131         final PortletBridgeMemento memento = (PortletBridgeMemento) session
132                 .getAttribute(mementoSessionKey);
133         if (memento == null) {
134             throw new ServletException(resourceBundle
135                     .getString("error.nomemento")
136                     + ", URL=" + request.getRequestURI());
137         }
138         BridgeRequest bridgeRequest = memento.getBridgeRequest(id);
139         if (bridgeRequest == null) {
140             throw new ServletException(resourceBundle
141                     .getString("error.nobridgerequest")
142                     + ", URL=" + request.getRequestURI());
143         }
144         final PerPortletMemento perPortletMemento = memento
145                 .getPerPortletMemento(bridgeRequest.getPortletId());
146         if (perPortletMemento == null) {
147             throw new ServletException(resourceBundle
148                     .getString("error.noperportletmemento")
149                     + ", URL=" + request.getRequestURI());
150         }
151 
152         // go and fetch the data from the backend as appropriate
153         URI url = bridgeRequest.getUrl();
154 
155         // TODO: if there is a query string, we should create a new bridge request with that query string added
156         // TODO: need to clean up how we create bridge requests (i.e. get rid of the pseudorenderresponse)
157         if (request.getQueryString() != null
158                 && request.getQueryString().trim().length() > 0) {
159             try {
160                 // TODO: may have to change encoding
161             	// if the url already has a query string add an & instead
162                 String urlAsString = url.toString();
163 				url = new URI(urlAsString + ((url.getQuery() != null) ? '&' : '?') + request.getQueryString());
164 	            PseudoRenderResponse renderResponse = createRenderResponse(bridgeRequest);
165 	            bridgeRequest = memento.createBridgeRequest(renderResponse, DefaultIdGenerator.getInstance().nextId(), url);
166             } catch (URISyntaxException e) {
167                 throw new ServletException(e.getMessage() + ", doGet(): URL="
168                         + url + ", id=" + id + ", request URI="
169                         + request.getRequestURI(), e);
170             }
171         }
172 
173         log.debug("doGet(): URL=" + url + ", id=" + id + ", request URI="
174                 + request.getRequestURI());
175 
176         fetch(request, response, bridgeRequest, memento, perPortletMemento, url);
177 
178     }
179 
180     /***
181      * Create a PseudoRenderResponse
182      * 
183      * @param bridgeRequest the bridgeRequest to use
184      * @return a render response
185      */
186 	protected PseudoRenderResponse createRenderResponse(BridgeRequest bridgeRequest) {
187 		PseudoRenderResponse renderResponse = new PseudoRenderResponse(
188 		        bridgeRequest
189 		                .getPortletId(),
190 		        bridgeRequest
191 		                .getPageUrl(),
192 		        bridgeRequest
193 		                .getId());
194 		return renderResponse;
195 	}
196 
197     /***
198      * @param response
199      * @param bridgeRequest
200      * @param perPortletMemento
201      * @param url
202      * @throws ServletException
203      */
204     protected void fetch(final HttpServletRequest request,
205             final HttpServletResponse response,
206             final BridgeRequest bridgeRequest,
207             final PortletBridgeMemento memento,
208             final PerPortletMemento perPortletMemento, final URI url)
209             throws ServletException {
210         try {
211             GetMethod getMethod = new GetMethod(url.toString());
212             // TODO: suspect to send the same request headers after a redirect?
213             copyRequestHeaders(request, getMethod);
214             httpClientTemplate.service(getMethod, perPortletMemento,
215                     new HttpClientCallback() {
216                         public Object doInHttpClient(int statusCode,
217                                 HttpMethodBase method)
218                                 throws ResourceException, Throwable {
219                             if (statusCode == HttpStatus.SC_OK) {
220                                 // if it's text/html then store it and redirect
221                                 // back to the portlet render view (portletUrl)
222                             	org.apache.commons.httpclient.URI effectiveUri = method.getURI();
223                             	BridgeRequest effectiveBridgeRequest = null;
224                             	if(!effectiveUri.toString().equals(url.toString())) {
225                     	            PseudoRenderResponse renderResponse = createRenderResponse(bridgeRequest);
226                     	            effectiveBridgeRequest = memento.createBridgeRequest(renderResponse, DefaultIdGenerator.getInstance().nextId(), new URI(effectiveUri.toString()));
227                             	} else {
228                             		effectiveBridgeRequest = bridgeRequest;
229                             	}
230                                 Header responseHeader = method
231                                         .getResponseHeader("Content-Type");
232                                 if (responseHeader != null
233                                         && responseHeader.getValue()
234                                                 .startsWith("text/html")) {
235                                     String content = ResourceUtil.getString(
236                                             method.getResponseBodyAsStream(),
237                                             method.getResponseCharSet());
238                                     // TODO: think about cleaning this up if we
239                                     // don't get back to the render
240                                     perPortletMemento.enqueueContent(
241                                     		effectiveBridgeRequest.getId(),
242                                             new PortletBridgeContent(url,
243                                                     "get", content));
244                                     // redirect
245                                     // TODO: worry about this... adding the id
246                                     // at the end
247                                     response.sendRedirect(effectiveBridgeRequest
248                                             .getPageUrl());
249                                 } else if (responseHeader != null
250                                         && responseHeader.getValue()
251                                                 .startsWith("text/javascript")) {
252                                     // rewrite external javascript
253                                     String content = ResourceUtil.getString(
254                                             method.getResponseBodyAsStream(),
255                                             method.getResponseCharSet());
256                                     BridgeFunctions bridge = bridgeFunctionsFactory
257                                             .createBridgeFunctions(
258                                                     memento,
259                                                     perPortletMemento,
260                                                     getServletName(),
261                                                     url,
262                                                     new PseudoRenderRequest(request.getContextPath()),
263                                                     createRenderResponse(effectiveBridgeRequest));
264                                     response.setContentType("text/javascript");
265                                     PrintWriter writer = response.getWriter();
266                                     writer.write(bridge.script(null, content));
267                                     writer.flush();
268                                 } else if (responseHeader != null
269                                         && responseHeader.getValue()
270                                                 .startsWith("text/css")) {
271                                     // rewrite external css
272                                     String content = ResourceUtil.getString(
273                                             method.getResponseBodyAsStream(),
274                                             method.getResponseCharSet());
275                                     BridgeFunctions bridge = bridgeFunctionsFactory
276                                             .createBridgeFunctions(
277                                                     memento,
278                                                     perPortletMemento,
279                                                     getServletName(),
280                                                     url,
281                                                     new PseudoRenderRequest(request.getContextPath()),
282                                                     createRenderResponse(effectiveBridgeRequest));
283                                     response.setContentType("text/css");
284                                     PrintWriter writer = response.getWriter();
285                                     writer.write(bridge.style(null, content));
286                                     writer.flush();
287                                 } else {
288                                     // if it's anything else then stream it
289                                     // back... consider stylesheets and
290                                     // javascript
291                                     // TODO: javascript and css rewriting
292                                     Header header = method
293                                             .getResponseHeader("Content-Type");
294                                     response
295                                             .setContentType(((null == header
296                                                     .getName() ? "" : header
297                                                     .getName())
298                                                     + ": " + (null == header
299                                                     .getValue() ? "" : header
300                                                     .getValue())));
301 
302                                     log.trace("fetch(): returning URL=" + url
303                                             + ", as stream, content type="
304                                             + header);
305                                     ResourceUtil.copy(method
306                                             .getResponseBodyAsStream(),
307                                             response.getOutputStream(), 4096);
308                                 }
309                             } else {
310                                 // if there is a problem with the status code
311                                 // then return that error back
312                                 response.sendError(statusCode);
313                             }
314                             return null;
315                         }
316                     });
317         } catch (ResourceException resourceException) {
318             String format = MessageFormat.format(resourceBundle
319                     .getString(resourceException.getMessage()),
320                     resourceException.getArgs());
321             throw new ServletException(format, resourceException);
322         }
323     }
324 
325     protected void doPost(final HttpServletRequest request,
326             final HttpServletResponse response) throws ServletException,
327             IOException {
328         // get the id
329         final String id = portletBridgeService.getIdFromRequestUri(request
330                 .getContextPath(), request.getRequestURI());
331         // look up the data associated with that id from the session
332         HttpSession session = request.getSession();
333         if (session == null) {
334             throw new ServletException(resourceBundle
335                     .getString("error.nosession"));
336         }
337         final PortletBridgeMemento memento = (PortletBridgeMemento) session
338                 .getAttribute(mementoSessionKey);
339         if (memento == null) {
340             throw new ServletException(resourceBundle
341                     .getString("error.nomemento"));
342         }
343         final BridgeRequest bridgeRequest = memento.getBridgeRequest(id);
344         if (bridgeRequest == null) {
345             throw new ServletException(resourceBundle
346                     .getString("error.nobridgerequest"));
347         }
348         final PerPortletMemento perPortletMemento = memento
349                 .getPerPortletMemento(bridgeRequest.getPortletId());
350         if (perPortletMemento == null) {
351             throw new ServletException(resourceBundle
352                     .getString("error.noperportletmemento"));
353         }
354 
355         // go and fetch the data from the backend as appropriate
356         final URI url = bridgeRequest.getUrl();
357 
358         log.debug("doPost(): URL=" + url);
359 
360         try {
361             PostMethod postMethod = new PostMethod(url.toString());
362             copyRequestHeaders(request, postMethod);
363             postMethod.setRequestEntity(new InputStreamRequestEntity(request
364                     .getInputStream()));
365             httpClientTemplate.service(postMethod, perPortletMemento,
366                     new HttpClientCallback() {
367                         public Object doInHttpClient(int statusCode,
368                                 HttpMethodBase method)
369                                 throws ResourceException, Throwable {
370                             if (statusCode == HttpStatus.SC_OK) {
371                                 // if it's text/html then store it and redirect
372                                 // back to the portlet render view (portletUrl)
373                                 Header responseHeader = method
374                                         .getResponseHeader("Content-Type");
375                                 if (responseHeader != null
376                                         && responseHeader.getValue()
377                                                 .startsWith("text/html")) {
378                                     String content = ResourceUtil.getString(
379                                             method.getResponseBodyAsStream(),
380                                             method.getResponseCharSet());
381                                     // TODO: think about cleaning this up if we
382                                     // don't get back to the render
383                                     perPortletMemento.enqueueContent(
384                                             bridgeRequest.getId(),
385                                             new PortletBridgeContent(url,
386                                                     "post", content));
387                                     // redirect
388                                     // TODO: worry about this... adding the id
389                                     // at the end
390 
391                                     log
392                                             .debug("doPost(): doing response.sendRedirect to URL="
393                                                     + bridgeRequest
394                                                             .getPageUrl());
395 
396                                     response.sendRedirect(bridgeRequest
397                                             .getPageUrl());
398                                 } else {
399                                     // if it's anything else then stream it
400                                     // back... consider stylesheets and
401                                     // javascript
402                                     // TODO: javascript and css rewriting
403                                     response.setContentType(method
404                                             .getResponseHeader("Content-Type")
405                                             .toExternalForm());
406                                     ResourceUtil.copy(method
407                                             .getResponseBodyAsStream(),
408                                             response.getOutputStream(), 4096);
409                                 }
410                             } else if (statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
411                                 Header locationHeader = method.getResponseHeader("Location");
412                                 if(locationHeader != null) {
413                                     URI redirectUrl = new URI(locationHeader.getValue().trim());
414                                     log.debug("redirecting to [" + redirectUrl + "]");
415                                     PseudoRenderResponse renderResponse = createRenderResponse(bridgeRequest);
416                                     BridgeRequest updatedBridgeRequest = memento.createBridgeRequest(renderResponse, DefaultIdGenerator.getInstance().nextId(), redirectUrl);
417                                     fetch(request, response, updatedBridgeRequest,
418                                         memento, perPortletMemento, redirectUrl);
419 
420                                 } else {
421                                 	throw new PortletBridgeException("error.missingLocation");
422                                 }
423                             } else {
424                                 // if there is a problem with the status code
425                                 // then return that error back
426                                 response.sendError(statusCode);
427                             }
428                             return null;
429                         }
430                     });
431         } catch (ResourceException resourceException) {
432             String format = MessageFormat.format(resourceBundle
433                     .getString(resourceException.getMessage()),
434                     resourceException.getArgs());
435             throw new ServletException(format, resourceException);
436         }
437     }
438 
439     public void setPortletBridgeService(
440             PortletBridgeService portletBridgeService) {
441         this.portletBridgeService = portletBridgeService;
442     }
443 
444     public void setHttpClientTemplate(HttpClientTemplate httpClientTemplate) {
445         this.httpClientTemplate = httpClientTemplate;
446     }
447 
448     protected void copyRequestHeaders(HttpServletRequest request,
449             HttpMethodBase method) {
450 
451         Enumeration properties = request.getHeaderNames();
452         while (properties.hasMoreElements()) {
453             String propertyName = (String) properties.nextElement();
454             String propertyNameToLower = propertyName.toLowerCase();
455 			if (!ignoreRequestHeaders.contains(propertyNameToLower)
456             		&& !(method instanceof GetMethod && ignorePostToGetRequestHeaders.contains(propertyNameToLower))) {
457                 Enumeration values = request.getHeaders(propertyName);
458                 while (values.hasMoreElements()) {
459                     String property = (String) values.nextElement();
460                     // System.out.println(propertyName + ":" + property);
461                     method.setRequestHeader(propertyName, property);
462                 }
463             }
464         }
465 
466         // TODO consider what happens if the host is different after a redirect...
467         // Conditional cookie transfer
468         try {
469             if (method.getURI().getHost().equals(request.getHeader("host"))) {
470                 String cookie = request.getHeader("cookie");
471                 if (cookie != null)
472                     method.setRequestHeader("cookie", cookie);
473             }
474         } catch (URIException e) {
475             log.warn(e, e);
476         }
477 
478     }
479 
480 }