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