com.googlecode.fascinator.portal.pages.Dispatch.java Source code

Java tutorial

Introduction

Here is the source code for com.googlecode.fascinator.portal.pages.Dispatch.java

Source

/* 
 * The Fascinator - Portal
 * Copyright (C) 2008-2011 University of Southern Queensland
 * Copyright (C) 2012 Queensland Cyber Infrastructure Foundation (http://www.qcif.edu.au/)
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package com.googlecode.fascinator.portal.pages;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.lang.StringUtils;
import org.apache.tapestry5.StreamResponse;
import org.apache.tapestry5.annotations.SessionState;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.util.TimeInterval;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.RequestGlobals;
import org.apache.tapestry5.services.Response;
import org.apache.tapestry5.upload.services.MultipartDecoder;
import org.apache.tapestry5.upload.services.UploadedFile;
import org.slf4j.Logger;

import com.googlecode.fascinator.HarvestClient;
import com.googlecode.fascinator.api.PluginException;
import com.googlecode.fascinator.api.authentication.AuthenticationException;
import com.googlecode.fascinator.api.authentication.User;
import com.googlecode.fascinator.common.JsonSimple;
import com.googlecode.fascinator.common.JsonSimpleConfig;
import com.googlecode.fascinator.common.MimeTypeUtil;
import com.googlecode.fascinator.portal.FormData;
import com.googlecode.fascinator.portal.JsonSessionState;
import com.googlecode.fascinator.portal.services.DynamicPageService;
import com.googlecode.fascinator.portal.services.GenericStreamResponse;
import com.googlecode.fascinator.portal.services.HttpStatusCodeResponse;
import com.googlecode.fascinator.portal.services.PortalManager;
import com.googlecode.fascinator.portal.services.PortalSecurityManager;
import com.googlecode.fascinator.portal.services.VelocityService;

/**
 * <h3>Introduction</h3>
 * <p>
 * Dispatch is the only Tapestry Page object, and it is responsible for three
 * tasks:
 * </p>
 * 
 * <ul>
 * <li><strong>Resource routing</strong>, mostly URL parsing according to some
 * basic rules, but also looking for special cases.</li>
 * <li><strong>Security</strong>, particularly focusing on Single Sign-On.</li>
 * <li><strong>File upload</strong> handling and storage. Files need to grabbed
 * from the Tapestry framework and moved into our storage and the transformation
 * tool chain.</li>
 * </ul>
 * 
 * <h3>Wiki Link</h3>
 * <p>
 * <b>https://fascinator.usq.edu.au/trac/wiki/Fascinator/Documents/Portal/
 * JavaCore#TapestryPages</b>
 * </p>
 * 
 * @author Oliver Lucido
 */
public class Dispatch {

    private static final String AJAX_EXT = ".ajax";

    @SuppressWarnings("unused")
    private static final String POST_EXT = ".post";

    private static final String SCRIPT_EXT = ".script";

    /** The default resource if none is specified. The home page, generally */
    public static final String DEFAULT_RESOURCE = "home";

    @Inject
    private Logger log;

    @SessionState
    private JsonSessionState sessionState;

    @Inject
    private DynamicPageService pageService;

    @Inject
    private PortalManager portalManager;

    @Inject
    private PortalSecurityManager security;

    @Inject
    private VelocityService velocityService;

    @Inject
    private Request request;

    @Inject
    private Response response;

    @Inject
    private MultipartDecoder decoder;

    @Inject
    private RequestGlobals rg;

    private FormData formData;

    // Resource Processing variable
    private String resourceName;
    private String portalId;
    private String defaultPortal;
    private String requestUri;
    private String[] path;
    private HttpServletRequest hsr;
    private boolean isFile;
    private boolean isSpecial;

    // Rendering variables
    private String mimeType;
    private InputStream stream;

    private JsonSimpleConfig sysConfig;

    /**
     * Entry point for Tapestry to send page requests.
     * 
     * @param params : An array of request parameters from Tapestry
     * @returns StreamResponse : Tapestry object to return streamed response
     */
    public StreamResponse onActivate(Object... params) {
        // log.debug("Dispatch starting : {} {}",
        // request.getMethod(), request.getPath());

        try {
            sysConfig = new JsonSimpleConfig();
            defaultPortal = sysConfig.getString(PortalManager.DEFAULT_PORTAL_NAME, "portal", "defaultView");
        } catch (IOException ex) {
            log.error("Error accessing system config", ex);
            return new HttpStatusCodeResponse(500, "Sorry, an internal server error has occured");
        }

        // Do all our parsing
        resourceName = resourceProcessing();

        // Make sure it's valid
        if (resourceName == null) {
            if (response.isCommitted()) {
                return GenericStreamResponse.noResponse();
            }
            return new HttpStatusCodeResponse(404, "Page not found: " + requestUri);
        }

        // Initialise storage for our form data
        // if there's no persistant data found.
        prepareFormData();

        // Set session timeout (defaults to 2 hours)
        int timeoutMins = sysConfig.getInteger(120, "portal", "sessionTimeout");
        hsr.getSession().setMaxInactiveInterval(timeoutMins * 60);

        // SSO Integration - Ignore AJAX and such
        if (!isSpecial) {
            // Make sure it's not a static resource
            if (security.testForSso(sessionState, resourceName, requestUri)) {
                // Run SSO
                boolean redirected = security.runSsoIntegration(sessionState, formData);
                // Finish here if SSO redirected
                if (redirected) {
                    return GenericStreamResponse.noResponse();
                }
            }
        }

        // Make static resources cacheable
        if (resourceName.indexOf(".") != -1 && !isSpecial) {
            response.setHeader("Cache-Control", "public");
            response.setDateHeader("Expires", System.currentTimeMillis() + new TimeInterval("10y").milliseconds());
        }

        // Are we doing a file upload?
        isFile = ServletFileUpload.isMultipartContent(hsr);
        if (isFile) {
            fileProcessing();
        }

        // Page render time
        renderProcessing();

        return new GenericStreamResponse(mimeType, stream);
    }

    private void prepareFormData() {
        hsr = rg.getHTTPServletRequest();
        if ((resourceName.indexOf(".") == -1) || isSpecial) {
            if (formData == null) {
                formData = new FormData(request, hsr);
                // log.debug("created FormData:{}", formData);
            }
        }
    }

    private void fileProcessing() {
        // What we are looking for
        UploadedFile uploadedFile = null;
        String workflowId = null;

        // Roles of current user
        String username = (String) sessionState.get("username");
        String userSource = (String) sessionState.get("source");
        List<String> roles = null;
        try {
            User user = security.getUser(sessionState, username, userSource);
            String[] roleArray = security.getRolesList(sessionState, user);
            roles = Arrays.asList(roleArray);
        } catch (AuthenticationException ex) {
            log.error("Error retrieving user data.");
            return;
        }

        // The workflow we're using
        List<String> reqParams = request.getParameterNames();
        log.info("REQUEST {}", request);
        log.info("reqParams {}", reqParams);
        if (reqParams.contains("upload-file-workflow")) {
            workflowId = request.getParameter("upload-file-workflow");
        }
        if (workflowId == null) {
            log.error("No workflow provided with form data.");
            return;
        }
        // Upload context
        String uploadContext = "";
        if (reqParams.contains("upload-file-context")) {
            uploadContext = request.getParameter("upload-file-context");
        }

        JsonSimple workflowConfig = sysConfig.getJsonSimpleMap("uploader").get(workflowId);

        // Roles allowed to upload into this workflow
        boolean security_check = false;
        for (String role : workflowConfig.getStringList("security")) {
            if (roles.contains(role)) {
                security_check = true;
            }
        }
        if (!security_check) {
            log.error("Security error, current user not allowed to upload.");
            return;
        }

        // Get the workflow's file directory
        String file_path = workflowConfig.getString(null, "upload-path");

        // Get the uploaded file
        for (String param : reqParams) {
            UploadedFile tmpFile = decoder.getFileUpload(param);
            if (tmpFile != null) {
                // Our file
                uploadedFile = tmpFile;
            }
        }
        if (uploadedFile == null) {
            log.error("No uploaded file found!");
            return;
        }

        // Write the file to that directory
        if (uploadContext == null || "".equals(uploadContext)) {
            file_path = file_path + "/" + uploadedFile.getFileName();
        } else {
            file_path = file_path + "/" + uploadContext + "/" + uploadedFile.getFileName();
        }
        File file = new File(file_path);
        if (!file.exists()) {
            file.getParentFile().mkdirs();
            try {
                file.createNewFile();
            } catch (IOException ex) {
                log.error("Failed writing file", ex);
                return;
            }
        }
        uploadedFile.write(file);

        // Make sure the new file gets harvested
        String configPath = workflowConfig.getString(null, "json-config");
        if (configPath == null) {
            log.error("No harvest configuration file provided!");
            return;
        }
        File harvestFile = new File(configPath);
        // Get the workflow template needed for stage 1
        String template = "";
        try {
            JsonSimple harvestConfig = new JsonSimple(harvestFile);
            List<JsonSimple> stages = harvestConfig.getJsonSimpleList("stages");
            if (stages.size() > 0) {
                template = stages.get(0).getString(null, "template");
            }
        } catch (IOException ex) {
            log.error("Unable to access workflow config : ", ex);
        }

        HarvestClient harvester = null;
        String oid = null;
        String error = null;
        try {
            harvester = new HarvestClient(harvestFile, file, username);
            harvester.start();
            oid = harvester.getUploadOid();
            harvester.shutdown();
        } catch (PluginException ex) {
            error = "File upload failed : " + ex.getMessage();
            log.error(error);
            harvester.shutdown();
        } catch (Exception ex) {
            log.error("Failed harvest", ex);
            return;
        }
        boolean success = file.delete();
        if (!success) {
            log.error("Error deleting uploaded file from cache: " + file.getAbsolutePath());
        }

        // Now create some session data for use later
        Map<String, String> file_details = new LinkedHashMap<String, String>();
        file_details.put("name", uploadedFile.getFileName());
        if (error != null) {
            // Strip our package/class details from error string
            Pattern pattern = Pattern.compile("au\\..+Exception:");
            Matcher matcher = pattern.matcher(error);
            file_details.put("error", matcher.replaceAll(""));
        }
        file_details.put("workflow", workflowId);
        file_details.put("template", template);
        file_details.put("location", file_path);
        file_details.put("size", String.valueOf(uploadedFile.getSize()));
        file_details.put("type", uploadedFile.getContentType());
        if (oid != null) {
            file_details.put("oid", oid);
        }
        // Helps some browsers (like IE7) resolve the path from the form
        sessionState.set("fileName", uploadedFile.getFileName());
        sessionState.set(uploadedFile.getFileName(), file_details);
        formData.set("fileProcessing", "true");
        formData.set("oid", oid);
        sessionState.set("uploadFormData", formData);
    }

    private void renderProcessing() {
        // render the page or retrieve the resource
        if ((resourceName.indexOf(".") == -1) || isSpecial) {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            mimeType = pageService.render(portalId, resourceName, out, formData, sessionState);
            stream = new ByteArrayInputStream(out.toByteArray());
        } else {
            mimeType = MimeTypeUtil.getMimeType(resourceName);
            stream = velocityService.getResource(portalId, resourceName);
        }
    }

    private String resourceProcessing() {
        requestUri = request.getAttribute("RequestURI").toString();
        path = requestUri.split("/");

        // log.debug("requestUri:'{}', path:'{}'", requestUri, path);
        // log.debug("path.length:{}", path.length);
        if ("".equals(requestUri) || path.length == 1) {
            portalId = "".equals(path[0]) ? defaultPortal : path[0];
            String url = sysConfig.getString(null, "urlBase");
            if (url != null) {
                url += portalId + "/" + DEFAULT_RESOURCE;
            } else {
                url = request.getContextPath() + "/" + portalId + "/" + DEFAULT_RESOURCE;
            }
            try {
                log.debug("REDIRECT : '{}'", url);
                response.sendRedirect(url);
            } catch (IOException ioe) {
                log.error("Failed to redirect to default URL: {}", url);
            }
            return null;
        }

        portalId = (String) sessionState.get("portalId", defaultPortal);
        resourceName = DEFAULT_RESOURCE;
        if (path.length > 1) {
            portalId = path[0];
            resourceName = StringUtils.join(path, "/", 1, path.length);
        }

        if (!portalManager.exists(portalId)) {
            return null;
        }

        isSpecial = false;
        String match = getBestMatchResource(resourceName);
        // log.trace("resourceName = {}, match = {}", resourceName, match);

        return match;
    }

    /**
     * Parse the request URL to find the best matching resource from the portal.
     * 
     * This method will recursively call itself if required to break the URL
     * down into constituent parts.
     * 
     * @param resource : The resource we are looking for
     * @returns String : The best matching resource
     */
    public String getBestMatchResource(String resource) {
        String searchable = resource;
        String ext = "";
        // Look for AJAX
        if (resource.endsWith(AJAX_EXT)) {
            isSpecial = true;
            ext = AJAX_EXT;
        }
        // Look for scripts
        if (resource.endsWith(SCRIPT_EXT)) {
            isSpecial = true;
            ext = SCRIPT_EXT;
        }
        // Strip special extensions whilst checking on disk
        if (isSpecial) {
            searchable = resource.substring(0, resource.lastIndexOf(ext));
        }
        // Return if found
        if (velocityService.resourceExists(portalId, searchable) != null) {
            return resource;
        }
        // Keep checking
        int slash = resource.lastIndexOf('/');
        if (slash != -1) {
            return getBestMatchResource(resource.substring(0, slash));
        }
        return null;
    }
}