org.protorabbit.model.impl.DefaultEngine.java Source code

Java tutorial

Introduction

Here is the source code for org.protorabbit.model.impl.DefaultEngine.java

Source

/*
 * Protorabbit
 *
 * Copyright (c) 2009 Greg Murray (protorabbit.org)
 * 
 * Licensed under the MIT License:
 * 
 *  http://www.opensource.org/licenses/mit-license.php
 *
 */

package org.protorabbit.model.impl;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.protorabbit.Config;
import org.protorabbit.model.ICommand;
import org.protorabbit.model.IContext;
import org.protorabbit.model.IDocumentContext;
import org.protorabbit.model.IEngine;
import org.protorabbit.model.IParameter;
import org.protorabbit.model.ITemplate;
import org.protorabbit.profile.Episode;

/*
 *  DefaultEngine.java
 *  
 *  This class parses the template file and then calls the corresponding Command Handlers to process
 *  different portions of the page.
 * 
 */
public class DefaultEngine implements IEngine {

    public static String COMMAND_START = "<^";
    public static String COMMAND_END = "^>";

    private static Logger logger = null;

    static final Logger getLogger() {
        if (logger == null) {
            logger = Logger.getLogger("org.protrabbit");
        }
        return logger;
    }

    public synchronized void renderTemplate(String tid, IContext ctx, OutputStream out) {

        Config cfg = ctx.getConfig();

        ITemplate template = cfg.getTemplate(tid, ctx);
        ctx.setTemplate(template);
        ctx.setTemplateId(tid);
        renderTemplate(template, ctx, out);
    }

    /*
     * Assumes that the document content is set to properly work.
     */
    public static List<ICommand> getCommands(ITemplate template, IContext ctx) {
        if (template != null) {
            DocumentContext dc = getDocumentContext(template, ctx);
            if (dc != null) {
                StringBuffer buff = dc.getDocument();
                gatherCommands(buff, ctx, dc);
                return dc.getAllCommands();
            }
        }
        return null;
    }

    public static synchronized void renderTemplate(ITemplate template, IContext ctx, OutputStream out) {

        if (template != null) {
            DocumentContext dc = null;
            try {
                dc = getDocumentContext(template, ctx);
                // go through all the commands and build up the buffers
                // before
                if (dc.getBeforeCommands() != null) {
                    processCommands(ctx, dc.getBeforeCommands());
                }
                // default commands
                if (dc.getDefaultCommands() != null) {
                    processCommands(ctx, dc.getDefaultCommands());
                }
                // after commands
                if (dc.getAfterCommands() != null) {
                    processCommands(ctx, dc.getAfterCommands());
                }
                List<ICommand> cmds = dc.getAllCommands();
                if (cmds == null || (cmds != null && cmds.size() == 0)) {
                    StringBuffer buff = dc.getDocument();
                    if (buff != null) {
                        try {
                            out.write(buff.toString().getBytes());
                        } catch (IOException iox) {
                            getLogger().log(Level.SEVERE, "Error rendering content.", iox);
                        }
                    }
                } else {
                    renderCommands(dc.getAllCommands(), dc.getDocument(), ctx, out);
                }
            } finally {
                if (dc != null) {
                    resetCommands(dc.getAllCommands());
                }
            }
        } else {
            getLogger().info("Unable to find template.");
        }
    }

    private static DocumentContext getDocumentContext(ITemplate template, IContext ctx) {
        DocumentContext dc = null;
        boolean requiresRefresh = false;
        if (template.getDocumentContext() != null) {
            requiresRefresh = template.getDocumentContext().requiresRefresh();
        }
        if (ctx.getConfig().profile()) {
            requiresRefresh = true;
        } else if (ctx.getConfig().getDevMode() && template.getDocumentContext() != null) {
            ResourceURI uri = template.getDocumentContext().getURI();
            if (template.getDocumentContext().requiresRefresh()) {
                requiresRefresh = true;
            } else if (uri != null) {
                requiresRefresh = ctx.isUpdated(uri.getFullURI(), template.getDocumentContext().getLastRefresh());
            }
        }
        if (template.getDocumentContext() == null || requiresRefresh) {
            dc = new DocumentContext();
            StringBuffer buff = template.getContent(ctx);
            dc.setDocument(buff);
            dc.setURI(template.getTemplateURI(ctx));
            gatherCommands(buff, ctx, dc);
            template.setDocumentContext(dc);
            if (ctx.getConfig().profile()) {
                dc.setRequiresRefresh(true);
            }

        } else {
            dc = template.getDocumentContext();
        }
        return dc;
    }

    private static void resetCommands(List<ICommand> cmds) {
        if (cmds == null) {
            return;
        }
        for (ICommand c : cmds) {
            c.reset();
            if (c.getDocumentContext() != null) {
                List<ICommand> scmds = c.getDocumentContext().getAllCommands();
                resetCommands(scmds);
            }
        }
    }

    @SuppressWarnings("unchecked")
    public static List<ICommand> gatherCommands(StringBuffer buff, IContext ctx, DocumentContext dc) {
        List<ICommand> cmds = null;
        try {
            cmds = getDocumentCommands(ctx.getConfig(), buff);
            dc.setAllCommands(cmds);
            List<ICommand> firstCmds = null;
            List<ICommand> lastCmds = (List<ICommand>) ctx.getAttribute(LAST_COMMAND_LIST);
            if (lastCmds == null) {
                lastCmds = new ArrayList<ICommand>();
                ctx.setAttribute(LAST_COMMAND_LIST, lastCmds);
            }
            dc.setAfterCommands(lastCmds);
            List<ICommand> defaultCmds = (List<ICommand>) ctx.getAttribute(DEFAULT_COMMAND_LIST);
            if (defaultCmds == null) {
                defaultCmds = new ArrayList<ICommand>();
                ctx.setAttribute(DEFAULT_COMMAND_LIST, defaultCmds);
            }
            dc.setDefaultCommands(defaultCmds);
            if (cmds != null) {
                // pre-process to find first and last commands

                for (ICommand c : cmds) {

                    if (c.getProcessOrder() == ICommand.PROCESS_FIRST) {
                        if (firstCmds == null) {
                            firstCmds = new ArrayList<ICommand>();
                            dc.setBeforeCommands(firstCmds);
                        }
                        firstCmds.add(c);
                    } else if (c.getProcessOrder() == ICommand.PROCESS_LAST) {

                        lastCmds.add(c);
                    } else {

                        defaultCmds.add(c);
                    }
                }
            }
        } catch (Exception e) {
            getLogger().log(Level.SEVERE, "Error gathering commands.", e);
        }
        return cmds;
    }

    public static void renderCommands(List<ICommand> cmds, StringBuffer buff, IContext ctx, OutputStream out) {
        if (cmds == null) {
            getLogger().log(Level.SEVERE, "Error rendering content.");
        }
        try {
            int index = 0;
            for (ICommand c : cmds) {
                // output everything before the first command
                // include profiler episodes.js
                if (ctx.getConfig().profile()) {
                    String preText = buff.substring(index, c.getStartIndex());
                    int headStart = preText.indexOf("<head>");
                    if (headStart != -1) {
                        long serverTime = ((Episode) ctx.getAttribute(Config.EPISODE)).getTimestamp();
                        preText = preText.substring(0, headStart + 6)
                                + "<script>var timeshift=0;var serverTimestamp = " + serverTime + ";</script>"
                                + "<script src=\"prt?resourceid=episodes.js\"></script>"
                                + "<script>var t_pingStart1 = (new Date()).getTime();</script>"
                                + "<script src=\"prt?command=ping\"></script>"
                                + "<script>var t_now1 = (new Date()).getTime();"
                                + "var t_transit1= Math.round((t_now1 - t_pingStart1) / 2);"
                                + "var t_pingStart2 = (new Date()).getTime();"
                                + "document.write(\"<scr\" + \"ipt src='prt?command=timeshift&clientTime=\" + t_pingStart2 + \"'></scr\" + \"ipt>\");\n"
                                + "var t_now2 = (new Date()).getTime();\n"
                                + "var t_transit2= Math.round((t_now2 - t_pingStart2) / 2);\n"
                                + "var t_transit= Math.round((t_transit1 + t_transit2) / 4);\n"
                                + "if ((t_transit * 2) < timeshift) {timeshift = timeshift - (t_transit * 2);} else if (timeshift < 0) {timeshift+=(t_transit * 2);}"
                                + "var t_sync='prt?command=episodesync&timestamp=" + serverTime
                                + "&transitTime=' + t_transit;"
                                + "document.write(\"<scr\" + \"ipt src='\" + t_sync + \"'></scr\" + \"ipt>\");</script>"
                                + preText.substring(headStart + 6, preText.length());
                    }
                    out.write(preText.getBytes());
                } else {
                    out.write(buff.substring(index, c.getStartIndex()).getBytes());
                }
                index = c.getEndIndex();
                try {
                    // if we have a sub document context render it
                    IDocumentContext dc = c.getDocumentContext();

                    if (dc != null) {
                        if ((dc.getAllCommands() == null || (dc.getAllCommands() != null
                                && dc.getAllCommands().size() == 0)) /* && 
                                                                     dc.getDocument() != null */) {
                            if (dc.getDocument() != null) {
                                out.write(dc.getDocument().toString().getBytes());
                            }
                        }
                        renderCommands(dc.getAllCommands(), dc.getDocument(), ctx, out);
                    }

                    ByteArrayOutputStream bos = ctx.getBuffer(c.getUUId());
                    if (bos != null) {
                        out.write(bos.toByteArray());
                    }
                } catch (IOException e) {
                    getLogger().log(Level.SEVERE, "Error rendering commands ", e);
                }

            }
            // now write everything after the last command
            if (cmds.size() > 0) {
                ICommand lc = cmds.get(cmds.size() - 1);
                out.write(buff.substring(lc.getEndIndex()).getBytes());
            }

        } catch (Exception e) {
            getLogger().log(Level.SEVERE, "Error rendering ", e);
        }
    }

    static void processCommands(IContext ctx, List<ICommand> cmds) {
        if (cmds == null) {
            return;
        }
        for (ICommand c : cmds) {
            try {
                c.doProcess(ctx);
                IDocumentContext dc = c.getDocumentContext();
                if (dc != null) {
                    // make sure sub document commands are processed
                    processCommands(ctx, dc.getBeforeCommands());
                    processCommands(ctx, dc.getDefaultCommands());
                    processCommands(ctx, dc.getAfterCommands());
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public List<ICommand> getCommands(Config cfg, StringBuffer doc) {
        return getDocumentCommands(cfg, doc);
    }

    /*
     * A command looks like 
     * 
     * <^ include("foo") ^> or <^ include("foo", "baz", "bin") ^>
     * <^ insert("bar") ^>
     * 
     */
    public static List<ICommand> getDocumentCommands(Config cfg, StringBuffer doc) {

        List<ICommand> commands = new ArrayList<ICommand>();
        if (doc == null)
            return null;
        int index = 0;
        int len = doc.length();

        while (index < len) {
            index = doc.indexOf(COMMAND_START, index);
            int end = doc.indexOf(COMMAND_END, index);

            if (index == -1 || end == -1) {
                break;
            }

            // find the full expression 
            String exp = doc.substring(index + 2, end);

            //find the command
            int paramStart = exp.indexOf("(");
            int paramEnd = exp.lastIndexOf(")");

            if (paramStart != -1 && paramEnd != -1 && paramEnd > paramStart) {

                // get commandType
                String commandTypeString = exp.substring(0, paramStart).trim();

                ICommand cmd = cfg.getCommand(commandTypeString);

                if (cmd != null) {
                    // get the params
                    String paramsString = exp.substring(paramStart + 1, paramEnd);

                    // need to parse out JSON
                    IParameter[] params = getParams(paramsString);
                    if (params != null) {
                        cmd.setParams(params);
                    }

                    cmd.setStartIndex(index);
                    cmd.setEndIndex(end + 2);

                    if ("include".equals(commandTypeString) && params.length > 0) {
                        if ("scripts".equals(params[0]) || "styles".equals(params[0])) {
                            cmd.setType(ICommand.INCLUDE_RESOURCES);
                        } else {
                            cmd.setType(ICommand.INCLUDE);
                        }
                    } else if ("insert".equals(commandTypeString)) {
                        cmd.setType(ICommand.INSERT);
                    } else {
                        cmd.setType(ICommand.CUSTOM);
                    }
                    commands.add(cmd);
                }
            }
            // start the process over
            index = end + 2;
        }
        return commands;
    }

    private static IParameter[] getParams(String paramsString) {

        paramsString = paramsString.trim();
        // for simple case of one item
        if (paramsString.indexOf(",") == -1 && paramsString.startsWith("\"") && paramsString.endsWith("\"")) {
            String text = paramsString.substring(0, paramsString.length());
            IParameter param = getParameter(text, new Character('\"'));
            IParameter[] params = new IParameter[1];
            params[0] = param;
            return params;
        }

        // otherwise we have quoted text or JSON objects
        ArrayList<IParameter> params = new ArrayList<IParameter>();
        int index = 0;
        int paramStart = index;
        boolean inQuote = false;
        boolean inArray = false;
        boolean inObject = false;
        int braceDepth = 0;
        int arrayDepth = 0;
        Character lastToken = null;
        while (index < paramsString.length()) {
            char c = paramsString.charAt(index);
            switch (c) {
            // string
            case '\"': {
                if (inQuote) {
                    // if not escaped end the quote
                    if (index > 0 && paramsString.charAt(index - 1) != '\\') {
                        inQuote = false;
                        lastToken = new Character('\"');
                    }
                } else if (!inArray && !inObject) {
                    inQuote = true;
                    lastToken = new Character('\"');
                }
                break;
            }
            case '[': {
                if (!inQuote) {
                    arrayDepth += 1;
                    lastToken = new Character('[');
                    inArray = true;
                }
                break;
            }
            case ']': {
                if (!inQuote) {
                    arrayDepth -= 1;
                    if (arrayDepth == 0 && braceDepth == 0) {
                        inArray = false;
                        String text = paramsString.substring(paramStart, index + 1).trim();
                        IParameter param = getParameter(text, lastToken);
                        params.add(param);
                        paramStart = index + 1;
                        lastToken = new Character(']');
                    }
                }
                break;
            }
            case '{': {
                if (!inQuote) {
                    if (braceDepth == 0) {
                        inObject = true;
                        paramStart = index;
                        lastToken = new Character('{');
                    }
                    braceDepth += 1;
                }
                break;
            }
            case '}': {
                if (!inQuote) {
                    braceDepth -= 1;
                    lastToken = new Character('}');
                }
                if (braceDepth == 0 && arrayDepth == 0 && lastToken != null && lastToken.charValue() != ']'
                        && lastToken.charValue() != '}') {
                    inObject = false;
                    String text = paramsString.substring(paramStart, index + 1).trim();
                    IParameter param = getParameter(text, lastToken);
                    params.add(param);
                    paramStart = index + 1;
                    lastToken = null;
                }
                break;
            }
            // could be a number or boolean
            case ',': {
                if (!inQuote && !inArray && !inObject) {
                    String text = paramsString.substring(paramStart, index).trim();
                    IParameter param = getParameter(text, lastToken);
                    params.add(param);
                    paramStart = index + 1;
                    lastToken = null;
                }
                break;
            }
            }
            index += 1;
        }
        // get the trailing param if there is one
        if (paramStart < paramsString.length()) {
            String text = paramsString.substring(paramStart, paramsString.length());
            IParameter param = getParameter(text, lastToken);
            params.add(param);
        }
        IParameter[] a = new IParameter[params.size()];
        return params.toArray(a);
    }

    static IParameter getParameter(String text, Character lastToken) {
        Object value = null;
        text = text.trim();
        int type = -1;
        if (lastToken != null && lastToken.charValue() == '\"') {
            type = IParameter.STRING;
            // set the value to not include the quotes
            value = text.substring(1, text.length() - 1);
        } else if ("true".equals(text) || "false".equals(text)) {
            type = IParameter.BOOLEAN;
            value = new Boolean(("true".equals(text)));
        } else if ("null".equals(text)) {
            type = IParameter.NULL;
        } else if (text.startsWith("{") && text.endsWith("}")) {
            try {
                value = new JSONObject(text);
                type = IParameter.OBJECT;
            } catch (JSONException e) {
                throw new RuntimeException("Error parsing JSON parameter " + text);
            }
        } else if (text.startsWith("[") && text.endsWith("]")) {
            try {
                value = new JSONArray(text);
                type = IParameter.ARRAY;
            } catch (JSONException e) {
                throw new RuntimeException("Error parsing JSON parameter " + text);
            }
        } else {
            try {
                NumberFormat nf = NumberFormat.getInstance();
                value = nf.parse(text);
                type = IParameter.NUMBER;
            } catch (ParseException pe) {
                // do nothing
            }
        }
        if (type != -1) {
            return new Parameter(type, value);
        } else {
            throw new RuntimeException("Error parsing parameter " + text);
        }
    }
}