com.celamanzi.liferay.portlets.rails286.Rails286Portlet.java Source code

Java tutorial

Introduction

Here is the source code for com.celamanzi.liferay.portlets.rails286.Rails286Portlet.java

Source

/**
 * Copyright (c) 2008,2009 Mikael Lammentausta
 *               2010 Mikael Lammentausta, Tulio Ornelas dos Santos
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package com.celamanzi.liferay.portlets.rails286;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletConfig;
import javax.portlet.PortletException;
import javax.portlet.PortletMode;
import javax.portlet.PortletPreferences;
import javax.portlet.PortletRequest;
import javax.portlet.PortletResponse;
import javax.portlet.PortletSession;
import javax.portlet.ReadOnlyException;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.ResourceRequest;
import javax.portlet.ResourceResponse;
import javax.portlet.ValidatorException;

import org.apache.commons.fileupload.portlet.PortletFileUpload;
import org.apache.commons.httpclient.Cookie;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.htmlparser.util.ParserException;

import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.kernel.upload.UploadPortletRequest;
import com.liferay.portal.kernel.util.FileUtil;
import com.liferay.portal.util.PortalUtil;
import com.liferay.util.servlet.PortletResponseUtil;
/*
1  ThemeDisplay themeDisplay= (ThemeDisplay)renderRequest.getAttribute(WebKeys.THEME_DISPLAY);
2  PortletDisplay portletDisplay= themeDisplay.getPortletDisplay();
3  String portletId= portletDisplay.getId();
*/

/**
 * Presents a web application in a JSR286 portlet.
 * Rails is the only tested framework, but the same principles apply to all HTTP servers.
 *
 * Supports the GET and POST methods (also unofficially PUT).
 * Session cookies are supported.
 * TODO: explain cookie system and security
 *
 * XHRs do not convey through the portlet. They should be directed to the Rails server in the HTML.
 * This presents a problem that the returned HTML is not processed anymore, and cannot have any PortletURLs.
 * Maybe XHR could be sent to the portal, which would trigger XMLPortletRequest,
 * then the returned HTML could be passed through portlet transformation.
 *
 * http://www.subbu.org/blog/2007/08/update-on-jsr-286-and-ajax 
 *
 * JSR-286 introduces a new URL type ResourceURL that can be used to invoke 
 * the serveResource method of a portlet.
 * Since serveResource mimics the service method of the servlet API,
 * portlets can use XMLHttpRequest with a ResourceURL,
 * generate some textual or XML response, and process it in the browser.
 *
 * http://ibmdw.blogspot.com/2009/04/jsr-268-portlets-standard-portlet-and.html
 * http://www.ibm.com/developerworks/websphere/library/techarticles/0803_hepper/0803_hepper.html
 *
 * Implemented @since 0.10.0
 *
 * @author Mikael Lammentausta
 */
public class Rails286Portlet extends GenericPortlet
        implements PreferencesAttributes, PublicRenderParameterAttributes {

    /** 
     * Class variables and the logger.
     */
    private final Log log = LogFactory.getLog(getClass().getName());

    private String sessionKey = null;
    private String sessionSecret = null;
    protected int responseStatusCode = -1;

    /** 
     * Base + Request URLs are set in the RenderFilter 
     * and are read from the PortletSession.
     */
    private URL railsBaseUrl = null;
    private String servlet = null;
    private String railsRoute = null;

    private OnlineClient client = null;

    /** 
     * Portlet initialization at portal startup.
     */
    @Override
    public void init(PortletConfig config) throws PortletException {
        log.info("Initializing Rails-portlet " + config.getPortletName() + " (version "
                + PortletVersion.PORTLET_VERSION + ")");

        // store session secret to private instance variable
        sessionKey = config.getInitParameter("session_key");
        sessionSecret = config.getInitParameter("secret");

        if (sessionSecret == null) {
            log.info("Session security not established");
        } else {
            log.info("Session is secured by a shared secret");
            log.debug("Session key: " + sessionKey);
        }

        super.init(config);
    }

    /**
     * Main method, render(). Other portlet modes are not supported.
     *
     * Downloads the Rails HTML, runs the HTML processor and
     * inserts it into RenderResponse.
     * 
     * @since 0.10.0
     *  It uses the callRails method
     *  
     * @since 0.8.2
     *    Split to subfunctions, cookie system...
     *
     * @since 0.6.1
     *  Changed to use Liferay 5.2.0 API.
     *
     */
    public void render(RenderRequest request, RenderResponse response) throws PortletException, IOException {

        loggerInfo(request, response);

        byte[] railsBytes = callRails(request, response);
        String outputHTML = processResponseBody(response, new String(railsBytes));

        log.debug("Response status code: " + responseStatusCode);

        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println(outputHTML);
    }

    /**
     * Used to download files and serve Ajax requests.
     */
    @Override
    public void serveResource(ResourceRequest request, ResourceResponse response)
            throws PortletException, IOException {
        log.debug("serveResource has been called");
        super.serveResource(request, response);

        // Retrieve parameters
        Map<String, String[]> params = Rails286PortletFunctions.mapRequestParameters(request);
        NameValuePair[] parametersBody = Rails286PortletFunctions.paramsToNameValuePairs(params);

        String actionMethod = (params.containsKey("originalActionMethod") ? params.remove("originalActionMethod")[0]
                : "post");

        // Adding the parameters to request for callRails
        request.getPortletSession(true).setAttribute("requestMethod", actionMethod);
        request.setAttribute("parametersBody", parametersBody);

        /* is this Ajax?
        request headers are not accessible..
        */
        //__headers['X_REQUESTED_WITH'] = 'XMLHttpRequest'
        //__headers['ACCEPT'] = 'text/javascript, text/html, application/xml, text/xml, */*'
        //__content_type = 'application/x-www-form-urlencoded; charset=UTF-8'
        /* .. so this is the best I could find..
        */
        if (request.getResponseContentType().equals("text/html")) {
            // .. this is Ajax.
            log.debug("Ajax (text/html)");
            request.setAttribute("X_REQUESTED_WITH", "XMLHttpRequest");
            byte[] railsBytes = callRails(request, response);
            try {
                PortletResponseUtil.write(response, railsBytes);
            } catch (IOException e) {
                log.error("Exception: " + e.getMessage());
            }
            return;
        }

        // else .. download
        byte[] railsBytes = callRails(request, response);

        /**
        When we write the bytes directly to the output of the portlet,
        we obtained corrupted files (pdf, png, zip, tar).
        - tulios
        */
        String filename = getFilename();
        if (filename != null && !filename.equals("")) {
            File file = null;
            try {
                file = new File(Rails286PortletFunctions.getTempPath() + "/" + filename);
                log.debug("downloading contents to " + file.toString());
                FileOutputStream fos = new FileOutputStream(file);
                fos.write(railsBytes);
                fos.flush();
                fos.close();

                // This "if" is to avoid this call when in a test environment, because liferay test environment is too
                // heavy and dirty to be implemented =[ (sorry...)
                if (FileUtil.getFile() != null) {
                    PortletResponseUtil.sendFile(request, response, filename, new FileInputStream(file));
                    // only delete the temporary file in live environment, not during test
                    if (!file.delete()) {
                        log.error("Failed to delete temporary file " + file.getAbsolutePath());
                    }
                }
            } catch (IOException e) {
                log.error("Exception: " + e.getMessage());
                if (file != null) {
                    log.error("File path: " + file.getAbsolutePath());
                }
            }
        }
    }

    /** 
     * Handles POST requests.
     */
    public void processAction(ActionRequest request, ActionResponse response) throws PortletException, IOException {
        log.debug("ActionRequest from the browser");
        log.debug("Request character encoding: " + request.getCharacterEncoding());
        log.debug("http.protocol.content-charset: " + System.getProperty("http.protocol.content-charset"));
        // request may be UTF-8, but the form data may be in different
        // encoding, set by <form accept-charset="..">

        String actionUrl = null;
        String actionMethod = null;

        // In case of a multipart request, retrieve the files
        if (PortletFileUpload.isMultipartContent(request)) {
            log.debug("Multipart request");
            retrieveFiles(request);
        }

        // retrieve parameters
        Map<String, String[]> params = Rails286PortletFunctions.mapRequestParameters(request);

        /** 
         * Process an action from the web page.
         * This can be a classic HTML form or a JavaScript-generator form POST.
         */
        // default to POST for actions
        actionMethod = (params.containsKey("originalActionMethod") ? params.remove("originalActionMethod")[0]
                : "post");
        // set the action URL
        if (params.containsKey("originalActionUrl")) {
            actionUrl = params.remove("originalActionUrl")[0];
            log.debug("Received a form action: " + actionUrl);

            // formulate NameValuePair[]
            NameValuePair[] parametersBody = Rails286PortletFunctions.paramsToNameValuePairs(params);
            debugParams(parametersBody);

            // Retrieving preference parameters and store them on liferay
            if (request.getPortletMode().equals(PortletMode.EDIT)) {
                savePreferences(request, parametersBody);
            }

            // Sending public render parameters
            publishRenderParameters(response, parametersBody);

            // save the attributes to the RenderRequest (set custom parameters now..)
            request.setAttribute("parametersBody", parametersBody);

        } else {
            log.warn("No action URL given! Halting action.");
            actionUrl = (String) request.getParameter("railsRoute");
            actionMethod = "get";
        }

        request.setAttribute("requestMethod", actionMethod);
        request.setAttribute("railsRoute", actionUrl);
    }

    public Map<String, String[]> getContainerRuntimeOptions() {
        return this.getPortletConfig().getContainerRuntimeOptions();
    }

    public URL getRailsBaseUrl() {
        return railsBaseUrl;
    }

    public void setRailsBaseUrl(URL railsBaseUrl) {
        this.railsBaseUrl = railsBaseUrl;
    }

    public void setRailsBaseUrl(PortletSession session) {
        setRailsBaseUrl((java.net.URL) session.getAttribute("railsBaseUrl"));
    }

    public String getServlet() {
        return servlet;
    }

    public void setServlet(String servlet) {
        this.servlet = servlet;
    }

    public void setServlet(PortletSession session) {
        setServlet((String) session.getAttribute("servlet"));
    }

    public String getRailsRoute() {
        return railsRoute;
    }

    public void setRailsRoute(String railsRoute) {
        this.railsRoute = railsRoute;
    }

    public void setRailsRoute(PortletSession session) {
        setRailsRoute((String) session.getAttribute("railsRoute"));
    }

    public OnlineClient getClient() {
        return client;
    }

    public void setClient(OnlineClient client) {
        this.client = client;
    }

    /**
     *   Cookie with session secret.
     */
    protected Cookie secretCookie(PortletSession session) {
        // no. this is not the best way to handle this.
        URL base = (java.net.URL) session.getAttribute("railsBaseUrl");
        String host = base.getHost();
        return new Cookie(host, "session_secret", sessionSecret, // instance variable
                "/", null, false);
    }

    /**
     * Cookie with UID.
     */
    protected Cookie uidCookie(PortletSession session) {
        // no. this is not the best way to handle this.
        URL base = (java.net.URL) session.getAttribute("railsBaseUrl");
        String host = base.getHost();
        return new Cookie(host, "Liferay_UID", (String) session.getAttribute("uid"), "/", null, false);
    }

    /**
     * Cookie with GID.
     */
    protected Cookie gidCookie(PortletSession session, PortletRequest request) {
        URL base = (java.net.URL) session.getAttribute("railsBaseUrl");
        String host = base.getHost();
        Cookie cookie = null;
        try {
            cookie = new Cookie(host, "Liferay_GID", PortalUtil.getScopeGroupId(request) + "", "/", null, false);
            /*
             exception com.liferay.portal.kernel.exception.PortalException is never thrown in body of corresponding try statement
             exception com.liferay.portal.kernel.exception.SystemException is never thrown in body of corresponding try statement
            } catch (PortalException e) {
               log.error("gitCookie: " + e.getMessage());
            } catch (SystemException e) {
               log.error("gitCookie: " + e.getMessage());
            }
            */
        } catch (Exception e) {
            log.error("gidCookie: " + e.getMessage());
        }
        return cookie;
    }

    protected Cookie resourceUrlCookie(PortletSession session, String value) {
        URL base = (java.net.URL) session.getAttribute("railsBaseUrl");
        String host = base.getHost();

        // Adding the resource url generated by liferay to achieve the serveResource method
        return new Cookie(host, "Liferay_resourceUrl", value, "/", null, false);
    }

    /**
     * Creates the preference cookie (Liferay_preferences) with the values of request.getPreferences().
     * It will encode the generated value (key=value;key=value;...) with UTF-8.
     * 
     * @see Rails286Porltlet#preferencesToString
     * 
     * @param session - {@link PortletSession}
     * @param request - {@link PortletRequest}
     * @return {@link Cookie}
     */
    private Cookie preferencesCookie(PortletSession session, PortletRequest request) {
        URL base = (java.net.URL) session.getAttribute("railsBaseUrl");
        String host = base.getHost();
        String value = preferencesToString(request.getPreferences());
        try {
            // Cookies do not accept space, brackets, parentheses, equals signs, commas, and a lot of chars, so the only
            // solution was encoding the content. UTF-8 was suggested as the better character encoding for this, 
            // but if something goes wrong we will send the raw value.
            value = URLEncoder.encode(value, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            log.error("preferencesCookie: " + e.getMessage());
        }

        return new Cookie(host, "Liferay_preferences", value, "/", null, false);
    }

    /**
     * Creates the namespace cookie (Portlet_namespace) with the value of response.getNamespace().
     * It will encode the generated value with UTF-8.
     * 
     * @param session - {@link PortletSession}
     * @param request - {@link PortletRequest}
     * @return {@link Cookie}
     */
    private Cookie namespaceCookie(PortletSession session, PortletResponse response) {
        URL base = (java.net.URL) session.getAttribute("railsBaseUrl");
        String host = base.getHost();
        String value = response.getNamespace();
        try {
            // Cookies do not accept space, brackets, parentheses, equals signs, commas, and a lot of chars, so the only
            // solution was encoding the content. UTF-8 was suggested as the better character encoding for this, 
            // but if something goes wrong we will send the raw value.
            value = URLEncoder.encode(value, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            log.error("namespaceCookie: " + e.getMessage());
        }
        return new Cookie(host, "Portlet_namespace", value, "/", null, false);
    }

    private byte[] callRails(PortletRequest request, PortletResponse response) throws PortletException {

        /**
         * Session storage.
         *
         * The current host, servlet and route are stored into the session.
         * The session is manipulated in the Render Filter.
         *
         * APPLICATION_SCOPE stores data across all user portlets in the same session.
         * PORTLET_SCOPE stores information only accessible to the user's RailsPortlet instance.
         *
         * @see javax.portlet.PortletSession
         * http://www.bluesunrise.com/portlet-api/javax/portlet/PortletSession.html
         *
         */
        PortletSession session = request.getPortletSession(true);

        /**
         * Host and route.
         *
         * Gets the base URL and the route from the session.
         */
        setRailsBaseUrl(session);
        setRailsRoute(session);
        setServlet(session);

        String railsHost = getRailsBaseUrl().getHost();

        byte[] railsBytes = new byte[] {};

        // check the server and route
        if (railsHost == null) {
            throw new PortletException("The host is undefined!");
        }
        if (getRailsRoute() == null) {
            log.warn("The requested route is undefined");
            setRailsRoute("/");
        }

        // are we in EDIT mode?
        if (request.getPortletMode().equals(PortletMode.EDIT)) {
            log.debug("Edit mode, defining preferences URL");
            definePreferencesURL(session);
            log.debug("RailsRoute: " + getRailsRoute());
        }

        // TODO: if the server is unreachable
        //if () {
        //  throw new PortletException("The server " + railsHost + " was unreachable.");
        //}

        try {
            java.net.URL requestUrl = getRequestURL();
            Map<String, Cookie> cookies = getCookies(session, request, response);

            /*
            // Retrieve servlet cookies.
            // Author Reinaldo Silva
            // Needs tests!
            Cookie[] servletCookies = OnlineUtils.getRequestCookies(request, requestUrl);
            for (int i = 0; i < servletCookies.length; i++) {
               if (!cookies.containsKey(servletCookies[i].getName()))
                  cookies.put((String)servletCookies[i].getName(), (Cookie)servletCookies[i]);
            }
            */

            String requestMethod = getRequestMethod(session);
            URL httpReferer = getHttpReferer(session);
            java.util.Locale locale = request.getLocale();

            String x_request = (String) request.getAttribute("X_REQUESTED_WITH");
            boolean ajax = false;
            if ((x_request != null) && x_request.equals("XMLHttpRequest"))
                ajax = true;

            /**
             * Execute the request
             */
            setClient(new OnlineClient(requestUrl, cookies, httpReferer, locale, ajax));

            /**
             * GET
             */
            if (requestMethod.equals("get")) {
                railsBytes = executeGet(session, getClient());
            }

            /**
             * POST, PUT (PUT is sent as POST)
             */
            else if (requestMethod.equals("post") || requestMethod.equals("put")) {
                railsBytes = executePost(request, httpReferer, session, getClient());
            }

            // OPTIONS, HEAD, DELETE, TRACE, CONNECT
            else {
                throw new PortletException("Unsupported HTTP method: " + requestMethod);
            }

        } catch (HttpException e) {
            log.error("callRails: HttpException: " + e.getMessage() + "\n" + new String(railsBytes));
            railsBytes = e.getMessage().getBytes();
        } catch (IOException e) {
            log.error("callRails: IOException: " + e.getMessage() + "\n" + new String(railsBytes));
            railsBytes = e.getMessage().getBytes();
        }

        // set the response status code (for tests)
        responseStatusCode = client.getStatusCode();
        return railsBytes;
    }

    /**
     * Gets parameters with {@link PreferencesAttributes}#PREFERENCES_SUFIX from the request and saves.
     * @param request
     * @param parametersBody
     * @throws IOException
     */
    private void savePreferences(PortletRequest request, NameValuePair[] parametersBody) throws IOException {
        PortletPreferences preferences = request.getPreferences();

        for (NameValuePair nameValue : parametersBody) {
            if (nameValue.getName().endsWith(PREFERENCES_SUFIX)) {
                try {
                    preferences.setValue(nameValue.getName(), nameValue.getValue());
                } catch (ReadOnlyException e) {
                    log.error("savePreferences: " + e.getMessage());
                }
            }
        }

        try {
            preferences.store();
        } catch (ValidatorException e) {
            log.error("savePreferences: " + e.getMessage());
        }
    }

    /**
     * Publish public parameters to all portlets
     * @param response - {@link ActionResponse}
     * @param parametersBody - {@link NameValuePair[]}
     */
    private void publishRenderParameters(ActionResponse response, NameValuePair[] parametersBody) {
        for (NameValuePair param : parametersBody) {
            if (param.getName().endsWith(PUBLIC_RENDER_PARAMETER_SUFIX)) {
                String name = param.getName().replaceAll(PUBLIC_RENDER_PARAMETER_SUFIX, "");
                response.setRenderParameter(name, param.getValue());
            }
        }
    }

    /**
     * Changes the railsRoute attribute to ensure that the preference method will be called.
     */
    private void definePreferencesURL(PortletSession session) {
        setRailsRoute((String) session.getAttribute(PREFERENCES_ROUTE));
    }

    private java.net.URL getRequestURL() {
        return Rails286PortletFunctions.getRequestURL(getRailsBaseUrl(), getServlet(), getRailsRoute());
    }

    /**
     * Retrieve the filename of contentDisposition. Return empty string ("")
     * if the matcher didn't find the group (filename=\"([^\"]+)\").
     * 
     * @param contentDisposition - {@link String}
     * @return {@link String}
     */
    private String getFilename() {
        String contentDisposition = getClient().getContentDisposition();

        if (contentDisposition == null) {
            return null;
        }

        Pattern p = Pattern.compile("filename=\"([^\"]+)\"");
        Matcher matcher = p.matcher(contentDisposition);
        return matcher.find() ? matcher.group(1) : "";
    }

    /** 
     * Executes a POST request.
     */
    @SuppressWarnings("unchecked")
    private byte[] executePost(PortletRequest request, URL httpReferer, PortletSession session, OnlineClient client)
            throws HttpException, IOException {
        // retrieve POST parameters        
        NameValuePair[] parametersBody = (NameValuePair[]) request.getAttribute("parametersBody");

        if (parametersBody == null) {
            log.warn("Empty parametersBody in the request, no POST data?");
        } else if (log.isDebugEnabled()) {
            debugParams(parametersBody);
        }

        // retrieve files
        Map<String, Object[]> files = (Map<String, Object[]>) request.getAttribute("files");

        // POST the parametersBody
        // OnlineClient handles cases where POST redirects.
        byte[] railsBytes = null;
        try {
            railsBytes = client.post(parametersBody, files);
        } catch (RailsAppException e) {
            log.error("executePost: " + e.getMessage());
            railsBytes = e.getHtml().getBytes();
        }

        // store new cookies into PortletSession.
        session.setAttribute("cookies", client.getCookies(), PortletSession.PORTLET_SCOPE);

        // do not leave the POST method hanging around in the session.
        session.setAttribute("requestMethod", "get", PortletSession.PORTLET_SCOPE);

        // ???
        if (session.getAttribute("httpReferer") != null) {
            log.debug("Saving route from httpReferer: " + httpReferer.toString());
            session.setAttribute("railsRoute", httpReferer.toString(), PortletSession.PORTLET_SCOPE);
        }

        return railsBytes;
    }

    /**
     *  Executes a GET request.
       TODO: get responseHeader and responseBody
     */
    private byte[] executeGet(PortletSession session, OnlineClient client) throws HttpException, IOException {
        /*
        // save/update client Cookie[] into cookies HashMap
        for (Cookie c : client.cookies)
          cookies.put((String)c.getName(), (Cookie)c);
         */

        // should servlet cookies be stored to the session? here they are not.
        byte[] railsBytes = null;
        try {
            railsBytes = client.get();
        } catch (RailsAppException e) {
            log.error("executeGet: " + e.getMessage());
            railsBytes = e.getHtml().getBytes();
        }

        session.setAttribute("cookies", client.getCookies(), PortletSession.PORTLET_SCOPE);
        return railsBytes;
    }

    /**
     * Create the {@link PageProcessor} and process the railsRoute.
     * 
     * @param response - {@link PortletResponse}
     * @param railsResponse - {@link String}
     * @return outputHTML - {@link String}
     */
    private String processResponseBody(RenderResponse response, String railsResponse) {

        String outputHTML = "";
        if ((railsResponse != null) && (railsResponse.length() > 1)) {
            try {
                // PageProcessor => HeadProcessor, BodyTagVisitor (uses RouteAnalyzer)
                PageProcessor p = new PageProcessor(railsResponse, getServlet(), response);
                outputHTML = p.process(getRailsBaseUrl(), getRailsRoute());

                /** Set the portlet title by HTML title */
                String title = p.getTitle();
                log.debug("Title: " + title);

                if (title == null || title.length() == 0) {
                    response.setTitle("\u00A0"); // nbsp, because Liferay post-processes blank strings
                } else {
                    response.setTitle(title);
                }
            } catch (ParserException e) {
                log.error(e.getMessage());

            } catch (IllegalStateException e) {
                log.error(e.getMessage());
            }
        }
        return outputHTML;
    }

    /**
     * Select the request method (GET or POST).
     *
     * Use requestMethod if defined, otherwise use GET.
     */
    private String getRequestMethod(PortletSession session) {
        String requestMethod = session.getAttribute("requestMethod") == null ? "get"
                : (String) session.getAttribute("requestMethod");
        log.debug("Request method: " + requestMethod);
        return requestMethod;
    }

    private URL getHttpReferer(PortletSession session) {
        if (session.getAttribute("httpReferer") != null) {
            return (java.net.URL) session.getAttribute("httpReferer");
        }
        return null; // otherwise
    }

    /** Cookie handling.
     *
     * If session cookies were found in the PortletSession,
     * use them later with OnlineClient.
     *
     * Cookies are never explicitly removed from PortletSession.
     *
     * In the PortletSession, the cookies are stored in Cookie[].
     * In this method they are handled in HashMap.
     *
     */
    private Map<String, Cookie> getCookies(PortletSession session, PortletRequest request,
            PortletResponse response) {
        Map<String, Cookie> cookies = new HashMap<String, Cookie>();
        // get cookies from PortletSession to HashMap
        if (session.getAttribute("cookies") != null) {
            log.debug("Stored session cookies found");
            for (Cookie cookie : (Cookie[]) session.getAttribute("cookies")) {
                cookies.put((String) cookie.getName(), cookie);
            }
        }

        // Add dynamic session cookies.
        // These should not be saved to the database and the portlet session.

        // Create the secret cookie.
        // This is a weak symmetry-key algorithm.
        // Security could be boosted by using private and public key pairs.
        // http://en.wikipedia.org/wiki/Public-key_cryptography
        if (sessionSecret != null) {
            Cookie _secretCookie = secretCookie(session);
            cookies.put((String) _secretCookie.getName(), _secretCookie);
        }

        // UID cookie
        if (session.getAttribute("uid") != null) {
            Cookie _uidCookie = uidCookie(session);
            cookies.put((String) _uidCookie.getName(), _uidCookie);
        }

        // GID cookie
        Cookie gidCookie = gidCookie(session, request);
        if (gidCookie != null) {
            cookies.put(gidCookie.getName(), gidCookie);
        }

        // ResourceURL cookie
        String resourceUrlValue = (String) session.getAttribute("resourceURL");
        if (resourceUrlValue != null) {
            Cookie resourceUrlCookie = resourceUrlCookie(session, resourceUrlValue);
            cookies.put(resourceUrlCookie.getName(), resourceUrlCookie);
        }

        // Stores preferences in a cookie
        Cookie preferencesCookie = preferencesCookie(session, request);
        cookies.put(preferencesCookie.getName(), preferencesCookie);

        // Store portlet namespace to cookie
        Cookie namespaceCookie = namespaceCookie(session, response);
        cookies.put(namespaceCookie.getName(), namespaceCookie);

        log.debug(cookies.size() + " cookies");
        return cookies;
    }

    /**
     * Generates a string with all the preferences values, using the patter:
     * key=value;key=value; and so on.
     * 
     * @param portletPreferences - {@link PortletPreferences}
     * @return {@link String}
     */
    private String preferencesToString(PortletPreferences portletPreferences) {
        Enumeration<String> names = portletPreferences.getNames();

        StringBuilder builder = new StringBuilder();

        while (names.hasMoreElements()) {
            String key = (String) names.nextElement();
            builder.append(key + "=" + portletPreferences.getValue(key, null) + ";");
        }

        return builder.toString();
    }

    private void retrieveFiles(ActionRequest request) throws IOException {
        UploadPortletRequest uploadRequest = PortalUtil.getUploadPortletRequest(request);
        Map<String, Object[]> files = new HashMap<String, Object[]>();

        // Try to discover the file parameters
        for (Object key : uploadRequest.getParameterMap().keySet()) {
            File file = uploadRequest.getFile(key.toString());
            if (file != null && file.length() > 0) {
                // I need to store the bytes because the file will be deleted
                // after the method execution, so when OnlineClient request the
                // file it no longer exists. I need to store the file object
                // to store the path and the attributes of this file.
                byte[] bytes = FileUtil.getBytes(file);
                files.put(key.toString(), new Object[] { file, bytes });
            }
        }

        if (files.size() > 0) {
            request.setAttribute("files", files);
        }
    }

    /** 
     * Debug 
     */
    private void loggerInfo(RenderRequest request, RenderResponse response) {
        if (log.isDebugEnabled()) {
            log.debug("Portlet namespace: " + response.getNamespace());
            log.debug("Remote user: " + request.getRemoteUser());

            if (request.getUserPrincipal() != null) {
                // user principal is null in pre-prod
                log.debug("User principal name: " + request.getUserPrincipal().getName());
            }
        }
    }

    private void debugParams(NameValuePair[] parametersBody) {
        log.debug(parametersBody.length + " parameters: --------------------");
        for (int x = 0; x < parametersBody.length; x++) {
            log.debug(parametersBody[x].toString());
        }
        log.debug("----------------------------------");
    }
}