org.danann.cernunnos.runtime.web.CernunnosPortlet.java Source code

Java tutorial

Introduction

Here is the source code for org.danann.cernunnos.runtime.web.CernunnosPortlet.java

Source

/*
 * Copyright 2007 Andrew Wills
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.danann.cernunnos.runtime.web;

import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletConfig;
import javax.portlet.PortletException;
import javax.portlet.PortletRequest;
import javax.portlet.PortletResponse;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.portlet.PortletFileUpload;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.danann.cernunnos.Attributes;
import org.danann.cernunnos.Grammar;
import org.danann.cernunnos.ReturnValueImpl;
import org.danann.cernunnos.Task;
import org.danann.cernunnos.runtime.RuntimeRequestResponse;
import org.danann.cernunnos.runtime.ScriptRunner;
import org.danann.cernunnos.runtime.XmlGrammar;
import org.dom4j.Document;
import org.dom4j.io.SAXReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class CernunnosPortlet extends GenericPortlet {

    // Static Members...
    private static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";

    // Instance Members...
    private ScriptRunner runner = null;
    final Map<URL, Task> tasks = new HashMap<URL, Task>();
    private Settings settings = null;
    private ApplicationContext spring_context = null;
    private final Log log = LogFactory.getLog(CernunnosPortlet.class); // Don't declare as static in general libraries

    /*
     * Public API.
     */

    /**
     * Use this constant to get a reference to the calling portlet instance from
     * the <code>PortletRequest</code> in your JSP.  Thereafter, you'll be able
     * to leverage the context settings by invoking <code>runScript</code>.
     */
    public static final String PORTLET_REQUEST_PARAM = "CernunnosPortlet.PORTLET_REQUEST_PARAM";

    @SuppressWarnings("unchecked")
    @Override
    public void init() throws PortletException {

        PortletConfig config = getPortletConfig();

        // Load the context, if present...
        try {

            // Bootstrap the portlet Grammar instance...
            final Grammar root = XmlGrammar.getMainGrammar();
            final InputStream inpt = CernunnosPortlet.class.getResourceAsStream("portlet.grammar"); // Can't rely on classpath:// protocol handler...
            final Document doc = new SAXReader().read(inpt);
            final Task k = new ScriptRunner(root).compileTask(doc.getRootElement());
            final RuntimeRequestResponse req = new RuntimeRequestResponse();
            final ReturnValueImpl rslt = new ReturnValueImpl();
            req.setAttribute(Attributes.RETURN_VALUE, rslt);
            k.perform(req, new RuntimeRequestResponse());
            Grammar g = (Grammar) rslt.getValue();
            runner = new ScriptRunner(g);

            // Choose a context location & load it if it exists...
            String defaultLoc = "/WEB-INF/" + config.getPortletName() + "-portlet.xml";
            URL defaultUrl = getPortletConfig().getPortletContext().getResource(defaultLoc);
            URL u = Settings.locateContextConfig(
                    getPortletConfig().getPortletContext().getResource("/").toExternalForm(),
                    config.getInitParameter(CONFIG_LOCATION_PARAM), defaultUrl);
            if (u != null) {
                // There *is* a resource mapped to this path name...
                spring_context = new FileSystemXmlApplicationContext(u.toExternalForm());
            }

            if (log.isTraceEnabled()) {
                log.trace("Loaction of spring context (null means none):  " + u);
            }

            // Load the Settings...
            Map<String, String> settingsMap = new HashMap<String, String>(); // default...
            if (spring_context != null && spring_context.containsBean("settings")) {
                settingsMap = (Map<String, String>) spring_context.getBean("settings");
            }
            settings = Settings.load(settingsMap);

        } catch (Throwable t) {
            String msg = "Failure in CernunnosPortlet.init()";
            throw new PortletException(msg, t);
        }

    }

    @Override
    public void processAction(ActionRequest req, ActionResponse res) throws PortletException {

        // If a 'view' parameter is provided, we need to forward it to the doView() method appropriately...
        if (req.getParameter(settings.getValue(Settings.Entry.VIEW_PARAMETER)) != null) {
            String view = req.getParameter(settings.getValue(Settings.Entry.VIEW_PARAMETER));
            res.setRenderParameter(settings.getValue(Settings.Entry.VIEW_PARAMETER), view);
        }

        // Perform specified action(s)...
        String[] actions = req.getParameterValues(settings.getValue(Settings.Entry.ACTION_PARAMETER));
        if (actions != null && actions.length > 0) {

            // Invoke each action...
            String scriptPath = null;
            try {

                for (String act : actions) {
                    if (act != null && act.length() > 0) {
                        scriptPath = settings.getValue(Settings.Entry.ACTION_PREFIX) + act
                                + settings.getValue(Settings.Entry.ACTION_SUFFIX);
                        URL url = getPortletConfig().getPortletContext().getResource(scriptPath);
                        runScript(url, req, res);
                    }
                }

            } catch (Throwable t) {
                String msg = "CernunnosPortlet.processAction() failed to run the specified script:  " + scriptPath;
                throw new PortletException(msg, t);
            }

        }

    }

    @Override
    public void doView(RenderRequest req, RenderResponse res) throws PortletException {

        // Choose a view from 4 possibilities...
        String view = null;
        if (req.getParameter(settings.getValue(Settings.Entry.VIEW_PARAMETER)) != null) {
            // (1) A view has been specified on this request...
            view = req.getParameter(settings.getValue(Settings.Entry.VIEW_PARAMETER));

            // We must also move the user to this screen for future renderings...
            req.getPortletSession(true).setAttribute(CURRENT_VIEW_SESSION_ATTRIBUTE, view);
        } else if (req.getPortletSession(true).getAttribute(CURRENT_VIEW_SESSION_ATTRIBUTE) != null) {
            // (2) The user has previously navigated off the default page...
            view = (String) req.getPortletSession(true).getAttribute(CURRENT_VIEW_SESSION_ATTRIBUTE);
        } else if (req.getPreferences().getValue(settings.getValue(Settings.Entry.VIEW_PARAMETER), "")
                .length() > 0) {
            // (3) The view preference was set, maybe at publish time, maybe by an
            // admin or maybe by the user.
            view = req.getPreferences().getValue(settings.getValue(Settings.Entry.VIEW_PARAMETER), "");
        } else {
            // (4) Use the default page...
            view = settings.getValue(Settings.Entry.DEFAULT_VIEW);
        }

        // Render...
        try {

            renderView(view, req, res);
        } catch (Throwable t) {

            String msg = "Rendering failure in CernunnosPortlet.doView()";
            throw new PortletException(msg, t);
        }
    }

    @Override
    public void doEdit(RenderRequest req, RenderResponse res) throws PortletException {
        // This is probably not a permanent approach, but atm all we need is...
        //        doView(req, res);
        String editView = getViewNameFromPreferredValueOrDefaultValue(req, res, Settings.Entry.DEFAULT_EDIT_VIEW);

        // Render...
        if (editView == null) {
            doView(req, res);
        } else {

            try {

                renderView(editView, req, res);
            } catch (Throwable t) {

                String msg = "Rendering failure in CernunnosPortlet.doEdit()";
                throw new PortletException(msg, t);
            }

        }
    }

    @Override
    public void doHelp(RenderRequest req, RenderResponse res) throws PortletException {

        String helpView = getViewNameFromPreferredValueOrDefaultValue(req, res, Settings.Entry.DEFAULT_HELP_VIEW);

        // Render...
        if (helpView == null) {
            doView(req, res);
        } else {

            try {

                renderView(helpView, req, res);
            } catch (Throwable t) {

                String msg = "Rendering failure in CernunnosPortlet.doHelp()";
                throw new PortletException(msg, t);
            }

        }
    }

    private String getViewNameFromPreferredValueOrDefaultValue(RenderRequest req, RenderResponse res,
            Settings.Entry defaultValue) throws PortletException {

        String viewToRender = null;

        // Choose a view from 2 possibilities (note we are ignoring the session
        // attribute and relying on PortletMode instead to get us into this
        // method and the portlet preferences since there is no way to specify
        // a view for Edit or Help modes without a Settings.Entry available)
        if (req.getParameter(settings.getValue(Settings.Entry.VIEW_PARAMETER)) != null) {
            // (1) A view has been specified on this request...
            viewToRender = req.getParameter(settings.getValue(Settings.Entry.VIEW_PARAMETER));
        } else {
            // (2) Use the default page (note: this could be null if this is
            // not set by the deployer in the Spring context)
            viewToRender = settings.getValue(defaultValue);
        }

        // This still may be null if a value was not set for the defaultValue.
        return viewToRender;

    }

    private void renderView(String viewName, RenderRequest req, RenderResponse res)
            throws PortletException, MalformedURLException {

        res.setContentType(req.getResponseContentType());
        String viewPath = settings.getValue(Settings.Entry.VIEW_PREFIX) + viewName
                + settings.getValue(Settings.Entry.VIEW_SUFFIX);
        URL url = getPortletConfig().getPortletContext().getResource(viewPath);
        if (url == null) {
            // This won't work at all...
            String msg = "The specified viewPath is not found:  " + viewPath;
            throw new RuntimeException(msg);
        }
        runScript(url, req, res);
    }

    private void runScript(URL u, PortletRequest req, PortletResponse res) {
        runScript(u, req, res, new RuntimeRequestResponse());
    }

    @SuppressWarnings("unchecked")
    private void runScript(URL u, PortletRequest req, PortletResponse res, RuntimeRequestResponse rrr) {

        // Choose the right Task...
        Task k = getTask(u);

        // Basic, guaranteed request attributes...
        rrr.setAttribute(WebAttributes.REQUEST, req);
        rrr.setAttribute(WebAttributes.RESPONSE, res);

        // Also let's check the request for multi-part form 
        // data & convert to request attributes if we find any...
        List<InputStream> streams = new LinkedList<InputStream>();
        if (req instanceof ActionRequest && PortletFileUpload.isMultipartContent((ActionRequest) req)) {

            if (log.isDebugEnabled()) {
                log.debug("Multipart form data detected (preparing to process).");
            }

            try {
                final DiskFileItemFactory fac = new DiskFileItemFactory();
                final PortletFileUpload pfu = new PortletFileUpload(fac);
                final long maxSize = pfu.getFileSizeMax(); // FixMe!!
                pfu.setFileSizeMax(maxSize);
                pfu.setSizeMax(maxSize);
                fac.setSizeThreshold((int) (maxSize + 1L));
                List<FileItem> items = pfu.parseRequest((ActionRequest) req);
                for (FileItem f : items) {
                    if (log.isDebugEnabled()) {
                        log.debug("Processing file upload:  name='" + f.getName() + "',fieldName='"
                                + f.getFieldName() + "'");
                    }
                    InputStream inpt = f.getInputStream();
                    rrr.setAttribute(f.getFieldName(), inpt);
                    rrr.setAttribute(f.getFieldName() + "_FileItem", f);
                    streams.add(inpt);
                }
            } catch (Throwable t) {
                String msg = "Cernunnos portlet failed to process multipart " + "form data from the request.";
                throw new RuntimeException(msg, t);
            }

        } else {

            if (log.isDebugEnabled()) {
                log.debug("Multipart form data was not detected.");
            }
        }

        // Anything that should be included from the spring_context?
        if (spring_context != null && spring_context.containsBean("requestAttributes")) {
            Map<String, Object> requestAttributes = (Map<String, Object>) spring_context
                    .getBean("requestAttributes");
            for (Map.Entry entry : requestAttributes.entrySet()) {
                rrr.setAttribute((String) entry.getKey(), entry.getValue());
            }
        }

        runner.run(k, rrr);

        // Clean up resources...
        if (streams.size() > 0) {
            try {
                for (InputStream inpt : streams) {
                    inpt.close();
                }
            } catch (Throwable t) {
                String msg = "Cernunnos portlet failed to release resources.";
                throw new RuntimeException(msg, t);
            }
        }

    }

    /*
     * Private Stuff.
     */

    private static final String CURRENT_VIEW_SESSION_ATTRIBUTE = "Death closes all: but something ere the end,\n"
            + "Some work of noble note, may yet be done,\n" + "Not unbecoming men that strove with Gods.";

    private synchronized Task getTask(URL u) {

        // Assertions.
        if (u == null) {
            String msg = "Argument 'u [URL]' cannot be null.";
            throw new IllegalArgumentException(msg);
        }

        Task rslt = null;
        if (tasks.containsKey(u)) {
            rslt = tasks.get(u);
        } else {

            rslt = runner.compileTask(u.toString());
            tasks.put(u, rslt);

            if (log.isTraceEnabled()) {
                log.trace("Compiling a Task for the following URL:  " + u.toString());
            }

        }

        return rslt;

    }
}