1   /**
2    * Copyright (c) 2000-2010 Liferay, Inc. All rights reserved.
3    *
4    * This library is free software; you can redistribute it and/or modify it under
5    * the terms of the GNU Lesser General Public License as published by the Free
6    * Software Foundation; either version 2.1 of the License, or (at your option)
7    * any later version.
8    *
9    * This library is distributed in the hope that it will be useful, but WITHOUT
10   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11   * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
12   * details.
13   */
14  
15  package com.liferay.portal.struts;
16  
17  import com.liferay.portal.kernel.log.Log;
18  import com.liferay.portal.kernel.log.LogFactoryUtil;
19  import com.liferay.portal.kernel.portlet.LiferayPortletURL;
20  import com.liferay.portal.kernel.util.JavaConstants;
21  import com.liferay.portal.kernel.util.StringPool;
22  import com.liferay.portal.kernel.util.Validator;
23  import com.liferay.portal.model.Layout;
24  import com.liferay.portal.model.Portlet;
25  import com.liferay.portal.model.User;
26  import com.liferay.portal.security.auth.PrincipalException;
27  import com.liferay.portal.security.permission.ActionKeys;
28  import com.liferay.portal.security.permission.PermissionChecker;
29  import com.liferay.portal.service.PortletLocalServiceUtil;
30  import com.liferay.portal.service.permission.PortletPermissionUtil;
31  import com.liferay.portal.theme.ThemeDisplay;
32  import com.liferay.portal.util.PortalUtil;
33  import com.liferay.portal.util.PropsValues;
34  import com.liferay.portal.util.WebKeys;
35  import com.liferay.portlet.ActionResponseImpl;
36  import com.liferay.portlet.PortletConfigImpl;
37  import com.liferay.portlet.PortletRequestDispatcherImpl;
38  
39  import java.io.IOException;
40  
41  import java.lang.reflect.Constructor;
42  
43  import javax.portlet.ActionRequest;
44  import javax.portlet.ActionResponse;
45  import javax.portlet.PortletContext;
46  import javax.portlet.PortletException;
47  import javax.portlet.PortletRequest;
48  import javax.portlet.PortletResponse;
49  import javax.portlet.RenderRequest;
50  import javax.portlet.RenderResponse;
51  import javax.portlet.ResourceRequest;
52  import javax.portlet.ResourceResponse;
53  
54  import javax.servlet.ServletException;
55  import javax.servlet.http.HttpServletRequest;
56  import javax.servlet.http.HttpServletResponse;
57  
58  import org.apache.struts.Globals;
59  import org.apache.struts.action.Action;
60  import org.apache.struts.action.ActionErrors;
61  import org.apache.struts.action.ActionForm;
62  import org.apache.struts.action.ActionForward;
63  import org.apache.struts.action.ActionMapping;
64  import org.apache.struts.action.ActionServlet;
65  import org.apache.struts.config.ForwardConfig;
66  import org.apache.struts.config.ModuleConfig;
67  import org.apache.struts.tiles.TilesRequestProcessor;
68  
69  /**
70   * <a href="PortletRequestProcessor.java.html"><b><i>View Source</i></b></a>
71   *
72   * @author Brian Wing Shun Chan
73   */
74  public class PortletRequestProcessor extends TilesRequestProcessor {
75  
76      public static PortletRequestProcessor getInstance(
77              ActionServlet servlet, ModuleConfig moduleConfig)
78          throws ServletException {
79  
80          try {
81              String className = PropsValues.STRUTS_PORTLET_REQUEST_PROCESSOR;
82  
83              Class<?> clazz = Class.forName(className);
84  
85              Constructor<?> constructor = clazz.getConstructor(
86                  new Class[] {
87                      ActionServlet.class, ModuleConfig.class
88                  }
89              );
90  
91              PortletRequestProcessor portletReqProcessor =
92                  (PortletRequestProcessor)constructor.newInstance(
93                      new Object[] {
94                          servlet, moduleConfig
95                      }
96                  );
97  
98              return portletReqProcessor;
99          }
100         catch (Exception e) {
101             _log.error(e);
102 
103             return new PortletRequestProcessor(servlet, moduleConfig);
104         }
105     }
106 
107     public PortletRequestProcessor(
108             ActionServlet actionServlet, ModuleConfig moduleConfig)
109         throws ServletException {
110 
111         init(actionServlet, moduleConfig);
112     }
113 
114     public void process(
115             ActionRequest actionRequest, ActionResponse actionResponse,
116             String path)
117         throws IOException, ServletException {
118 
119         ActionResponseImpl actionResponseImpl =
120             (ActionResponseImpl)actionResponse;
121 
122         HttpServletRequest request = PortalUtil.getHttpServletRequest(
123             actionRequest);
124         HttpServletResponse response = PortalUtil.getHttpServletResponse(
125             actionResponse);
126 
127         ActionMapping mapping = processMapping(request, response, path);
128 
129         if (mapping == null) {
130             return;
131         }
132 
133         if (!processRoles(request, response, mapping, true)) {
134             return;
135         }
136 
137         ActionForm form = processActionForm(request, response, mapping);
138 
139         processPopulate(request, response, form, mapping);
140 
141         if (!processValidateAction(request, response, form, mapping)) {
142             return;
143         }
144 
145         PortletAction action =
146             (PortletAction)processActionCreate(request, response, mapping);
147 
148         if (action == null) {
149             return;
150         }
151 
152         PortletConfigImpl portletConfigImpl =
153             (PortletConfigImpl)actionRequest.getAttribute(
154                 JavaConstants.JAVAX_PORTLET_CONFIG);
155 
156         try {
157             if (action.isCheckMethodOnProcessAction()) {
158                 if (!PortalUtil.isMethodPost(actionRequest)) {
159                     String currentURL = PortalUtil.getCurrentURL(actionRequest);
160 
161                     if (_log.isWarnEnabled()) {
162                         _log.warn(
163                             "This URL can only be invoked using POST: " +
164                                 currentURL);
165                     }
166 
167                     throw new PrincipalException(currentURL);
168                 }
169             }
170 
171             action.processAction(
172                 mapping, form, portletConfigImpl, actionRequest,
173                 actionResponse);
174         }
175         catch (Exception e) {
176             String exceptionId =
177                 WebKeys.PORTLET_STRUTS_EXCEPTION + StringPool.PERIOD +
178                     portletConfigImpl.getPortletId();
179 
180             actionRequest.setAttribute(exceptionId, e);
181         }
182 
183         String forward = (String)actionRequest.getAttribute(
184             PortletAction.getForwardKey(actionRequest));
185 
186         if (forward != null) {
187             String queryString = StringPool.BLANK;
188 
189             int pos = forward.indexOf(StringPool.QUESTION);
190 
191             if (pos != -1) {
192                 queryString = forward.substring(pos + 1, forward.length());
193                 forward = forward.substring(0, pos);
194             }
195 
196             ActionForward actionForward = mapping.findForward(forward);
197 
198             if ((actionForward != null) && (actionForward.getRedirect())) {
199                 String forwardPath = actionForward.getPath();
200 
201                 if (forwardPath.startsWith(StringPool.SLASH)) {
202                     LiferayPortletURL forwardURL =
203                         (LiferayPortletURL)actionResponseImpl.createRenderURL();
204 
205                     forwardURL.setParameter("struts_action", forwardPath);
206 
207                     StrutsURLEncoder.setParameters(forwardURL, queryString);
208 
209                     forwardPath = forwardURL.toString();
210                 }
211 
212                 actionResponse.sendRedirect(forwardPath);
213             }
214         }
215     }
216 
217     public void process(
218             RenderRequest renderRequest, RenderResponse renderResponse)
219         throws IOException, ServletException {
220 
221         HttpServletRequest request = PortalUtil.getHttpServletRequest(
222             renderRequest);
223         HttpServletResponse response = PortalUtil.getHttpServletResponse(
224             renderResponse);
225 
226         process(request, response);
227     }
228 
229     public void process(
230             ResourceRequest resourceRequest, ResourceResponse resourceResponse)
231         throws IOException, ServletException {
232 
233         HttpServletRequest request = PortalUtil.getHttpServletRequest(
234             resourceRequest);
235         HttpServletResponse response = PortalUtil.getHttpServletResponse(
236             resourceResponse);
237 
238         process(request, response);
239     }
240 
241     protected void doForward(
242             String uri, HttpServletRequest request,
243             HttpServletResponse response)
244         throws IOException, ServletException {
245 
246         doInclude(uri, request, response);
247     }
248 
249     protected void doInclude(
250             String uri, HttpServletRequest request,
251             HttpServletResponse response)
252         throws IOException, ServletException {
253 
254         PortletConfigImpl portletConfig =
255             (PortletConfigImpl)request.getAttribute(
256                 JavaConstants.JAVAX_PORTLET_CONFIG);
257 
258         PortletContext portletContext = portletConfig.getPortletContext();
259 
260         PortletRequest portletRequest = (PortletRequest)request.getAttribute(
261             JavaConstants.JAVAX_PORTLET_REQUEST);
262 
263         PortletResponse portletResponse = (PortletResponse)request.getAttribute(
264             JavaConstants.JAVAX_PORTLET_RESPONSE);
265 
266         PortletRequestDispatcherImpl portletRequestDispatcher =
267             (PortletRequestDispatcherImpl)portletContext.getRequestDispatcher(
268                 StrutsUtil.TEXT_HTML_DIR + uri);
269 
270         try {
271             if (portletRequestDispatcher == null) {
272                 _log.error(uri + " is not a valid include");
273             }
274             else {
275                 portletRequestDispatcher.include(
276                     portletRequest, portletResponse, true);
277             }
278         }
279         catch (PortletException pe) {
280             Throwable cause = pe.getCause();
281 
282             if (cause instanceof ServletException) {
283                 throw (ServletException)cause;
284             }
285             else {
286                 _log.error(cause, cause);
287             }
288         }
289     }
290 
291     protected ActionForm processActionForm(
292         HttpServletRequest request, HttpServletResponse response,
293         ActionMapping mapping) {
294 
295         ActionForm form = super.processActionForm(request, response, mapping);
296 
297         if (form instanceof InitializableActionForm) {
298             InitializableActionForm initForm = (InitializableActionForm)form;
299 
300             initForm.init(request, response, mapping);
301         }
302 
303         return form;
304     }
305 
306     protected ActionForward processActionPerform(
307             HttpServletRequest request, HttpServletResponse response,
308             Action action, ActionForm form, ActionMapping mapping)
309         throws IOException, ServletException {
310 
311         PortletConfigImpl portletConfig =
312             (PortletConfigImpl)request.getAttribute(
313                 JavaConstants.JAVAX_PORTLET_CONFIG);
314 
315         String exceptionId =
316             WebKeys.PORTLET_STRUTS_EXCEPTION + StringPool.PERIOD +
317                 portletConfig.getPortletId();
318 
319         Exception e = (Exception)request.getAttribute(exceptionId);
320 
321         if (e != null) {
322             return processException(request, response, e, form, mapping);
323         }
324         else {
325             return super.processActionPerform(
326                 request, response, action, form, mapping);
327         }
328     }
329 
330     protected void processForwardConfig(
331             HttpServletRequest request, HttpServletResponse response,
332             ForwardConfig forward)
333         throws IOException, ServletException {
334 
335         if (forward == null) {
336             _log.error("Forward does not exist");
337         }
338         else {
339 
340             // Don't render a null path. This is useful if you're sending a file
341             // in an exclusive window state.
342 
343             if (forward.getPath().equals(ActionConstants.COMMON_NULL)) {
344                 return;
345             }
346         }
347 
348         super.processForwardConfig(request, response, forward);
349     }
350 
351     public ActionMapping processMapping(
352             HttpServletRequest request, HttpServletResponse response,
353             String path)
354         throws IOException {
355 
356         if (path == null) {
357             return null;
358         }
359 
360         ActionMapping mapping = super.processMapping(request, response, path);
361 
362         if (mapping == null) {
363             String msg = getInternal().getMessage("processInvalid");
364 
365             _log.error("User ID " + request.getRemoteUser());
366             _log.error("Current URL " + PortalUtil.getCurrentURL(request));
367             _log.error("Referer " + request.getHeader("Referer"));
368             _log.error("Remote address " + request.getRemoteAddr());
369 
370             _log.error(msg + " " + path);
371         }
372 
373         return mapping;
374     }
375 
376     protected HttpServletRequest processMultipart(HttpServletRequest request) {
377 
378         // Disable Struts from automatically wrapping a multipart request
379 
380         return request;
381     }
382 
383     protected String processPath(
384         HttpServletRequest request, HttpServletResponse response) {
385 
386         String path = request.getParameter("struts_action");
387 
388         if (_log.isDebugEnabled()) {
389             _log.debug("Getting request parameter path " + path);
390         }
391 
392         if (Validator.isNull(path)) {
393             if (_log.isDebugEnabled()) {
394                 _log.debug("Getting request attribute path " + path);
395             }
396 
397             path = (String)request.getAttribute(WebKeys.PORTLET_STRUTS_ACTION);
398         }
399 
400         if (path == null) {
401             PortletConfigImpl portletConfig =
402                 (PortletConfigImpl)request.getAttribute(
403                     JavaConstants.JAVAX_PORTLET_CONFIG);
404 
405             _log.error(
406                 portletConfig.getPortletName() +
407                     " does not have any paths specified");
408         }
409         else {
410             if (_log.isDebugEnabled()) {
411                 _log.debug("Processing path " + path);
412             }
413         }
414 
415         return path;
416     }
417 
418     protected boolean processRoles(
419             HttpServletRequest request, HttpServletResponse response,
420             ActionMapping mapping)
421         throws IOException, ServletException {
422 
423         return processRoles(request, response, mapping, false);
424     }
425 
426     protected boolean processRoles(
427             HttpServletRequest request, HttpServletResponse response,
428             ActionMapping mapping, boolean action)
429         throws IOException, ServletException {
430 
431         User user = null;
432 
433         try {
434             user = PortalUtil.getUser(request);
435         }
436         catch (Exception e) {
437         }
438 
439         if (user == null) {
440             return true;
441         }
442 
443         String path = mapping.getPath();
444 
445         try {
446             PortletConfigImpl portletConfig =
447                 (PortletConfigImpl)request.getAttribute(
448                     JavaConstants.JAVAX_PORTLET_CONFIG);
449 
450             Portlet portlet = PortletLocalServiceUtil.getPortletById(
451                 user.getCompanyId(), portletConfig.getPortletId());
452 
453             if (portlet == null) {
454                 return false;
455             }
456 
457             String strutsPath = path.substring(
458                 1, path.lastIndexOf(StringPool.SLASH));
459 
460             if (!strutsPath.equals(portlet.getStrutsPath())) {
461                 if (_log.isWarnEnabled()) {
462                     _log.warn(
463                         "The struts path " + strutsPath + " does not belong " +
464                             "to portlet " + portlet.getPortletId() + ". " +
465                                 "Check the definition in liferay-portlet.xml");
466                 }
467 
468                 throw new PrincipalException();
469             }
470             else if (portlet.isActive()) {
471                 ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
472                     WebKeys.THEME_DISPLAY);
473 
474                 Layout layout = themeDisplay.getLayout();
475                 PermissionChecker permissionChecker =
476                     themeDisplay.getPermissionChecker();
477 
478                 if (!PortletPermissionUtil.contains(
479                         permissionChecker, layout.getPlid(), portlet,
480                         ActionKeys.VIEW)) {
481 
482                     throw new PrincipalException();
483                 }
484             }
485             else if (!portlet.isActive()) {
486                 ForwardConfig forwardConfig =
487                     mapping.findForward(_PATH_PORTAL_PORTLET_INACTIVE);
488 
489                 if (!action) {
490                     processForwardConfig(request, response, forwardConfig);
491                 }
492 
493                 return false;
494             }
495         }
496         catch (Exception e) {
497             if (_log.isWarnEnabled()) {
498                 _log.warn(e.getMessage());
499             }
500 
501             ForwardConfig forwardConfig =
502                 mapping.findForward(_PATH_PORTAL_PORTLET_ACCESS_DENIED);
503 
504             if (!action) {
505                 processForwardConfig(request, response, forwardConfig);
506             }
507 
508             return false;
509         }
510 
511         return true;
512     }
513 
514     protected boolean processValidateAction(
515         HttpServletRequest request, HttpServletResponse response,
516         ActionForm form, ActionMapping mapping) {
517 
518         if (form == null) {
519             return true;
520         }
521 
522         if (request.getAttribute(Globals.CANCEL_KEY) != null) {
523             return true;
524         }
525 
526         if (!mapping.getValidate()) {
527             return true;
528         }
529 
530         ActionErrors errors = form.validate(mapping, request);
531 
532         if ((errors == null) || errors.isEmpty()) {
533             return true;
534         }
535 
536         if (form.getMultipartRequestHandler() != null) {
537             form.getMultipartRequestHandler().rollback();
538         }
539 
540         String input = mapping.getInput();
541 
542         if (input == null) {
543             _log.error("Validation failed but no input form is available");
544 
545             return false;
546         }
547 
548         request.setAttribute(Globals.ERROR_KEY, errors);
549 
550         // Struts normally calls internalModuleRelativeForward which breaks
551         // if called inside processAction
552 
553         request.setAttribute(PortletAction.getForwardKey(request), input);
554 
555         return false;
556     }
557 
558     private static final String _PATH_PORTAL_PORTLET_ACCESS_DENIED =
559         "/portal/portlet_access_denied";
560 
561     private static final String _PATH_PORTAL_PORTLET_INACTIVE =
562         "/portal/portlet_inactive";
563 
564     private static Log _log = LogFactoryUtil.getLog(
565         PortletRequestProcessor.class);
566 
567 }