J2EE Filter that modifies a Stripes response for Velocity rendering

I combined Stripes and Velocity into a J2EE filter that came out to be extremely useful, so I could have Stripes/JSPs and have Velocity rendering with every request.

I love the toolbox.xml concept of velocity so I couldnt live without it, but wanted to use stripes for its amazing action controller functionality. Here is a filter which modifies the stripes response and then parses the response through the Velocity Template Engine.

 
package org.adk.java.viewfilter;
 
import org.adk.java.locale.LocaleTool;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.adk.java.store.StoreWebApplication;
import org.adk.java.store.WebSessionBean;
import org.adk.java.log.KLogger;
import org.adk.java.secure.SecureRedirectorActionBean;
import org.adk.java.stripes.ActionBeanUrlManager;
import org.adk.java.viewfilter.tools.BaseTool;
import org.adk.java.viewfilter.tools.BreadCrumbTool;
import org.adk.java.viewfilter.tools.PropertyTool;
import org.adk.java.viewfilter.tools.RequestTool;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.tools.view.XMLToolboxManager;
 
/********************************************************************************************
 * <p>
 * Our basic filter, all it does is take our WebSessionBean which is a wrapper around
 * the current session and passes it in the request, so that all jsps and other objects
 * can access the data, such as the current users shopping cart, his user info, and
 * other pertinent details to the session for this user.
 * <p>
 * In addition to basic session addition, we also take the content and
 * add in the velocity template engine so the session can be accessed from
 * velocity, and all content, after stripes is done with it is modified
 * with velocity.
 * <p>
 * We perform the following steps:
 * -------------------------------
 * <ol>
 * <li>  Load in the velocity toolbox so these can be exposed and used throughout
 *    the web app.
 * 
 * <li> Add the session to the queue and attributes
 *
 * <li>  Perform the filter chain and take the modified content in the form
 *    of a PrintWriter
 *
 * <li>  Modify this content with the Velocity Engine.
 *
 * <li>  Using the localeTool, we can then render all translations loaded in
 *    the appropriate strings.xml file.
 *</ol>
 *
 * @author Austin Kottke
 ********************************************************************************************/
public class ViewFilterEngine implements Filter {
 
    private StoreWebApplication webApp = StoreWebApplication.getInstance();
    private KLogger logger = KLogger.getLogger(this.toString());
    private VelocityContext velocityContext = new VelocityContext();
    private static ViewFilterEngine instance ;
    private FilterConfig filterConfig;
    private ServletContext servletContext;
 
    // Security redirector
    private SecureRedirectorActionBean secureRedir = new SecureRedirectorActionBean();
 
    // The actual path of the primary servlet. From here we can load
    // in all other files based on this path.
    private String currentPath;
    private String toolboxPath;
    private XMLToolboxManager toolboxManager;
    private Map toolBox;
 
    // A reference to the locale tool.
    private LocaleTool localeTool;
    private RequestTool requestTool;
    private BreadCrumbTool breadCrumbTool;
 
    private boolean initialized = false;
 
    // Action url manager
    private ActionBeanUrlManager actionUrlManager ;
 
    private void ViewFilterEngine(){
 
    }
 
    /**
     *
     * @return
     */
    public static ViewFilterEngine getInstance(){
        if (instance == null ){
            return new ViewFilterEngine();
        }
        return instance;
    }
 
 
 
    /**
     *
     * @param aConfig
     * @throws ServletException
     */
    public void init(FilterConfig aConfig) throws ServletException {
        this.filterConfig=aConfig;
        this.currentPath = filterConfig.getServletContext().getRealPath("");
        this.toolboxPath = currentPath + filterConfig.getInitParameter("ToolBoxPath");
        this.servletContext = filterConfig.getServletContext() ;
 
        logger.initFilters( currentPath );
        instance = this;
        try {
 
            toolboxManager = new XMLToolboxManager();
            Velocity.init(currentPath + "/WEB-INF/classes/velocity.properties");
            logger.debug("Loading velocity toolbox: " );
            toolboxManager.load( toolboxPath );
 
            // Initialize the velocity tools and add them to the
            // velocity context so they can be used in the app
            initVelocityTools();
 
        } catch (Exception ex) {
            Logger.getLogger(ViewFilterEngine.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
 
    /**
     * Propery loads and initializes all of the velocity tools so they can be used
     * throughout the application.
     */
    public void initVelocityTools()
    {
         // Retrieve the toolbox
         toolBox = toolboxManager.getToolbox(null);
         Set keys = toolBox.keySet();
         if( keys.size() <= 0 ) return;
 
         for( Object key : keys )
         {
            String k  = key.toString();
            Object obj = toolBox.get(k);
            logger.debug("Tool object ["+k+"] = " + obj);
 
            velocityContext.put(k, obj);
 
            try
            {
                if( obj.getClass().getSuperclass().toString().indexOf("BaseTool") >= 0  )
                {
                    logger.debug("Setting tool properties: " + obj);
                    BaseTool t = (BaseTool ) obj;
                    logger.debug("Property set!");
                    t.setServletFilePath(currentPath);
                    t.setContext(velocityContext);
                    t.setServletContext(servletContext);
                }
            } catch(Exception e){}
        }
         try
         {
 
             if( toolBox.get("breadcrumbTool") != null )
             breadCrumbTool = (BreadCrumbTool) toolBox.get("breadcrumbTool");
 
             if( toolBox.get("locale") != null)
             localeTool = (LocaleTool) toolBox.get("locale");
 
             if( toolBox.get("requestTool") != null)
             requestTool = (RequestTool) toolBox.get("requestTool");
 
         } catch( Exception e )
         {
             logger.error("Error - missing locale tool and / or request tool in the toolbox.xml");
         }
    }
 
    /**========================================================================
     * Here we add the session to the webApp singleton and add the user to the
     * session queue if he is not already there. We also modify the content
     * with VTL.
     * 
     * @param request
     * @param response
     * @param chain
     * @throws java.io.IOException
     * @throws javax.servlet.ServletException
     *========================================================================*/
 
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
    {
        HttpServletRequest req =  (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        PropertyTool p  = (PropertyTool) toolBox.get("propertyTool");
        /*********************************************************************
         * Initalize the tools that need the current request object.
         *********************************************************************/
 
        doRequestInitialization(p,req,resp);
        p.initProperties(req.getContextPath(),  req.getRequestURI() );
        if( ! initialized )
        {
                webApp.doInitialization();
                actionUrlManager = ActionBeanUrlManager.getActionUrlManager();
                initialized = true;
        }
 
        /*********************************************************************
         * Take the current session, and if it is not already added to
         * to the current request, add this to the current session.
         *********************************************************************/
        HttpSession s = addSessionToQueue( req, resp);
 
        /*********************************************************************
         * Check if the page should be redirected to Https.
         *********************************************************************/
       if( doHttpsCheck(p, req,resp)  )
           return;
 
        /*********************************************************************
         * Return the html stream.
         *********************************************************************/
        CharArrayWriter caw = new CharArrayWriter();
 
        logger.debug("Retrieving html stream... ["+req.getRequestURI()+"]");
 
        try
        {
 
         PrintWriter out = getHtmlStream(chain, req, resp, caw );
 
        /*********************************************************************
         * render the html stream with velocity and persist the session
         * so velocity can access this.
         *********************************************************************/
 
        velocityContext.put( "velocitySession", s );
 
        out.write(  renderVelocity( caw.toString() ).toString() );
        } catch(Exception e )
        {
            logger.error("Error: " + e.toString() );
        }
 
    }
 
    /***
     * Initializes the tools and the property tool for requests that need the
     * information. It also initializes all of the tools that are instances
     * of base tools, including setting the current request and setting the
     * current locale for the localeTool.
     * 
     * @param p
     * @param request
     * @param response
     */
    public void doRequestInitialization(PropertyTool p, HttpServletRequest request, HttpServletResponse response)
    {
        try
        {
            breadCrumbTool.setServletContextPath( request.getContextPath() );
            String curLocale = request.getParameter("locale");
            if( curLocale != null ) {
                Locale l = null;
                String lang = curLocale.split("_")[0];
                if( curLocale.indexOf("_") >= 0)
                {
                    String country = curLocale.split("_")[1];
                    l = new Locale(lang, country);
                }
                else
                {
                    if( lang.indexOf("en") >=0 )
                    l = new Locale(lang, "US");
                    else
                    l = new Locale(lang,lang);
 
                }
                localeTool.setCurrentLocale( l );
            }
 
 
 
            Collection c = toolBox.values();
            Iterator i = c.iterator();
            while( i.hasNext() )
            {
                try
                {
                     Object o = i.next();
 
                     if( o.getClass().getSuperclass().toString().indexOf("BaseTool") >= 0  )
                     {
                         //logger.debug("Base tool! " + o);
                         BaseTool t = (BaseTool) o;
                         t.setRequest(request);
                     }
 
                }catch(Exception e ){}
            }
 
 
 
        } catch(Exception e ){
            logger.error("Did you not load in the propertyTool ? - Check your velocity toolbox.xml file.");
            logger.error("Error: you might be missing a /global.properties in the site root! " + e.toString());
        }
    }
    /***
     * Modifys the request if this page should be secured or not. If it is
     * supposed to be secured, based on the global properties 'securePage' we
     * then forward to Https. If it is not supposed to be secured, then
     * we redirect to a regular url.
     *
     * @param p The property tool for lookup of the securePage property.
     * @param request The current request
     * @param response The response request.
     * @return
     */
 
    public boolean doHttpsCheck(PropertyTool p, HttpServletRequest request, HttpServletResponse response)
    {
 
        logger.debug("Secure page: " + p.get("securePage") );
 
        try
        {
            if( p.get("securePage").equals("1") && request.isSecure() == false)
            {
                logger.debug("This page should be secured...");
                String securedUrl = secureRedir.returnSecureUrl(request, request.getRequestURI() );
                logger.debug("Secure url: "  + securedUrl );
                try {
                    response.sendRedirect(securedUrl);
                    return true;
                } catch (Exception ex) {
                    logger.error("Error redirecting to url...");
                    return false;
                }
            }
 
            // Do check for if this should not be a secure page, but is in https mode.
 
 
            if( p.get("securePage").equals("0") && request.isSecure() == true)
            {
                logger.debug("This page should be NOT be secured!");
                 String regularUrl = secureRedir.returnRegularUrl(request, request.getRequestURI() );
                 try {
                    logger.debug("Redirecting to: " + regularUrl);
                    response.sendRedirect(regularUrl);
                    return true;
                } catch (Exception ex) {
                    logger.error("Error redirecting to url... " +ex.toString());
                    return false;
                }
            }
        }
        catch(Exception e ){
            return false;
        }
        return false;
    }
 
    /*
     *
     * Adds the session bean to our web app session queue, so this can be
     * referred to throughout the application
     *
     * @param request
     * @param response
     * @return Returns the HttpSession instance
     **/
 
    public HttpSession addSessionToQueue(HttpServletRequest request, HttpServletResponse response )
    {
        HttpSession currentSession = (HttpSession) request.getSession();
        WebSessionBean cartSession = null;
 
        logger.debug("Total sessions: " + webApp.getActiveSessions());
 
        boolean isSessioninQueue = webApp.isSessionInQueue(currentSession.getId() );
 
        if( ! isSessioninQueue ) {
            cartSession = new WebSessionBean ( currentSession );
            webApp.addSession( cartSession );
        }
 
        if( isSessioninQueue ) cartSession = (WebSessionBean) webApp.getSession(currentSession.getId());
 
        // on the request for this particular user, make sure the session is included
        // with the shopping info, etc.
        request.setAttribute( "sessionBean", cartSession );
 
        return cartSession;
 
    }
 
 
 
    /**
     * Returns a PrintWriter with the current Html stream, after it has gone
     * through all redirects and filters.
     *
     * We do this by making a wrapper around the current html response and then
     * writing the current content after the  filters have been performed.
     * Once we have a writer we can then modify the response with whatever is
     * needed. Currently we modify the current response with the
     * velocity template engine, so we can also use VTL in a jsp or html.
     *
     * @param chain
     * @param request
     * @param response
     * @param writer
     * @return A PrintWriter with the modified html content.
     **/
 
    public PrintWriter getHtmlStream(FilterChain chain, HttpServletRequest request, HttpServletResponse response, CharArrayWriter writer ){
        /****************************************************************
         * Add in our custom response wrapper, so all filters and content
         * are written to, then we can add in our own content once we
         * have the outputted content in string form.
         ****************************************************************/
        PrintWriter out = null;
 
        try
        {
            out = response.getWriter();
            CharRequestWrapper reqWrapper = new CharRequestWrapper( request, request.getRequestURI(), request.getRequestURL() );
            CharResponseWrapper respWrapper = new CharResponseWrapper( response );
 
            /****************************************************************
             * If what was requested was an html page, then we need to
             * still retrieve the content based on the request, since we use
             * jsp as our presentation technology we still want to serve
             * up the content based on an html string.
             ****************************************************************/
 
            chain.doFilter(reqWrapper, respWrapper);
 
            /****************************************************************
             * If we have a 404, then we forward to the error pages...
             ****************************************************************/
            boolean sendErrorPage = false;
            if( respWrapper.getErrorCode() == HttpServletResponse.SC_NOT_FOUND ) sendErrorPage = true;
            if( respWrapper.getErrorCode() == HttpServletResponse.SC_INTERNAL_SERVER_ERROR ) sendErrorPage = true;
 
            if( sendErrorPage ) respWrapper.sendRedirect(reqWrapper.getContextPath() + "/engine/error/error.jsp");
 
 
            writer.write( respWrapper.getContent());
 
 
        } catch (Throwable ex) {
            Logger.getLogger(ViewFilterEngine.class.getName()).log(Level.SEVERE, null, ex);
        } 
 
        return out;
    }
 
    /**
     *
     * @param htmlContent
     * @return
     */
    public CharArrayWriter renderVelocity(String htmlContent)
    {
 
        CharArrayWriter outputVelocity = new CharArrayWriter();
        try {
 
 
            Velocity.evaluate(velocityContext, outputVelocity, "log", htmlContent);
        } catch (Throwable ex) {
            Logger.getLogger(ViewFilterEngine.class.getName()).log(Level.SEVERE, null, ex);
        }
 
        return outputVelocity;
    }
 
    /**
     *
     * @return
     */
    public XMLToolboxManager getToolboxManager() {
        return toolboxManager;
    }
 
    /**
     *
     * @param t
     */
    public void setToolboxManager(XMLToolboxManager t) {
         toolboxManager = t;
    }
 
    /**
     *
     */
    public void destroy() {
        webApp.clearSessions();
    }
 
    /**
     *
     * @return
     */
    public Map getToolBox() {
        return toolBox;
    }
 
    /**
     *
     * @param toolBox
     */
    public void setToolBox(Map toolBox) {
        this.toolBox = toolBox;
    }
 
 
}

A bread crumb tool, for creating breadcrumbs from a site map:

 
package org.adk.java.viewfilter.tools;
 
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.adk.java.log.KLogger;
import org.adk.java.web.sitemap.SiteDirectory;
import org.adk.java.web.sitemap.SiteMapBuilder;
import org.adk.java.web.sitemap.SitePage;
import org.jdom.Attribute;
 
/***===============================================================================
 * The BreadCrumbTool generates a tree structure based on a site map xml file
 * located in the WEB-INF directory.
 * <p>
 * The way we do this, is through first using the {@link SiteMapBuilder} class which
 * generates a tree structure full of SiteDirectory and SitePage classes including
 * sub directories.
 * <p>
 * Then using the {@link SiteMapBuilder} class, we can retrieve the entire structure
 * in a TreeMap.
 * <p>
 * We then use velocity to create the breadcrumb as follows:<br>
 * =========================================================
 * <p>
 *   #set( $requestUri =  "$requestTool.getRequest().getRequestURI()" )
 *   #set ( $pageTitle = "")
 *   #set ( $dirs = $breadcrumbTool.getDirectory( $requestUri  ) )
 *
 *    <span  style="font-size: 10px; font-family: verdana">
 *    #foreach ($dir in $dirs )
 *       #set ( $isDisplayed = "$!dir.isDisplayed()")
 *
 *       <a href="$dir.getFinalTreeUrl()">$locale.get( "$dir.getId()" )</a>
 *    #end
 *
 *<p>
 * What this does:<br>
 * ===============
 *<p>
 * It retrieves the entire directory object and puts it in $dirs.
 *<p>
 * We then loop through each directory and generate a link and we pull the
 * title of the directory using the $locale tool.
 *<br>
 * As a note - this class needs major refactoring. I recommend another
 * version that handles dynamic directory creation.
 *
 * @see SiteMapBuilder
 * 
 * @version 0.1
 * @author Austin Kottke
 * ===============================================================================*/
 
public class BreadCrumbTool extends BaseTool {
 
    private KLogger logger = KLogger.getLogger(this.toString());
    private SiteMapBuilder primarySiteMap  = new SiteMapBuilder();
    private String siteMapXmlPath;
 
    /**
     *
     */
    public void BreadCrumbTool(){
    }
 
    /**
     *
     * @param params
     */
    public void configure(Map params)
    {
        logger.debug("configure() called.");
        siteMapXmlPath = params.get("siteMapXmlPath").toString() ;
 
    }
 
    /**
     *
     * @param requestUri
     */
    public void get( String requestUri ){
        if( ! primarySiteMap.isInitialized() ) {
            primarySiteMap.buildSiteMap( this.getServletFilePath() + siteMapXmlPath );
        }
 
        generateBreadcrumb(requestUri);
    }
 
    /**===============================================================================
     * This is the primary entry point to retrieve the breadcrumb directory.
     *
     * It returns an arraylist based on where the page request is coming
     * from.
     *
     * @param requestUri
     * @return
     *===============================================================================*/
 
    public ArrayList getDirectory( String requestUri ){
        logger.debug("getDirectory() called.");
 
        if( ! primarySiteMap.isInitialized() )
            primarySiteMap.buildSiteMap( this.getServletFilePath() + siteMapXmlPath );
 
        logger.debug("Servlet context name: " + getServletContext().getServletContextName() );
 
        String uriContextPath = this.getServletContextPath();
        String requestPath = requestUri.replace(uriContextPath, "");
        ArrayList b = generateBreadcrumb(requestPath);
 
        // Build urls...
        String url = "";
 
        for( int i=0; i<b.size(); i++ )
        {
 
            try
            {
                SiteDirectory dir = (SiteDirectory) b.get(i);
                if( dir.getDynamicPageId() != null) continue;
 
 
                url += dir.getUrl();
                SitePage p = null;
 
                if( dir.getPages().size() >= 1 )
                {
                    String pageKey = dir.getDefaultPage();
                    p = dir.getPages().get(pageKey);
                    url += "/"+p.getUrl();
                }
 
                dir.setFinalTreeUrl(url.replace(".jsp", ".html"));
 
                if( dir.getPages().size() >= 1) {
 
                    url = url.replace("/"+p.getUrl(), "");
                   // logger.debug("Modified url: " + url );
                }
 
 
            } catch (Exception e ) { }
 
        }
 
        List newbreadcrumb = (List) getRequest().getAttribute("additionalbreadcrumb");
        if( newbreadcrumb != null )
        {
            for( int j=0; j<newbreadcrumb.size(); j++)
                b.add( newbreadcrumb.get(j));
        }
 
        return b;
    }
 
    /**===============================================================================
     * Let us say that you are adding in dynamic directories and do not want to
     * use the site map, you can add urls at request time so that the bread crumb
     * shows up properly.
     *
     * This is recommended to use in case of very dynamic cases, but otherwise you
     * should configure the SiteMap.xml file.
     *
     * @param localeId The current page locale id to be displayed to the end user
     * @param urlPath the Path to the service or url being generated.
     *===============================================================================*/
 
    public void addDirectory(String localeId, String urlPath){
        logger.debug("addUrl() called.");
 
        List breadcrumb = getAdditionalBreadcrumb();
 
        SiteDirectory dir = new SiteDirectory();
        dir.setId(localeId);
        dir.setFinalTreeUrl( urlPath );
        dir.setDynamicPageId( "dynamic" );
        breadcrumb.add(dir);
 
        getRequest().setAttribute("additionalbreadcrumb", breadcrumb);
    }
 
    /***
     * Adds a page using the localeId as the name, this is primarily
     * used for static pages that do not change.
     * 
     * @param localeId
     */
 
    public void addPage(String localeId ){
        List breadcrumb = getAdditionalBreadcrumb();
 
        SitePage p = new SitePage();
        p.setId( localeId );
 
        breadcrumb.add(p);
    }
 
    /***
     * Used for dynamic pages that change the title based on a specific
     * attribute passed.
     * 
     * @param localeId
     * @param dynamicPageTitleAttribute
     */
 
     public void addPage(String localeId, String dynamicPageTitleAttribute ){
        List breadcrumb = getAdditionalBreadcrumb();
 
        SitePage p = new SitePage();
        p.setId( localeId );
        p.setDynamicPageTitle( dynamicPageTitleAttribute );
 
        breadcrumb.add(p);
    }
    /***
     * Stores a bread crumb in the request attributes. Used primarily for
     * creating directories that are marked as dynamic. We then can
     * add on additional directories and subdirectories at will. Particularly
     * useful for pages that have very dynamic page structures.
     * 
     * @return List A breadcrumb list that will be added on to the end of the breadcrumb
     */
    public List getAdditionalBreadcrumb(){
        List breadcrumb = null ;
        try {
            if( getRequest().getAttribute("additionalbreadcrumb") != null )
                breadcrumb = (List)getRequest().getAttribute("additionalbreadcrumb");
            else
                breadcrumb = new ArrayList();
 
        } catch(Exception e ){}
        return breadcrumb;
    }
 
 
    /**
     *
     * @return
     */
    public SitePage getPage(){
 
        return null;
    }
 
    /****************************************************************************
     * Creates the bread crumb.
     * 
     * @param requestUri
     * @return
     ****************************************************************************/
    public ArrayList generateBreadcrumb(String requestUri){
 
        logger.debug("generateBreadcrumb for: " + requestUri);
        TreeMap<String,SiteDirectory>  map = this.primarySiteMap.getSiteMap();
        Set directorySet = map.entrySet();
        String reqUri = requestUri;
 
        // Check if the action is in the request uri, which needs to be taken
        // out as this is not a top level directory
        if( requestUri.indexOf("action") >= 0 )
            reqUri = requestUri.split("/action")[1];
 
        String [] directories = reqUri.split("/");
 
        SiteDirectory parentDir = null;
        ArrayList breadcrumb = new ArrayList();
 
        breadcrumb.add(map.get("dir_home"));
 
        logger.debug("Directory size: " + directorySet.size());
 
        for( int i=0; i<directorySet.size(); i++ )  {
            String key = directorySet.toArray()[i].toString().split("=")[0];
            SiteDirectory d = map.get(key);
            String topLeveldirectoryName = d.getUrl().replace("/", "");
 
            for( int k=0; k<directories.length; k++ )
            {
                String directoryToFind = directories[1].toString().trim();
 
                if( directoryToFind.length() > 1 ) {
 
                    if( topLeveldirectoryName.equals(directoryToFind))
                    {
                        logger.debug("Matching top level directory: " + topLeveldirectoryName );
                        getFinalDirectoryStructure(d, directories, breadcrumb, 0);
                        return breadcrumb;
                    }
                }
            }
 
        }
 
 
 
        return breadcrumb;
    }
 
    /***===============================================================================
     * Generates the final directory used for the breadcrumb, this can include
     * any number of directories and a single page to send the url to.
     * 
     * @param dir 
     * @param requestPath
     * @param intDirIndex
     * @param breadcrumb
     * ===============================================================================*/
 
    public void getFinalDirectoryStructure(SiteDirectory dir, String[] requestPath, ArrayList breadcrumb, int intDirIndex)
    {
        breadcrumb.add( dir );
 
        for( int k=0; k<requestPath.length; k++ )
        {
            String pathToFind = requestPath[k].toString().trim();
 
            int dirLength = dir.getSubcategories().size();
            int pageLength = dir.getPages().size();
 
            Set pages = dir.getPages().entrySet();
            Set directories = dir.getSubcategories().entrySet();
 
            //logger.debug("Current dir index: " + dir.getId() );
            // Check directories and then check pages.
            for( int i=0; i<dirLength; i++ )
            {
                String dirKey = directories.toArray()[i].toString().split("=")[0];
                SiteDirectory d = dir.getSubcategories().get(dirKey);
 
                if( pathToFind.equals( d.getUrl().replace("/", "") ) )  {
                    logger.debug("Directory: " + d.getDynamicPageId() );
                    getFinalDirectoryStructure(d, requestPath, breadcrumb, i);
                    return;
                }
 
            }
 
            // Now go through the last few pages...
            for( int m=0; m<pageLength; m++ )
            {
                String pageKey = pages.toArray()[m].toString().split("=")[0];
                SitePage p =  dir.getPages().get(pageKey);
 
                String url = p.getUrl();
 
                if( url.equals( requestPath[k]) || url.equals( requestPath[k].replace(".html", ".jsp") ) ){
                    logger.debug("Found a page match: ["+p.getUrl()+"] in directory: ["+dir.getId()+"]");
 
                    breadcrumb.add(p);
                    return;
                }
            }
        }
 
    }
 
    /****************************************************************************
     * Builds a dynamic directory based on the requestUri. A store has
     * many categories and sub categories which are generated dynamically
     * and are not in the site map.
     * <p>
     * As a result of this, it is impossible if not tedious to create an entire
     * directory structure based on a possible directory structure that has
     * many infinite possibilities of shopping options.
     *<p>
     * So to generate the dynamic directory we simply build the structure based
     * on the uri and let the action handler correctly go to the correct directory
     * structure.
     *<p>
     * Example of a Directory:
     * ------------------------
     * <p>
     *
     * Home > Store > Books > Fiction > New and Used > Foundation Series
     * <p>
     * As you can see the directory structure is very long and the categories
     * are built within Books to Fiction to New and Used.
     *
     * @deprecated Not used, use the addDirectory and addPage methods as these
     *             are less messy.
     ****************************************************************************/
 
    public void addDynamicDirectoryToBreadCrumb( ArrayList breadcrumb, SiteDirectory d, Object[] requestDirectories)
    {
        logger.debug("addDynamicDirectoryToBreadCrumb() called: " + d );
 
        // ==============================================================================
        // We need to first find what the directory name is and in the current path array
        // contains the requestUri. We find the index into this path array so we can then
        // parse the remaining part of the array and build a dynamic directory with all
        // path info taken into account.
        // ==============================================================================
        String currentDirPathName = d.getUrl().split("/")[1];
        int currentDirectoryIndex = 0;
        for( int k=0; k<requestDirectories.length; k++ ) {
            if( requestDirectories[k].toString().equals( currentDirPathName ) ) currentDirectoryIndex=k;
        }
        // ==============================================================================
        // Now we have the current directory index, we now need to build the correct
        // dynamic directory.
        //
        // The remaining directory names correspond to localeIds and refer to
        // an action service.
        // ==============================================================================
        Attribute e = d.getAttributeById("dynamicUrl");
        String previousUrl = d.getAttributeById("previousUrl").getValue();
 
        SiteDirectory dynamicDir = new SiteDirectory();
        if( previousUrl != null )
        dynamicDir.setFinalTreeUrl( getServletContextPath() + previousUrl + requestDirectories[currentDirectoryIndex+1] );
        dynamicDir.setId( requestDirectories[currentDirectoryIndex+1].toString() );
        dynamicDir.setDynamicPageId("dynamic");
        breadcrumb.add( dynamicDir );
 
        for( int i=currentDirectoryIndex+2; i<requestDirectories.length; i++)
        {
            SiteDirectory newDynamicDir = new SiteDirectory();
 
            if( requestDirectories[i].toString().indexOf(".html") <= -1 )
            {
                newDynamicDir.setId( requestDirectories[i].toString() );
                newDynamicDir.setFinalTreeUrl( getServletContextPath() + e.getValue() + "/" + requestDirectories[i] );
                newDynamicDir.setDynamicPageId("dynamic");
                logger.debug("Directories: " +requestDirectories[i] );
                breadcrumb.add( newDynamicDir );
            }
 
            // This is a page, add a site page to the breadcrumb.
            if( requestDirectories[i].toString().indexOf(".html") >= 0 )
            {
                SitePage p = new SitePage();
                p.setId(requestDirectories[i].toString().split(".html")[0]);
                breadcrumb.add(p);
            }
 
        }
 
        logger.debug("Index: " + currentDirectoryIndex );
    }
}

If anyone wants the source let me know =)

Austin

JAR like use in Flash? Use mxmlc -load-externs

For the last few years I’ve been developing in flex. One of the very nice features is RSLs to save on file space, but no matter how much optimization work you end up with a huge SWF right? Which is why developers end up using flash for web development.

How many times have you wanted to use the RPC components but ended up scrapping them because they bloat your SWFs? I’ve had to resort to custom AS3 soap libraries, or even have had to hack an existing AS3 web service implementation because it wasn’t compatible with ALL SOAP specs, as in each server has little nuances which can potentially break an AS3 implementation. The joys =)

Ive been doing a lot of flash dev recently, and one option which I think a lot of flash developers miss over is the fact that they compile assets within assets that are shared across multiple SWF files. E.g. you have the Pure MVC framework or cairngorm classes compiled into 5 swfs, when really, all you need to load in a single shell SWF and all future SWF files can reference the classes normally WITHOUT compiling in the entire framework.

So how do you do this? How do you make your other SWFs not compile the entire framework such as ui components and buttons and flash?

Well, instead of doing all the runtime instantiation and non strong typing – you know like var myClass:* – which is prone to errors, use mxmlc -load-externs.

So the steps are as follows:

  1. Create main loader SWF that uses the majority of the framework, ui components etc.
  2. Generate link-report XML using ant:  <mxmlc … link-report=”shell-link-report.xml”
  3. In your ant build file, when compiling all sub-module swfs, compile all module swfs using load-externs=shell-link-report.xml.

By doing these very simple steps you can reduce legacy swf projects by half sometimes simply because when using the Flash IDE it does not natively create this for you.

Tutorial: Build Twitter with Groovy Grails in 90 Minutes

I found an awesome video that came out last month from one of the Spring Source engineers on building an entire application in Grails.

I think if anything this video shows the true simplicity of grails. It outlines security, ajax, authentication, jdbc, validation and how fast it is to develop with grails. You see how robust and quick to develop a data-base driven twitter clone.

Grails – Making flex/java developers happy

I come from a myriad of development backgrounds, actually started doing C/8086 assembly back in the day. Went into playstation development, and then moved onto flash, qt c++, win 32, java, and ajax.

I developed heavily in java for the last few years and stumbled upon rails – couldn’t stand the syntax although I loved the power and the scaffolding. I then discovered grails and man this is like a new world. The power of ruby on rails but in a java-like syntax.

The key problems I see in web development are:

1. Properly planning the development project
2. Allocating enough time to do the project without shortcutting it
3. Implementing the project in the time allotted
4. Getting management to work with the clients to properly allocate enough time/money to spend on the lifecycle without shortcutting the features

I think projects that are heavily ajax/html or flex projects can be GREATLY simplified by using grails as it writes a lot of the code for you and can really make some of the back-end development which we never want to do a breeze and very integrated.

The problem with writing entity beans in java is that the annotations and amount of code written pales in comparison to a grails entity bean and how simple it is to add validation, etc.

I plan to do some more examples as I get more and more familiar with the grails code base.

Check it out: http://www.grails.org/