Austin Kottke's Code Site

Thoughts about Architecture – Java, C/C++, JS, Objective-C, Swift, Groovy, Grails, (RIP Flash)

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

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.

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.

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/

Well, Ive used this logger a million times and thought it would be useful to let others take a look. The main thing this class supports is FILTERING, which is something that a lot of log classes do not have which I find problematic when dealing with a very large project or want to debug on a live server.

Check it out:

package com.adk.util {
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.utils.*;

/**=======================================================================
*
* Generic logger that provides filtering so you can specify exactly
* classes that should be traced out. You do this by adding in a filter
* for a specific class using the logger xml file.
*
* NOTE:
*
* Load in the logger xml file by listening for the LOADED event
* before dispatching and using logging statements.
*
* @example
*
*         var logger:FlashLogger = FlashLogger.getLogger( this, “path.to/logger.xml” );
*             logger.addEventListener( FlashLogger.LOADED, onLoggerLoad );
*
*        public function onLoggerLoad( event:Event ):void
*        {
*            var logger:FlashLogger = FlashLogger.getLogger( this );
*            logger.debug(“Test”, “param2”);
*        }
*
*
* Specify log filters in logger.xml:
*
* <?xml version=”1.0″ ?>
* <Logger logLevel=”DEBUG”>
* <filters>com.adobe, com.adk.util</filters>
* </Logger>
*
* You can add as many filters you want and specify the log level.
*
* @author Austin Kottke
*=======================================================================*/

public class FlashLogger extends EventDispatcher {

/*============================================================================
* Events and log types.
*============================================================================*/
public static var NONE : uint = 0;
public static var INFO : uint = 1;
public static var DEBUG : uint = 2;
public static var WARN : uint = 3;
public static var ERROR : uint = 4;
public static var FATAL : uint = 5;

/*============================================================================
* Variables
*============================================================================*/

public static var LOG_TYPES : Array;
private static var logLevel : int;
private static var filters : Array = new Array();
private static var filtersLoaded : Boolean = false;
private static var created : Boolean = false;
public var className : String ;

/*============================================================================
* EVENTS
*============================================================================*/
public static var LOADED : String = “xmlLoaded”;

/**********************************************************************************
* Adds in a new filter
*
* @param aString Adds in a new filter for the logger.
**********************************************************************************/

public function addFilter( aString : String ) {
FlashLogger.filters.push(aString);
trace(“FlashLogger.addFilter(” + aString + “)”);
}

/**********************************************************************************
* Checks if the class is in the filter.
*
* @return true or false if the class is in the filter or if there are no filters.
**********************************************************************************/

private function isClassInFilter( aClassName : String ) : Boolean {
for ( var i : Number = 0;i < filters.length;i++ )
if ( aClassName.indexOf(filters[i]) >= 0 ) return true;

if ( filters.length == 0 ) return true;

return false;
}

/**********************************************************************************
*  Constructor
**********************************************************************************/

public function FlashLogger( aClassName : String, loggerXMLPath : String ) {
className = aClassName;
if ( !created ) {
FlashLogger.LOG_TYPES = new Array(“NONE”, “INFO”, “DEBUG”, “WARN”, “ERROR”, “FATAL”);
setLogLevel(DEBUG);
created = true;
loadLogXml(loggerXMLPath);
}
}

/**********************************************************************************
*  Sets the log level
*
* @param aLogLevel the log level for debugging purposes
**********************************************************************************/

public function setLogLevel( aLogLevel : uint ) {
logLevel = aLogLevel;
trace(“FlashLogger.setLogLevel(” + FlashLogger.LOG_TYPES[aLogLevel] + “)”);
}

public function getLogLevelByName( aLogLevelName : String ) : Number {
for ( var i : Number = 0;i < FlashLogger.LOG_TYPES.length;i++ ) {
var curLevel : String = FlashLogger.LOG_TYPES[i].toLowerCase();
if ( curLevel.indexOf(aLogLevelName.toLowerCase()) >= 0 )
return i;
}
return 1;
}

/**********************************************************************************
*  Retrieves the logger
*
* @param className The class name for the logger to be debugged correctly.
**********************************************************************************/

public static function getLogger( obj : Object, loggerXMLPath : String = “logger.xml”) : FlashLogger {
var className : String = getQualifiedClassName(obj).split(“::”).join(“.”);
var logger : FlashLogger = new FlashLogger(className, loggerXMLPath);
return logger;
}

/**********************************************************************************
* Load in the xml configuration for the flash logger. By default this
* is logger.xml
**********************************************************************************/

private function loadLogXml( aPath : String = “” ) : void {
var url : URLLoader = new URLLoader();
url.addEventListener(Event.COMPLETE, loadLogFilters);
url.load(new URLRequest(aPath));
}

/**********************************************************************************
* Sets the filters based on the xml file.
**********************************************************************************/

private function loadLogFilters( event : Event = null ) : void {
try {
var loggerConfig : XML = new XML(event.target.data) ;
var filters : Array = loggerConfig.filters.split(” “).join(“”).split(“,”);
var logLevel : String = loggerConfig.@logLevel;
setLogLevel(getLogLevelByName(logLevel));

for ( var i : Number = 0;i < filters.length;i++ ) {
addFilter(filters[i]);
}
filtersLoaded = true;
this.dispatchEvent(new Event(LOADED));
}
catch (e : Error ) {
trace(“FlashLogger [ERROR] error loading filters: ” + e.message);
}
}

/**********************************************************************************
*  Ouputs a debug message.
*
* @param message The message to output
**********************************************************************************/

public function debug(…paramaters) : void {
if ( logLevel == NONE ) return;

if ( isClassInFilter(className) ) outputLogging(DEBUG, paramaters.join(“, “));
}

/**********************************************************************************
*  Ouputs an info message.
*
* @param message The message to output
**********************************************************************************/

public function info(…paramaters) : void {
if ( logLevel == NONE ) return;

if ( isClassInFilter(className) ) outputLogging(INFO, paramaters.join(“, “));
}

/**********************************************************************************
*  Ouputs an error message.
*
* @param message The message to output
**********************************************************************************/

public function error(…paramaters) : void {
if ( logLevel == NONE ) return;

if ( isClassInFilter(className) ) outputLogging(ERROR, paramaters.join(“, “));
}

/**********************************************************************************
*  Core trace output
*
* @param logLevel The log level to output
* @param message The message to display
**********************************************************************************/

private function outputLogging( logLevel : int, message : String ) {
if ( !filtersLoaded ) {
trace(“FlashLogger [ERROR] – please load in logger.xml before using this logger.”);
return;
}
var my_date : Date = new Date();
var hours:String = my_date.hours.toString();
var minutes:String = my_date.minutes.toString();
var seconds:String = my_date.seconds.toString();

if( my_date.hours < 10 ) hours = “0” + hours;
if( my_date.minutes < 10 ) minutes = “0” + minutes;
if( my_date.seconds < 10 ) seconds = “0” + seconds;

var time : String = hours + “:” + minutes + “:” + seconds;
trace(“[” + time + “] [” + FlashLogger.LOG_TYPES[logLevel] + “] (” + className + “) ” + message);
}
}
}

So I’ve been doing a lot of java/ajax development recently, and having to compare frameworks was one of the main things I’ve had to do. I used JSF 1.2 actually due to legacy server issues, not 2.0 (which I hear is a lot better) but comparing it with Stripes/Wicket I must say that:

1. JSF has a LOT of issues integrating into existing browsers, 2.0 is probably a bit better but sometimes the pain of having to have ALL tags written for you is VERY troublesome. I recently had to use a rich faces fileupload component and the integration on some browsers has major issues. While it is good to have a very tightly integrated ajax framework it sometimes is painful when trying to integrate in something like the YUI components.

2. Stripes/Wicket are good because they leave a lot of the ajax development to yourself and provide a VERY awesome hands on approach where you can actually write your own code with minimal development time. I find this actually was easier on some other projects.

So in a nut shell… Id stick with Stripes/Wicket for a java serverside application as opposed to straight JSF as it is sometimes painful when dealing with all the pre-written javascript components.

I guess this brings up a philosophy:

1. A java server side framework should NEVER be so tightly binded to the front-end UI logic WHEN you are depending on browser support. Or provide some alternative to use the latest prototype/ajax libraries.

2. If it is tightly binded, there should be a very easy way to extend it and use it smoothly. JSF 1.2 was a nightmare !!! =)

For some reason, stripes/velocity and wicket still remain my top java web frameworks. Oh and forget to mention, integration with other flash/flex projects is a lot smoother without using JSF due to the freedom it gives you.

My 2c =)

January 31st, 2010

runtime mxml

1 Comment, as2, as3, flex, by austin.

Ok guys, for one I have to give kudos to flash builder 4 – I think it rocks!

But the one thing which I think would just be the cream of the crop is runtime mxml! I actually ported 60% of the functionality of the flex ui component framework to as2 and now im considering doing an as3 version.

I mean, I think that it would so useful to have runtime mxml, maybe no logic, but just the layout code, that’s all.

Does anyone concur?

January 24th, 2010

Crazy 3D C++ Open GL project

No Comments, 3d, C++, OpenGL, QT, by austin.

Hey guys, I’ve been working on a project for a few months to teach myself open gl and 3d mathematics and nvidia’s cg shader language. I think it came out ok, I learned a lot of technology about graphics, etc. It’s no where near the level of PS3 but it’s got some potential. Qt4 was used for the UI technology.

Features:

1. Bump mapping, spherical mapping, multi-texturing
2. Terrain generation
3. Matrix pallette skinning
4. Realtime lighting
5. Explosions, collision detection
6. MD2, POD model loading
7. Shadows

I posted some shots….

As a note, none of the models are mine, they are just used to demonstrate concepts. Two of the models are NVidia’s and Sony’s used in a book I found. I converted the models to a format I needed, POD. And used shaders to animate the models using matrix pallette skinning.

This is purely used for educational purposes and not to be a commercial project at all.

I tell you it’s been a while since I last posted, but one thing that I have been spending a lot of time is looking at web frameworks. I looked at wicket, web macro, jsp, velocity, and I have to say that the most awesome framework I can see is stripes.

I was first reading a book on hibernate – harnessing hibernate and in the end of the chapter it mentions about Stripes. One of the things I can’t stand is the auto-wiring of Java bean objects and a web form. It’s not a big deal with flex because you have amf, but in a web site it’s a pain.

So take stripes, I just take an entity bean and I can specify the bean on the page and stripes will auto-validate, and send errors if a field is not required. It’s pretty nice.

See below:

*/
@ValidateNestedProperties({
@Validate(field=”firstName”, required=true, minlength=3, maxlength=50),
@Validate(field=”lastName”, required=true, minlength=5, maxlength=50),
@Validate(field=”email”, required=true, minlength=5, converter=EmailTypeConverter.class ),
@Validate(field=”userName”, required=true, minlength=5 ),
@Validate(field=”password”, required=true, minlength=8 )
})
@Validate(encrypted=true)
public UserVO getUser() {
return this.beanContext.getUser();
}

Using annotations the validation occurs and its just a simple matter of inserting a field and binding directly:

Doing this will automatically validate the field in java.

Cool huh?

Well that’s one of the major things I like about it. Besides flex, it’s pretty smoothe. I like wicket, but for some reason I just cant get over writing all my UI logic in java…. I feel like im doing swing again!

Anyway, check out stripes – its pretty cool: www.stripesframework.org

As I look more and more at the industry trends, we see more and more handheld devices being developed, that’s a no brainer. More and more devices, such as the android, sidekick, blackberry and iphone have some form of a web browser. Just the other day I was walking outside and I saw atleast 4 young kids with sidekicks, a blackberry and a couple with an iphone.

Now when we look at trends we see that the majority of people browse with their own personal computers, but as the future progresses more and more people will be using their mobile devices to browse the web. Of the 4 different devices I mentioned NONE have the flash player, or are capable of viewing flash in a normal browsing environment.

Now, I’ve been developing flash applications for over 10 years, I started with flash 4. Used flex, and all variations with design patterns, coding standards, but the one thing that is becoming more difficult is convincing executives of the company to use a flash solution when all of the above devices competently show ajax and html just fine.

I know that the flash player is going to come to the android…. but let’s get real, is adobe taking a serious look at this? I mean its awesome to do flashlite for the various nokia devices and devices that can support this. But there needs to be major changes here because as the industry moves more and more mobile, ajax/html solutions are becoming a lot more appealing because these mobile devices render ajax/html without hickups.

Is there any official word on this, supporting these various devices that are mainstream such as the sidekick, blackberry, iphone and android ?

I dont mean to rehash an old subject, but there are a lot of projects in the works and if there is no flash player for these devices then it really limits the potential and thus getting the bosses to agree to a flash solution greatly decreases. After all they have the blackberries and iphones. I love flash, but really it comes down to getting flash everywhere. Flash won because of how ubiquitous it was, now… this is fading as the mobile arena becomes bigger and bigger.

Every kid on the block is getting a cell phone… Is this a major concern for adobe?