org.spirit.spring.BotListRubyController.java Source code

Java tutorial

Introduction

Here is the source code for org.spirit.spring.BotListRubyController.java

Source

/* 
 *** Notice Update: 8/14/2007
 *** Copyright 2007 Berlin Brown
 *** Copyright 2006-2007 Newspiritcompany.com
 *
 * -------------------------- COPYRIGHT_AND_LICENSE --
 * Botlist contains an open source suite of software applications for 
 * social bookmarking and collecting online news content for use on the web.  
 * Multiple web front-ends exist for Django, Rails, and J2EE.  
 * Users and remote agents are allowed to submit interesting articles.
 *
 * Copyright (c) 2007, Botnode.com (Berlin Brown)
 * http://www.opensource.org/licenses/bsd-license.php
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *
 *       * Redistributions of source code must retain the above copyright notice, 
 *       this list of conditions and the following disclaimer.
 *       * Redistributions in binary form must reproduce the above copyright notice, 
 *       this list of conditions and the following disclaimer in the documentation 
 *       and/or other materials provided with the distribution.
 *       * Neither the name of the Botnode.com (Berlin Brown) nor 
 *       the names of its contributors may be used to endorse or promote 
 *       products derived from this software without specific prior written permission.
 *   
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * -------------------------- END_COPYRIGHT_AND_LICENSE --
 */
package org.spirit.spring;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.bsf.BSFException;
import org.apache.bsf.BSFManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jruby.javasupport.bsf.JRubyEngine;
import org.spirit.cache.BotListCacheController;
import org.spirit.cache.BotListCacheEntity;
import org.spirit.dao.BotListPostListingDAO;
import org.spirit.dao.BotListUserLinkDAO;
import org.spirit.form.base.BotListBaseForm;
import org.spirit.util.BotListCookieManager;
import org.spirit.util.BotListFileUploadType;
import org.spirit.util.io.JRubyIOHelper;
import org.springframework.context.ApplicationContext;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;

/**
 * Spring Controller that interfaces between spring and jruby; reads the ruby script and invokes the
 * correct model and view.
 * 
 * @author Berlin Brown 
 */
public class BotListRubyController extends BotListRubyDAOHandler {

    private Log log = LogFactory.getLog(getClass());
    private static final String _CLASS_IDENTIFIER = "BotListRubyController: ";
    private static final String PROCESSING_TIME = "processingtime";

    /**
     * User Link Data Access Object.
     */
    private BotListUserLinkDAO userLinkDAO = null;

    /**
     * Post Listing DAO.
     */
    private BotListPostListingDAO postListingDAO = null;

    /**
     * Ruby and BSF Managers.
     */
    private BSFManager mManager;
    private JRubyEngine mEngine;

    // configuration parametes and defaults
    private String mScriptEngineClass = "org.jruby.javasupport.bsf.JRubyEngine";

    private String mJspDir = "/WEB-INF/jsps";
    private String mScriptEngineName = "ruby";
    private String mScriptExtension = "rb";
    private String mInitScript = "/WEB-INF/jsps/INIT.rb";

    private String springServletContext = "spring";

    /** 
     * Record keeping, when the script starts and ends.
     */
    private long scriptStartTime = 0;
    private long scriptEndTime = 0;

    private BotListCacheController cacheController;

    public static final String DECLARE_BEAN_CONTEXT = "context";
    public static final String DECLARE_BEAN_REQUEST = "request";
    public static final String DECLARE_BEAN_RESPONSE = "response";
    public static final String DECLARE_BEAN_CONTROLLER = "controller";

    /**
     * Last command object instance.
     */
    private Object rubyCommand;

    /**
     * File Upload Properties and Processor.
     */
    private BotListFileUploadType fileUploadUtil;

    /**
     * Default Constructor.    
     */
    BotListRubyController() {
        this.setBindOnNewForm(true);
    }

    private static String CLASS_IDENTIFIER() {
        return _CLASS_IDENTIFIER;
    }

    /**
     * This function is quite similar to the Jobster internal PathToViewController.
     * Duplicated here so that the RAD module can be self contained.
     */
    public String getViewNameFromServletPath(String servletPath, String uri, String contextPath) {

        String viewName = servletPath;
        int beg = 0, end = viewName.length();
        if (end > 0 && viewName.charAt(0) == '/') {
            beg = 1;
        } else {
            // We are now assuming incoming servletPath is actually a full URL
            // beg = servletPath.lastIndexOf('/');
            if (getJspPathPos(uri, contextPath) != -1) {
                beg = getJspPathPos(uri, contextPath);
            }
        }
        int dot = viewName.lastIndexOf('.');
        if (dot >= 0) {
            end = dot;
        }
        viewName = viewName.substring(beg, end);
        return viewName;
    }

    public String getDefaultViewNameFromRequest(HttpServletRequest request) {
        //log.info(CLASS_IDENTIFIER() + "getDefaultViewNameFromRequest() - From Request: " + request.getRequestURL());
        // TODO: to be fixed
        return getViewNameFromServletPath(request.getRequestURI().substring(1), request.getRequestURI(),
                request.getContextPath());
    }

    protected ModelAndView getModelAndView(Object rubyResult, String defaultView) {
        String viewName = null;
        if (Map.class.isAssignableFrom(rubyResult.getClass())) {
            Map map = (Map) rubyResult;
            // look for an embedded view name in the model
            viewName = (String) map.get("viewName");

        } else if (BotListBaseForm.class.isAssignableFrom(rubyResult.getClass())) {

            // Or use the Base Class Form to get the view name.
            BotListBaseForm form = (BotListBaseForm) rubyResult;
            if (form.getViewName() != null) {
                viewName = form.getViewName();
            }
        }
        if (viewName == null) {
            viewName = defaultView;
        }
        TreeMap result = new TreeMap();
        result.put(getCommandName(), rubyResult);

        // keep record of processing time
        scriptEndTime = System.currentTimeMillis();
        long diff = scriptEndTime - scriptStartTime;
        double diffS = diff / 1000.0d;
        result.put(PROCESSING_TIME, "" + diffS);
        return new ModelAndView(viewName, result);
    }

    /**
     * Print Request Information
     */
    private void printRequestInfo(HttpServletRequest request) {
        //log.info(CLASS_IDENTIFIER() + " showForm() uri=" + request.getRequestURI());      
    }

    /**
     * @see org.springframework.web.servlet.mvc.AbstractFormController#showForm(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, org.springframework.validation.BindException)
     */
    protected ModelAndView showForm(HttpServletRequest request, HttpServletResponse response, BindException errors)
            throws Exception {
        printRequestInfo(request);
        Object command = null;
        if (this.getRubyCommand() != null) {
            command = this.getRubyCommand();
        } else {
            command = this.getCommand(request);
        }
        return getModelAndView(command, getDefaultViewNameFromRequest(request));
    }

    /**
     * @see org.springframework.web.servlet.mvc.AbstractFormController#processFormSubmission(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, org.springframework.validation.BindException)
     */
    protected ModelAndView processFormSubmission(HttpServletRequest request, HttpServletResponse response,
            Object command, BindException errors) throws Exception {
        // The command could be an instance of a map
        // or one of the Form beans
        if (command instanceof Map) {
            Map map = (java.util.Map) command;
            for (Iterator iter = request.getParameterMap().entrySet().iterator(); iter.hasNext();) {
                Map.Entry e = (Map.Entry) iter.next();
                Object[] values = ((Object[]) e.getValue());
                map.put(e.getKey(), values[0]);
            }
        }
        try {
            Object result = invokeRubyControllerMethod(request, "onSubmit",
                    new Object[] { request, response, command, errors });
            if (errors.getErrorCount() > 0) {
                return new ModelAndView(getDefaultViewNameFromRequest(request), errors.getModel());
            } else {
                return getModelAndView(result, getDefaultViewNameFromRequest(request));
            }
        } catch (BSFException bfe) {
            bfe.printStackTrace();
            log.error(bfe);
            Throwable targetException = bfe.getTargetException();
            throw new BSFException(bfe.getReason(), targetException.getMessage(), targetException);
        }
    }

    /**
     * Get the botlist cache controller, get a new instance if not existing.
     */
    private BotListCacheController getCacheController() {
        if (this.cacheController == null) {
            this.cacheController = new BotListCacheController();
            this.cacheController.setContext(this.getApplicationContext());
        }
        return this.cacheController;
    }

    /**
     * Establish bean objects for Ruby environment.
     * 
     * @throws BSFException
     */
    protected final void initializeManagerBeans() throws BSFException {
        getCacheController();
        // Define the hibernate database objects for later use
        mManager.declareBean(DECLARE_BEAN_CONTEXT, getApplicationContext(), ApplicationContext.class);
        if (this.getUserLinkDao() != null) {
            mManager.declareBean("daohelper", this.getUserLinkDao(), BotListUserLinkDAO.class);
        } else {
            log.error(CLASS_IDENTIFIER() + "-- ERR: Invalid Database Object Helper --");
        }
        if (this.getPostListingDao() != null) {
            mManager.declareBean("daohelperlisting", getPostListingDao(), getPostListingDao().getClass());
        } else {
            log.error(CLASS_IDENTIFIER() + "-- ERR: Invalid Database Object Helper --");
        }

        // Set this object for DAO access
        mManager.declareBean(DECLARE_BEAN_CONTROLLER, this, BotListRubyDAOHandler.class);
    }

    protected void initializeManager() throws BSFException {
        BSFManager.registerScriptingEngine(getScriptEngineName(), getScriptEngineClass(),
                new String[] { getScriptExtension() });
        mManager = new BSFManager();
        setEngine((JRubyEngine) (mManager.loadScriptingEngine(getScriptEngineName())));
        initializeManagerBeans();
        onInitializeManager();
    }

    public BSFManager getManager() throws BSFException {
        if (mManager == null) {
            initializeManager();
        }
        return mManager;
    }

    /**
     * Read an entire ruby script in from a file. Isn't there a better way to do this?
     * 
     * @param filename
     * @return
     * @throws IOException    
     */
    private String readRubyScript(String filename) throws IOException {

        FileInputStream fis = null;
        BufferedInputStream bis = null;

        fis = new FileInputStream(filename);
        bis = new BufferedInputStream(fis);
        String rubyCode = JRubyIOHelper.inputStreamToString(bis);

        JRubyIOHelper.close(bis);
        JRubyIOHelper.close(fis);
        return rubyCode;
    }

    /**
     * Get JSP Path Pos.
     * i.e. extract 'mypath/file.html' from '/webapp/b/mypath/file.html'
     */
    private int getJspPathPos(String uri, String contextPath) {

        // Example, /webapp/spring
        String target = contextPath + "/" + this.getSpringServletContext();
        int len = target.length();
        return uri.startsWith(target) ? len : -1;
    }

    /**
     * Execute the cache management for this particule ruby code.
     * 1. If CACHE is empty, create new cache object.
     * 2. If CACHE node is available, check node last modified vs file last modified 
     *   2a. If reloaded needed, create new cache object.
     *   2b. Otherwise, use existing CACHE node.  
     */
    public String executeCache(String filename) throws IOException {
        String rubyCode = "";
        if (this.cacheController == null) {
            this.getCacheController();
        }
        BotListCacheEntity cacheObj = (BotListCacheEntity) this.cacheController.getManager().getCacheStore()
                .get(filename);
        if (cacheObj == null) {
            rubyCode = readRubyScript(filename);
            cacheObj = BotListCacheController.createCacheEntity(filename, rubyCode);
            this.cacheController.getManager().getCacheStore().put(filename, cacheObj);
            log.info(CLASS_IDENTIFIER() + "cache-manager: creating new cache entity=" + cacheObj);
        } else {
            // Determine if we need to reload the cache file, otherwise, use existing.
            File rFile = new File(filename);
            long lastModified = rFile.lastModified();
            long cacheLastModified = cacheObj.getLastModified().getTime();
            long cacheModCheck = ((lastModified - cacheLastModified)
                    - (BotListCacheController.CACHE_FREE_TIME * 1000));
            boolean reloadFile = (cacheModCheck > 0);
            if (reloadFile) {
                rubyCode = readRubyScript(filename);
                cacheObj = BotListCacheController.createCacheEntity(filename, rubyCode);
                this.cacheController.getManager().getCacheStore().put(filename, cacheObj);
                log.info(CLASS_IDENTIFIER() + "cache-manager: reloading cache modifed-diff=" + cacheModCheck
                        + " entity=" + cacheObj + "");
            } else {
                rubyCode = cacheObj.getRubyCodeData();
            }
        }
        return rubyCode;
    }

    /**
     * Call system utilities before loading the ruby objects. 
     */
    private void rubySystemInit(HttpServletRequest request) {
        // If user cooke is available, auto login.
        BotListCookieManager.systemGetUserCookieParams(request, this.getCoreUsersDao(),
                this.getProfileSettingsDao());
    }

    /**
     * Get Ruby Controller.       
     * @param request
     * @return
     * @throws BSFException
     */
    public Object getRubyController(HttpServletRequest request) throws BSFException {
        BSFManager manager = getManager();
        manager.declareBean(DECLARE_BEAN_REQUEST, request, HttpServletRequest.class);
        final String CACHED_RUBY_CONTROLLER_ATTRIBUTE = "__RUBYCONTROLLER";
        Object controller = null;
        try {
            // Currently, getServletPath() is returning an incorrect value
            // Use the full URL instead to get the correct servletPath
            String fullURI = request.getRequestURI().toString();
            int lastPos = getJspPathPos(request.getRequestURI(), request.getContextPath());
            if (lastPos == -1) {
                lastPos = 0;
            }
            String servletPath = "/" + fullURI.substring(lastPos + 1);
            String baseFilePath = getServletContext().getRealPath(getJspDir());
            String filename = baseFilePath + servletPath.replaceAll("\\.[a-zA-Z]+$", "." + getScriptExtension());
            String rubyCode = "";

            // System initialization.
            rubySystemInit(request);

            // Simple cache management.
            if (getCacheController().isEnableCaching()) {
                rubyCode = executeCache(filename);
            } else {
                rubyCode = readRubyScript(filename);
            }
            controller = manager.eval(getScriptEngineName(), "(java)", 1, 1, rubyCode);
            request.setAttribute("__manager", manager);
            request.setAttribute(CACHED_RUBY_CONTROLLER_ATTRIBUTE, controller);
        } catch (FileNotFoundException fne) {
            log.error(fne);
            System.err.println("*** WARN: Script not found ********* [" + request.getRequestURI() + "]");
        } catch (IOException e) {
            e.printStackTrace();
            log.error(e);
            // we don't care if the controller file doesn't exist-- we don't require one.
            System.err.println("*** IO Error while reading script ********* [" + request.getRequestURI() + "]");
            System.err.println(e.getMessage());
        } catch (BSFException bfe) {
            setManager(null);
            bfe.printStackTrace();
            log.error(bfe);
            Throwable targetException = bfe.getTargetException();
            throw new BSFException(bfe.getReason(), targetException.getMessage(), targetException);
        }
        return controller;
    }

    /**
     * Load the ruby script based from a servlet.
     * The path will correspond with the actual ruby script, eg:
     * 
     * "/chart/daily_chart.rb"
     * 
     * @throws BSFException
     */
    public Object getRubyServletController(String path, String jspdir, HttpServletRequest request,
            HttpServletResponse response) throws BSFException {
        BSFManager manager = getManager();
        manager.declareBean(DECLARE_BEAN_REQUEST, request, HttpServletRequest.class);
        manager.declareBean(DECLARE_BEAN_RESPONSE, response, HttpServletResponse.class);
        final String CACHED_RUBY_CONTROLLER_ATTRIBUTE = "__RUBYCONTROLLER";
        Object controller = null;
        try {
            String baseFilePath = getServletContext().getRealPath(jspdir);
            String filename = baseFilePath + path;
            String rubyCode = "";
            if (getCacheController().isEnableCaching()) {
                rubyCode = executeCache(filename);
            } else {
                rubyCode = readRubyScript(filename);
            }
            controller = manager.eval(getScriptEngineName(), "(java)", 1, 1, rubyCode);
            request.setAttribute("__manager", manager);
            request.setAttribute(CACHED_RUBY_CONTROLLER_ATTRIBUTE, controller);

        } catch (IOException e) {
            log.error(e);
            System.err.println(e.getMessage());
        } catch (BSFException bfe) {
            setManager(null);
            bfe.printStackTrace();
            log.error(bfe);
            Throwable targetException = bfe.getTargetException();
            throw new BSFException(bfe.getReason(), targetException.getMessage(), targetException);
        }
        return controller;
    }

    /**
     * Invoke Ruby Controller Method.
     * 
     * @param rubyController
     * @param methodName
     * @param args
     * @return
     * @throws BSFException
     */
    protected Object invokeRubyControllerMethod(Object rubyController, String methodName, Object[] args)
            throws BSFException {
        if (rubyController == null) {
            return null;
        }
        return getEngine().call(rubyController, methodName, args);
    }

    /**
     * Invoke the Ruby Controller Method.
     * 
     * @param request
     * @param methodName
     * @param args
     * @return
     * @throws BSFException
     */
    protected Object invokeRubyControllerMethod(HttpServletRequest request, String methodName, Object[] args)
            throws BSFException {
        return invokeRubyControllerMethod(getRubyController(request), methodName, args);
    }

    /**
     * Form Backing Object.
     * You must create the command without commandClass being set - either 
     * set commandClass or (in a form controller) override formBackingObject.
     */
    protected Object formBackingObject(HttpServletRequest request) throws Exception {

        this.scriptStartTime = System.currentTimeMillis();
        Object model = null;
        Object mapOrController = getRubyController(request);

        if (mapOrController != null) {
            if (Map.class.isAssignableFrom(mapOrController.getClass())) {
                model = mapOrController;
            } else {
                try {
                    // Script errors and other BSF type errors are normally thrown
                    // here.  Log the error and print stacktrace
                    model = invokeRubyControllerMethod(mapOrController, "getModel", new Object[] { request });
                } catch (BSFException be) {
                    be.printStackTrace();
                    log.error("Error: executing formBackingObject() script 'getModel', url="
                            + request.getRequestURL());
                    log.error(be);
                    // Stop normal execution
                    // TODO: redirect to error page?  have some kind of backout routine?
                    throw be;
                } // End - try - catch bsfmanager exceptions
            }
        }
        long curEndTime = System.currentTimeMillis();
        long diff = curEndTime - scriptStartTime;
        log.debug("formBackingObject(): processing=" + diff + " ms");

        if (model == null) {
            TreeMap locTreeMap = new TreeMap();
            this.setRubyCommand(locTreeMap);
            return locTreeMap;
        }
        this.setRubyCommand(model);
        return model;
    }

    /**************************************************************************
     *  Setter and Getter Utility Methods
     **************************************************************************/
    protected void onInitializeManager() {
    }

    private void setEngine(JRubyEngine jRubyEngine) {
        mEngine = jRubyEngine;
    }

    public String getJspDir() {
        return mJspDir;
    }

    public void setJspDir(String jspDir) {
        mJspDir = jspDir;
    }

    public Log getLogger() {
        return log;
    }

    public void setLogger(Log logger) {
        log = logger;
    }

    public void setManager(BSFManager manager) {
        this.mManager = manager;
    }

    public JRubyEngine getEngine() {
        return mEngine;
    }

    public String getScriptEngineClass() {
        return mScriptEngineClass;
    }

    public void setScriptEngineClass(String scriptEngineClass) {
        mScriptEngineClass = scriptEngineClass;
    }

    public String getScriptEngineName() {
        return mScriptEngineName;
    }

    public void setScriptEngineName(String scriptEngineName) {
        mScriptEngineName = scriptEngineName;
    }

    public String getScriptExtension() {
        return mScriptExtension;
    }

    public void setScriptExtension(String scriptExtension) {
        mScriptExtension = scriptExtension;
    }

    public String getInitScript() {
        return mInitScript;
    }

    public void setInitScript(String initScript) {
        mInitScript = initScript;
    }

    /******************************************************
     * 
     *  Set the Data Access Object
     *  
     ******************************************************/
    public void setUserLinkDao(BotListUserLinkDAO dao) {
        this.userLinkDAO = dao;
    }

    public BotListUserLinkDAO getUserLinkDao() {
        return this.userLinkDAO;
    }

    /**
     * @return the postListingDAO
     */
    public BotListPostListingDAO getPostListingDao() {
        return postListingDAO;
    }

    /**
     * @param postListingDAO the postListingDAO to set
     */
    public void setPostListingDao(BotListPostListingDAO postListingDAO) {
        this.postListingDAO = postListingDAO;
    }

    /**
     * @return the springServletContext
     */
    public String getSpringServletContext() {
        return springServletContext;
    }

    /**
     * @param springServletContext the springServletContext to set
     */
    public void setSpringServletContext(String springServletContext) {
        this.springServletContext = springServletContext;
    }

    /******************************************************
     * 
     *  Set the File Upload Properties
     *  
     ******************************************************/

    /**
     * @return the fileUploadUtil
     */
    public BotListFileUploadType getFileUploadUtil() {
        return fileUploadUtil;
    }

    /**
     * @param fileUploadUtil the fileUploadUtil to set
     */
    public void setFileUploadUtil(BotListFileUploadType fileUploadUtil) {
        this.fileUploadUtil = fileUploadUtil;
    }

    public int parseFileUploadRequest(HttpServletRequest request) {

        int res = -1;
        if (this.getFileUploadUtil() != null) {
            try {
                res = this.fileUploadUtil.uploadFiles(request);
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        } else {
            log.error("ERR: File Upload Bean is NULL, should be set properly in the spring configuration");
        }
        return res;
    }

    /**
     * @return the rubyCommand
     */
    public Object getRubyCommand() {
        return rubyCommand;
    }

    /**
     * @param rubyCommand the rubyCommand to set
     */
    public void setRubyCommand(Object rubyCommand) {
        this.rubyCommand = rubyCommand;
    }

}