Package com.psddev.cms.db

Source Code of com.psddev.cms.db.PageFilter

package com.psddev.cms.db;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.psddev.cms.tool.AuthenticationFilter;
import com.psddev.cms.tool.CmsTool;
import com.psddev.cms.tool.RemoteWidgetFilter;
import com.psddev.cms.tool.ToolPageContext;
import com.psddev.dari.db.Application;
import com.psddev.dari.db.ApplicationFilter;
import com.psddev.dari.db.Database;
import com.psddev.dari.db.ObjectType;
import com.psddev.dari.db.Query;
import com.psddev.dari.db.Record;
import com.psddev.dari.db.State;
import com.psddev.dari.util.AbstractFilter;
import com.psddev.dari.util.ErrorUtils;
import com.psddev.dari.util.HtmlWriter;
import com.psddev.dari.util.JspBufferFilter;
import com.psddev.dari.util.JspUtils;
import com.psddev.dari.util.ObjectUtils;
import com.psddev.dari.util.PageContextFilter;
import com.psddev.dari.util.Profiler;
import com.psddev.dari.util.PullThroughCache;
import com.psddev.dari.util.Settings;
import com.psddev.dari.util.StringUtils;
import com.psddev.dari.util.TypeDefinition;

public class PageFilter extends AbstractFilter {

    /** @deprecated No replacement. */
    @Deprecated
    public static final String WIREFRAME_PARAMETER = "_wireframe";

    private static final Logger LOGGER = LoggerFactory.getLogger(PageFilter.class);

    private static final String PARAMETER_PREFIX = "_cms.db.";

    public static final String DEBUG_PARAMETER = PARAMETER_PREFIX + "debug";

    /**
     * @deprecated No replacement.
     */
    @Deprecated
    public static final String OVERLAY_PARAMETER = PARAMETER_PREFIX + "overlay";

    public static final String PREVIEW_DATA_PARAMETER = PARAMETER_PREFIX + "previewData";
    public static final String PREVIEW_ID_PARAMETER = PARAMETER_PREFIX + "previewId";
    public static final String PREVIEW_SITE_ID_PARAMETER = "_previewSiteId";
    public static final String PREVIEW_TYPE_ID_PARAMETER = PARAMETER_PREFIX + "previewTypeId";
    public static final String PREVIEW_OBJECT_PARAMETER = "_previewObject";

    private static final String ATTRIBUTE_PREFIX = PageFilter.class.getName();
    private static final String FIXED_PATH_ATTRIBUTE = ATTRIBUTE_PREFIX + ".fixedPath";
    private static final String NEW_REQUEST_ATTRIBUTE = ATTRIBUTE_PREFIX + ".newRequest";
    private static final String PATH_ATTRIBUTE = ATTRIBUTE_PREFIX + ".path";
    private static final String PATH_MATCHES_ATTRIBUTE = ATTRIBUTE_PREFIX + ".matches";
    private static final String PREVIEW_ATTRIBUTE = ".preview";
    private static final String PERSISTENT_PREVIEW_ATTRIBUTE = ".persistentPreview";

    public static final String ABORTED_ATTRIBUTE = ATTRIBUTE_PREFIX + ".aborted";
    public static final String CURRENT_SECTION_ATTRIBUTE = ATTRIBUTE_PREFIX + ".currentSection";
    public static final String MAIN_OBJECT_ATTRIBUTE = ATTRIBUTE_PREFIX + ".mainObject";
    public static final String MAIN_OBJECT_CHECKED_ATTRIBUTE = ATTRIBUTE_PREFIX + ".mainObjectChecked";
    private static final String OBJECTS_ATTRIBUTE = ATTRIBUTE_PREFIX + ".objects";
    public static final String PAGE_ATTRIBUTE = ATTRIBUTE_PREFIX + ".page";
    public static final String PAGE_CHECKED_ATTRIBUTE = ATTRIBUTE_PREFIX + ".pageChecked";
    public static final String PARENT_SECTIONS_ATTRIBUTE = ATTRIBUTE_PREFIX + ".parentSections";
    public static final String PROFILE_ATTRIBUTE = ATTRIBUTE_PREFIX + ".profile";
    public static final String PROFILE_CHECKED_ATTRIBUTE = ATTRIBUTE_PREFIX + ".profileChecked";
    public static final String RENDERED_OBJECTS_ATTRIBUTE = ATTRIBUTE_PREFIX + ".renderedObjects";
    public static final String SITE_ATTRIBUTE = ATTRIBUTE_PREFIX + ".site";
    public static final String SITE_CHECKED_ATTRIBUTE = ATTRIBUTE_PREFIX + ".siteChecked";
    public static final String SUBSTITUTIONS_ATTRIBUTE = ATTRIBUTE_PREFIX + ".substitutions";

    /**
     * Returns {@code true} if rendering the given {@code request} has
     * been aborted.
     */
    public static boolean isAborted(HttpServletRequest request) {
        return Boolean.TRUE.equals(request.getAttribute(ABORTED_ATTRIBUTE));
    }

    /** Aborts rendering the given {@code request}. */
    public static void abort(HttpServletRequest request) {
        request.setAttribute(ABORTED_ATTRIBUTE, Boolean.TRUE);
    }

    /**
     * Returns the section currently being rendered in the given
     * {@code request}.
     */
    public static Section getCurrentSection(HttpServletRequest request) {
        return (Section) request.getAttribute(CURRENT_SECTION_ATTRIBUTE);
    }

    /**
     * Sets the section currently being rendered in the given
     * {@code request}.
     */
    protected static void setCurrentSection(HttpServletRequest request, Section section) {
        request.setAttribute(CURRENT_SECTION_ATTRIBUTE, section);
        request.setAttribute("section", section);
    }

    /**
     * Returns an unmodifiable list of all the parent sections used to render
     * the given {@code request} so far.
     */
    public static List<Section> getParentSections(HttpServletRequest request) {
        @SuppressWarnings("unchecked")
        List<Section> parents = (List<Section>) request.getAttribute(PARENT_SECTIONS_ATTRIBUTE);
        return parents != null ? Collections.unmodifiableList(parents) : Collections.<Section>emptyList();
    }

    /**
     * Adds the given {@code section} to the list of sections used to render
     * the given {@code request} so far.
     */
    protected static void addParentSection(HttpServletRequest request, Section section) {
        @SuppressWarnings("unchecked")
        List<Section> parents = (List<Section>) request.getAttribute(PARENT_SECTIONS_ATTRIBUTE);
        if (parents == null) {
            parents = new ArrayList<Section>();
            request.setAttribute(PARENT_SECTIONS_ATTRIBUTE, parents);
        }
        parents.add(section);

        @SuppressWarnings("unchecked")
        Map<String, Boolean> isInside = (Map<String, Boolean>) request.getAttribute("inside");
        if (isInside == null) {
            isInside = new HashMap<String, Boolean>();
            request.setAttribute("inside", isInside);
        }
        isInside.put(section.getDisplayName(), Boolean.TRUE);
        isInside.put(section.getInternalName(), Boolean.TRUE);
    }

    /**
     * Removes the last parent section used to render the given
     * {@code request} so far.
     */
    @SuppressWarnings("unchecked")
    protected static void removeLastParentSection(HttpServletRequest request) {
        List<Section> parents = (List<Section>) request.getAttribute(PARENT_SECTIONS_ATTRIBUTE);
        Section section = parents.remove(parents.size() - 1);
        ((Map<String, Boolean>) request.getAttribute("inside")).remove(section.getDisplayName());
        ((Map<String, Boolean>) request.getAttribute("inside")).remove(section.getInternalName());
    }

    /**
     * Returns a modifiable set of all the objects used to render the given
     * {@code request}.
     */
    public static Set<Object> getRenderedObjects(HttpServletRequest request) {
        @SuppressWarnings("unchecked")
        Set<Object> rendered = (Set<Object>) request.getAttribute(RENDERED_OBJECTS_ATTRIBUTE);
        if (rendered == null) {
            rendered = new LinkedHashSet<Object>();
            request.setAttribute(RENDERED_OBJECTS_ATTRIBUTE, rendered);
        }
        return rendered;
    }

    /**
     * Returns the map of object substitutions for the given
     * {@code request}.
     */
    public static Map<UUID, Object> getSubstitutions(HttpServletRequest request) {
        @SuppressWarnings("unchecked")
        Map<UUID, Object> substitutions = (Map<UUID, Object>) request.getAttribute(SUBSTITUTIONS_ATTRIBUTE);
        if (substitutions == null) {
            substitutions = new HashMap<UUID, Object>();
            request.setAttribute(SUBSTITUTIONS_ATTRIBUTE, substitutions);
        }
        return substitutions;
    }

    // --- AbstractFilter support ---

    @Override
    public Iterable<Class<? extends Filter>> dependencies() {
        List<Class<? extends Filter>> dependencies = new ArrayList<Class<? extends Filter>>();
        dependencies.add(FrameFilter.class);
        dependencies.add(ApplicationFilter.class);
        dependencies.add(RemoteWidgetFilter.class);
        dependencies.add(AuthenticationFilter.class);
        dependencies.add(com.psddev.cms.tool.ScheduleFilter.class);
        dependencies.add(com.psddev.dari.util.FormFilter.class);
        dependencies.add(com.psddev.dari.util.FrameFilter.class);
        dependencies.add(com.psddev.dari.util.RoutingFilter.class);
        dependencies.add(FieldAccessFilter.class);
        return dependencies;
    }

    private static boolean redirectIfFixedPath(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String path = (String) request.getAttribute(FIXED_PATH_ATTRIBUTE);
        if (path == null) {
            return false;
        } else {
            String queryString = request.getQueryString();
            if (queryString != null) {
                path += "?" + queryString;
            }
            JspUtils.redirectPermanently(request, response, path);
            return true;
        }
    }

    @Override
    protected void doError(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain chain)
            throws IOException, ServletException {

        doForward(request, response, chain);
    }

    @Override
    protected void doForward(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain chain)
            throws IOException, ServletException {

        request.removeAttribute(MAIN_OBJECT_CHECKED_ATTRIBUTE);
        request.removeAttribute(PAGE_CHECKED_ATTRIBUTE);
        request.removeAttribute(PROFILE_CHECKED_ATTRIBUTE);
        request.removeAttribute(SITE_CHECKED_ATTRIBUTE);

        doRequest(request, response, chain);
    }

    @Override
    protected void doInclude(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain chain)
            throws Exception {

        if (Static.isInlineEditingAllContents(request)) {
            response = new LazyWriterResponse(request, response);
        }

        super.doInclude(request, response, chain);
    }

    @Override
    protected void doRequest(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain chain)
            throws IOException, ServletException {

        if (request.getMethod().equalsIgnoreCase("HEAD") &&
                ObjectUtils.to(boolean.class, request.getHeader("Brightspot-Main-Object-Id-Query"))) {
            Object mainObject = Static.getMainObject(request);

            if (mainObject != null) {
                response.setHeader("Brightspot-Main-Object-Id", State.getInstance(mainObject).getId().toString());
            }

            return;
        }

        if (Static.isInlineEditingAllContents(request)) {
            try {
                JspBufferFilter.Static.overrideBuffer(0);
                doRequestForReal(request, response, chain);

            } finally {
                JspBufferFilter.Static.restoreBuffer();
            }

        } else {
            doRequestForReal(request, response, chain);
        }
    }

    @SuppressWarnings("deprecation")
    private void doRequestForReal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain chain)
            throws IOException, ServletException {

        Profile profile = Static.getProfile(request);
        Variation.Static.applyAll(TypeDefinition.getInstance(Record.class).newInstance(), profile);

        if (redirectIfFixedPath(request, response)) {
            return;
        }

        ToolUser user = AuthenticationFilter.Static.getInsecureToolUser(request);
        request.setAttribute("toolUser", user);

        VaryingDatabase varying = new VaryingDatabase();
        varying.setDelegate(Database.Static.getDefault());
        varying.setRequest(request);
        varying.setProfile(profile);
        Database.Static.overrideDefault(varying);

        Writer writer = null;

        try {
            String servletPath = request.getServletPath();

            // Serve a special robots.txt file for non-production.
            if (servletPath.equals("/robots.txt") && !Settings.isProduction()) {
                response.setContentType("text/plain");
                writer = response.getWriter();
                writer.write("User-agent: *\n");
                writer.write("Disallow: /\n");
                return;

            // Render a single section.
            } else if (servletPath.startsWith("/_render")) {
                UUID sectionId = ObjectUtils.to(UUID.class, request.getParameter("_sectionId"));
                Section section = Query.findById(Section.class, sectionId);
                if (section != null) {
                    writeSection(request, response, response.getWriter(), section);
                }
                return;

            // Strip the special directory suffix.
            } else if (servletPath.endsWith("/index")) {
                JspUtils.redirectPermanently(request, response, servletPath.substring(0, servletPath.length() - 5));
                return;
            }

            // Global prefix?
            String prefix = Settings.get(String.class, "cms/db/directoryPrefix");
            if (!ObjectUtils.isBlank(prefix)) {
                Static.setPath(request, StringUtils.ensureEnd(prefix, "/") + servletPath);
            }

            Site site = Static.getSite(request);
            if (redirectIfFixedPath(request, response)) {
                return;
            }

            Object mainObject = Static.getMainObject(request);
            if (redirectIfFixedPath(request, response)) {
                return;
            } else {
                HttpServletRequest newRequest = (HttpServletRequest) request.getAttribute(NEW_REQUEST_ATTRIBUTE);
                if (newRequest != null) {
                    request = newRequest;
                }
            }

            // Not handled by the CMS.
            if (mainObject == null) {
                chain.doFilter(request, response);
                return;
            }

            // If mainObject has a redirect path AND a permalink and the
            // current request is the redirect path, then redirect to the
            // permalink.
            String path = Static.getPath(request);
            Directory.Path redirectPath = null;
            boolean isRedirect = false;

            for (Directory.Path p : State.getInstance(mainObject).as(Directory.Data.class).getPaths()) {
                if (p.getType() == Directory.PathType.REDIRECT &&
                        ObjectUtils.equals(p.getSite(), site) &&
                        path.equalsIgnoreCase(p.getPath())) {
                    isRedirect = true;

                } else if (p.getType() == Directory.PathType.PERMALINK &&
                        ObjectUtils.equals(p.getSite(), site)) {
                    redirectPath = p;
                }
            }

            if (isRedirect && redirectPath != null) {
                JspUtils.redirectPermanently(request, response, site != null ?
                        site.getPrimaryUrl() + redirectPath.getPath() :
                        redirectPath.getPath());
                return;
            }

            Static.pushObject(request, mainObject);

            if (Static.isPreview(request) || user != null) {
                response.setHeader("Cache-Control", "private, no-cache");
                response.setHeader("Brightspot-Cache", "none");
            }

            final State mainState = State.getInstance(mainObject);

            // Fake the request path in preview mode in case the servlets
            // depend on it.
            if (Static.isPreview(request)) {
                final String previewPath = request.getParameter("_previewPath");

                if (!ObjectUtils.isBlank(previewPath)) {
                    int colonAt = previewPath.indexOf(':');

                    if (colonAt > -1) {
                        Site previewSite = Query.
                                from(Site.class).
                                where("_id = ?", ObjectUtils.to(UUID.class, previewPath.substring(0, colonAt))).
                                first();

                        if (previewSite != null) {
                            Static.setSite(request, previewSite);
                        }
                    }

                    request = new HttpServletRequestWrapper(request) {

                        @Override
                        public String getRequestURI() {
                            return getContextPath() + getServletPath();
                        }

                        @Override
                        public StringBuffer getRequestURL() {
                            return new StringBuffer(getRequestURI());
                        }

                        @Override
                        public String getServletPath() {
                            return previewPath;
                        }
                    };
                }
            }

            if (!Static.isPreview(request) &&
                    !mainState.isVisible()) {
                SCHEDULED: {
                    if (user != null) {
                        Schedule currentSchedule = user.getCurrentSchedule();

                        if (currentSchedule != null &&
                                Query.from(Draft.class).where("schedule = ? and objectId = ?", currentSchedule, mainState.getId()).first() != null) {
                            break SCHEDULED;
                        }

                    } else {
                        if (Settings.isProduction()) {
                            chain.doFilter(request, response);
                            return;

                        } else {
                            throw new IllegalStateException(String.format(
                                    "[%s] isn't visible!", mainState.getId()));
                        }
                    }
                }
            }

            // If showing an invisible item, make sure all nested invisible
            // items show up too.
            if (!mainState.isVisible() || Static.isPreview(request)) {
                mainState.setResolveInvisible(true);
            }

            ObjectType mainType = mainState.getType();
            Page page = Static.getPage(request);

            if (page == null &&
                    mainType != null &&
                    !ObjectUtils.isBlank(mainType.as(Renderer.TypeModification.class).getPath())) {
                page = Application.Static.getInstance(CmsTool.class).getModulePreviewTemplate();
            }

            // SEO and <head>.
            Map<String, Object> seo = new HashMap<String, Object>();
            Seo.ObjectModification seoData = mainState.as(Seo.ObjectModification.class);
            String seoTitle = seoData.findTitle();
            String seoDescription = seoData.findDescription();
            String seoRobots = seoData.findRobotsString();
            Set<String> seoKeywords = seoData.findKeywords();
            String seoKeywordsString = null;

            request.setAttribute("seo", seo);
            seo.put("title", seoTitle);
            seo.put("description", seoDescription);
            seo.put("robots", seoRobots);

            if (seoKeywords != null) {
                seoKeywordsString = seoKeywords.toString();

                seo.put("keywords", seoKeywords);
                seo.put("keywordsString", seoKeywordsString);
            }

            PageStage stage = new PageStage(getServletContext(), request);

            request.setAttribute("stage", stage);
            stage.setMetaProperty("og:type", mainType.as(Seo.TypeModification.class).getOpenGraphType());

            if (mainType != null &&
                    !ObjectUtils.isBlank(mainType.as(Renderer.TypeModification.class).getEmbedPath())) {
                stage.findOrCreateHeadElement("link",
                        "rel", "alternate",
                        "type", "application/json+oembed").
                        getAttributes().
                        put("href", JspUtils.getAbsoluteUrl(request, "",
                                "_embed", true,
                                "_format", "oembed"));
            }

            stage.setTitle(seoTitle);
            stage.setDescription(seoDescription);
            stage.setMetaName("robots", seoRobots);
            stage.setMetaName("keywords", seoKeywordsString);
            stage.update(mainObject);

            // Try to set the right content type based on the extension.
            String contentType = URLConnection.getFileNameMap().getContentTypeFor(servletPath);
            response.setContentType((ObjectUtils.isBlank(contentType) ? "text/html" : contentType) + ";charset=UTF-8");

            // Disable Webkit's XSS Auditor since it often interferes with
            // how preview works.
            if (Static.isPreview(request)) {
                response.setHeader("X-XSS-Protection", "0");
            }

            // Render the page.
            if (Static.isInlineEditingAllContents(request)) {
                response = new LazyWriterResponse(request, response);
            }

            writer = new HtmlWriter(response.getWriter());

            ((HtmlWriter) writer).putAllStandardDefaults();

            request.setAttribute("sections", new PullThroughCache<String, Section>() {
                @Override
                protected Section produce(String name) {
                    return Query.from(Section.class).where("internalName = ?", name).first();
                }
            });

            beginPage(request, response, writer, page);

            if (!response.isCommitted()) {
                request.getSession();
            }

            String context = request.getParameter("_context");
            boolean contextNotBlank = !ObjectUtils.isBlank(context);
            boolean embed = ObjectUtils.coalesce(
                    ObjectUtils.to(Boolean.class, request.getParameter("_embed")),
                    contextNotBlank);

            String layoutPath = findLayoutPath(mainObject, embed);

            if (page != null && ObjectUtils.isBlank(layoutPath)) {
                layoutPath = findLayoutPath(page, embed);

                if (!embed && ObjectUtils.isBlank(layoutPath)) {
                    layoutPath = page.getRendererPath();
                }
            }

            if (ObjectUtils.isBlank(layoutPath) &&
                    Static.isPreview(request)) {
                layoutPath = findLayoutPath(mainObject, true);
            }

            String typePath = mainType.as(Renderer.TypeModification.class).getPath();
            boolean rendered = false;

            try {
                if (contextNotBlank) {
                    ContextTag.Static.pushContext(request, context);
                }

                if (!ObjectUtils.isBlank(layoutPath)) {
                    rendered = true;
                    JspUtils.include(request, response, writer, StringUtils.ensureStart(layoutPath, "/"));

                } else if (page != null) {
                    Page.Layout layout = page.getLayout();

                    if (layout != null) {
                        rendered = true;
                        renderSection(request, response, writer, layout.getOutermostSection());
                    }
                }

                if (!rendered && !embed && !ObjectUtils.isBlank(typePath)) {
                    rendered = true;
                    JspUtils.include(request, response, writer, StringUtils.ensureStart(typePath, "/"));
                }

                if (!rendered && mainObject instanceof Renderer) {
                    rendered = true;
                    ((Renderer) mainObject).renderObject(request, response, (HtmlWriter) writer);
                }

            } finally {
                if (contextNotBlank) {
                    ContextTag.Static.popContext(request);
                }
            }

            if (!rendered) {
                if (Settings.isProduction()) {
                    chain.doFilter(request, response);
                    return;

                } else {
                    StringBuilder message = new StringBuilder();

                    if (embed) {
                        message.append("@Renderer.EmbedPath required on [");
                        message.append(mainObject.getClass().getName());
                        message.append("] to render it in [");
                        message.append(ObjectUtils.isBlank(context) ? "_embed" : context);
                        message.append("] context");

                    } else {
                        message.append("@Renderer.Path or @Renderer.LayoutPath required on [");
                        message.append(mainObject.getClass().getName());
                        message.append("] to render it");
                    }

                    message.append(" (Object: [");
                    message.append(mainState.getLabel());
                    message.append("], ID: [");
                    message.append(mainState.getId().toString().replaceAll("-", ""));
                    message.append("])!");

                    throw new IllegalStateException(message.toString());
                }
            }

            endPage(request, response, writer, page);

            if (Static.isInlineEditingAllContents(request)) {
                LazyWriterResponse lazyResponse = (LazyWriterResponse) response;
                Map<String, String> map = new HashMap<String, String>();
                State state = State.getInstance(mainObject);
                StringBuilder marker = new StringBuilder();

                map.put("id", state.getId().toString());
                map.put("label", state.getLabel());
                map.put("typeLabel", state.getType().getLabel());

                marker.append("<span class=\"cms-mainObject\" style=\"display: none;\" data-object=\"");
                marker.append(StringUtils.escapeHtml(ObjectUtils.toJson(map)));
                marker.append("\"></span>");

                lazyResponse.getLazyWriter().writeLazily(marker.toString());
            }

        } finally {
            Database.Static.restoreDefault();

            if (response instanceof LazyWriterResponse) {
                ((LazyWriterResponse) response).getLazyWriter().writePending();
            }
        }

        if (Settings.isDebug() ||
                (Static.isPreview(request) &&
                !Boolean.TRUE.equals(request.getAttribute(PERSISTENT_PREVIEW_ATTRIBUTE)))) {
            return;
        }

        String contentType = response.getContentType();

        if (contentType == null ||
                !StringUtils.ensureEnd(contentType, ";").startsWith("text/html;")) {
            return;
        }

        Object mainObject = PageFilter.Static.getMainObject(request);

        if (mainObject != null && user != null) {
            if (!JspUtils.isError(request)) {
                user.saveAction(request, mainObject);
            }

            @SuppressWarnings("all")
            ToolPageContext page = new ToolPageContext(getServletContext(), request, response);
            State mainState = State.getInstance(mainObject);

            page.setDelegate(writer instanceof HtmlWriter ? (HtmlWriter) writer : new HtmlWriter(writer));

            page.writeStart("style", "type", "text/css");
                page.writeCss(".bsp-inlineEditorMain",
                        "background", "rgba(32, 52, 63, 0.8) !important",
                        "color", "white !important",
                        "font-family", "'Helvetica Neue', sans-serif !important",
                        "font-size", "13px !important",
                        "font-weight", "normal !important",
                        "left", "0 !important",
                        "line-height", "21px !important",
                        "list-style", "none !important",
                        "margin", "0 !important",
                        "padding", "0 !important",
                        "position", "fixed !important",
                        "top", "0 !important",
                        "z-index", "1000001 !important");

                page.writeCss(".bsp-inlineEditorMain a",
                        "color", "#54d1f0 !important",
                        "display", "block !important",
                        "float", "left !important",
                        "max-width", "250px",
                        "overflow", "hidden",
                        "padding", "5px !important",
                        "text-overflow", "ellipsis",
                        "white-space", "nowrap");

                page.writeCss(".bsp-inlineEditorMain a:hover",
                        "background", "#54d1f0 !important",
                        "color", "black !important");

                page.writeCss(".bsp-inlineEditorMain .bsp-inlineEditorMain_logo",
                        "padding", "8px 10px !important");

                page.writeCss(".bsp-inlineEditorMain .bsp-inlineEditorMain_logo:hover",
                        "background", "transparent !important");

                page.writeCss(".bsp-inlineEditorMain .bsp-inlineEditorMain_logo img",
                        "display", "block !important");

                page.writeCss(".bsp-inlineEditorMain .bsp-inlineEditorMain_remove",
                        "color", "#ff0e40 !important",
                        "font-size", "21px");
            page.writeEnd();

            page.writeStart("div", "class", "bsp-inlineEditorMain");
                page.writeStart("a",
                        "class", "bsp-inlineEditorMain_logo",
                        "target", "_blank",
                        "href", page.fullyQualifiedToolUrl(CmsTool.class, "/"));
                    page.writeElement("img",
                            "src", page.cmsUrl("/style/brightspot.png"),
                            "alt", "Brightspot",
                            "width", 104,
                            "height", 14);
                page.writeEnd();

                Schedule currentSchedule = user.getCurrentSchedule();

                if (currentSchedule != null) {
                    page.writeStart("a",
                            "target", "_blank",
                            "href", page.fullyQualifiedToolUrl(CmsTool.class, "/scheduleEdit", "id", currentSchedule.getId()));
                        page.writeHtml("Current Schedule: ");
                        page.writeObjectLabel(currentSchedule);
                    page.writeEnd();
                }

                page.writeStart("a",
                        "target", "_blank",
                        "href", page.fullyQualifiedToolUrl(CmsTool.class, "/content/edit.jsp", "id", State.getInstance(mainObject).getId()));
                    page.writeHtml("Edit ");
                    page.writeTypeObjectLabel(mainObject);
                page.writeEnd();

                if (Boolean.TRUE.equals(request.getAttribute(PERSISTENT_PREVIEW_ATTRIBUTE))) {
                    page.writeStart("a",
                            "target", "_blank",
                            "href", page.url("", "_clearPreview", true));
                        page.writeHtml("(Previewing - View Live Instead)");
                    page.writeEnd();
                }

                page.writeStart("a",
                        "class", "bsp-inlineEditorMain_remove",
                        "href", "#",
                        "onclick",
                                "var main = this.parentNode," +
                                        "contents = this.ownerDocument.getElementById('bsp-inlineEditorContents');" +

                                "main.parentNode.removeChild(main);" +

                                "if (contents) {" +
                                    "contents.parentNode.removeChild(contents);" +
                                "}" +

                                "return false;");
                    page.writeHtml("\u00d7");
                page.writeEnd();
            page.writeEnd();

            if (user.getInlineEditing() != ToolUser.InlineEditing.DISABLED) {
                page.writeStart("iframe",
                        "class", "cms-inlineEditor",
                        "id", "bsp-inlineEditorContents",
                        "onload", "this.style.visibility = 'visible';",
                        "scrolling", "no",
                        "src", page.cmsUrl("/inlineEditor", "id", mainState.getId()),
                        "style", page.cssString(
                                "border", "none",
                                "height", 0,
                                "left", 0,
                                "margin", 0,
                                "pointer-events", "none",
                                "position", "absolute",
                                "top", 0,
                                "visibility", "hidden",
                                "width", "100%",
                                "z-index", 1000000));
                page.writeEnd();
            }
        }

        if (response instanceof LazyWriterResponse) {
            ((LazyWriterResponse) response).getLazyWriter().writePending();
        }
    }

    private String findLayoutPath(Object object, boolean embed) {
        ObjectType type = State.getInstance(object).getType();

        if (type != null) {
            Renderer.TypeModification rendererData = type.as(Renderer.TypeModification.class);

            return embed ? rendererData.getEmbedPath() : rendererData.getLayoutPath();

        } else {
            return null;
        }
    }

    /** Renders the beginning of the given {@code page}. */
    protected static void beginPage(
            HttpServletRequest request,
            HttpServletResponse response,
            Writer writer,
            Page page)
            throws IOException, ServletException {
    }

    /** Renders the end of the given {@code page}. */
    protected static void endPage(
            HttpServletRequest request,
            HttpServletResponse response,
            Writer writer,
            Page page)
            throws IOException, ServletException {
    }

    /** Renders the given {@code section}. */
    public static void renderSection(
            HttpServletRequest request,
            HttpServletResponse response,
            Writer writer,
            Section section)
            throws IOException, ServletException {

        if (isAborted(request)) {
            return;
        }

        try {
            Profiler.Static.startThreadEvent("Render", section);

            if (section != null) {
                Object substitution = getSubstitutions(request).get(section.getId());
                if (substitution != null) {
                    if (substitution instanceof Section) {
                        section = (Section) substitution;
                    } else {
                        ContentSection substitutionSection = new ContentSection();
                        substitutionSection.setContent(substitution);
                        section = substitutionSection;
                    }
                }
            }

            long cacheDuration = section != null ? section.getCacheDuration() : 0;
            if (cacheDuration > 0) {
                SectionCacheKey key = new SectionCacheKey();
                key.sectionId = section.getId();
                key.cacheDuration = cacheDuration;
                key.request = request;
                key.response = response;
                key.section = section;
                writer.write(SECTION_CACHE.get(key));

            } else {
                Section previousSection = getCurrentSection(request);
                try {
                    setCurrentSection(request, section);
                    writeSection(request, response, writer, section);
                } finally {
                    setCurrentSection(request, previousSection);
                }
            }

        } finally {
            Profiler.Static.stopThreadEvent();
        }
    }

    /**
     * Processes and writes the given {@code section} to the given
     * {@code writer}.
     */
    @SuppressWarnings("all")
    private static void writeSection(
            HttpServletRequest request,
            HttpServletResponse response,
            Writer writer,
            Section section)
            throws IOException, ServletException {

        // Container section - begin, child sections, then end.
        if (section instanceof ContainerSection) {
            ContainerSection container = (ContainerSection) section;
            List<Section> children = container.getChildren();

            try {
                addParentSection(request, container);
                beginContainer(request, response, writer, container);

                for (Section child : children) {
                    renderSection(request, response, writer, child);
                    if (isAborted(request)) {
                        return;
                    }
                }

                endContainer(request, response, writer, container);

            } finally {
                removeLastParentSection(request);
            }

        // Script section may be associated with an object.
        } else if (section instanceof ScriptSection) {
            Object object;
            if (section instanceof MainSection) {
                object = getMainObject(request);
            } else if (section instanceof ContentSection) {
                object = ((ContentSection) section).getContent();
            } else {
                object = null;
            }
            renderObjectWithSection(request, response, writer, object, (ScriptSection) section);
        }
    }

    /**
     * Key for {@link #SECTION_CACHE} that contains extra information
     * like the request object.
     */
    private static class SectionCacheKey {

        public UUID sectionId;
        public long cacheDuration;
        public HttpServletRequest request;
        public HttpServletResponse response;
        public Section section;

        @Override
        public boolean equals(Object other) {
            return this == other || (
                    other instanceof SectionCacheKey &&
                    sectionId.equals(((SectionCacheKey) other).sectionId));
        }

        @Override
        public int hashCode() {
            return sectionId.hashCode();
        }
    }

    /** Cache that contains output of each sections. */
    private static final PullThroughCache<SectionCacheKey, String>
            SECTION_CACHE = new PullThroughCache<SectionCacheKey, String>() {

        @Override
        protected boolean isExpired(SectionCacheKey key, Date lastProduced) {
            return System.currentTimeMillis() - lastProduced.getTime() > key.cacheDuration;
        }

        @Override
        protected String produce(SectionCacheKey key) throws IOException, ServletException {
            try {
                StringWriter writer = new StringWriter();
                writeSection(key.request, key.response, writer, key.section);
                return writer.toString();

            } finally {
                key.request = null;
                key.response = null;
                key.section = null;
            }
        }
    };

    /** Renders the given {@code object}. */
    public static void renderObject(
            HttpServletRequest request,
            HttpServletResponse response,
            Writer writer,
            Object object)
            throws IOException, ServletException {

        if (object instanceof Section) {
            renderSection(request, response, writer, (Section) object);

        } else {
            renderObjectWithSection(request, response, writer, object, null);
        }
    }

    /** Renders the given {@code object} using the given {@code section}. */
    @SuppressWarnings("all")
    private static void renderObjectWithSection(
            HttpServletRequest request,
            HttpServletResponse response,
            Writer writer,
            Object object,
            ScriptSection section)
            throws IOException, ServletException {

        String engine;
        String script;
        if (section != null) {
            engine = section.getEngine();
            script = section.getRendererPath();
        } else {
            engine = null;
            script = null;
        }

        if (object != null) {
            Object substitution = getSubstitutions(request).get(State.getInstance(object).getId());
            if (substitution != null) {
                object = substitution;
            }

            getRenderedObjects(request).add(object);

            // Engine not specified on section so fall back to the one
            // specified in the object type definition.
            if (ObjectUtils.isBlank(script)) {
                ObjectType type = State.getInstance(object).getType();
                if (type != null) {
                    Renderer.TypeModification typeRenderer = type.as(Renderer.TypeModification.class);
                    engine = typeRenderer.getEngine();
                    script = typeRenderer.findContextualPath(request);
                }
            }
        }

        LazyWriter lazyWriter;

        if (Static.isInlineEditingAllContents(request)) {
            lazyWriter = new LazyWriter(request, writer);
            writer = lazyWriter;

        } else {
            lazyWriter = null;
        }

        try {
            if (object != null) {
                Static.pushObject(request, object);
            }

            if (lazyWriter != null) {
                Map<String, String> map = new HashMap<String, String>();
                Object concrete = Static.peekConcreteObject(request);
                StringBuilder marker = new StringBuilder();

                if (section != null) {
                    map.put("sectionName", section.getName());
                    map.put("sectionId", section.getId().toString());
                }

                if (concrete != null) {
                    State state = State.getInstance(concrete);
                    ObjectType stateType = state.getType();

                    map.put("id", state.getId().toString());

                    if (stateType != null) {
                        map.put("typeLabel", stateType.getLabel());
                    }

                    try {
                        map.put("label", state.getLabel());

                    } catch (RuntimeException error) {
                        // Not a big deal if label can't be retrieved.
                    }
                }

                marker.append("<span class=\"cms-objectBegin\" style=\"display: none;\" data-object=\"");
                marker.append(StringUtils.escapeHtml(ObjectUtils.toJson(map)));
                marker.append("\"></span>");

                lazyWriter.writeLazily(marker.toString());
            }

            if (ObjectUtils.isBlank(script) && object instanceof Renderer) {
                ((Renderer) object).renderObject(
                        request,
                        response,
                        writer instanceof HtmlWriter ? (HtmlWriter) writer : new HtmlWriter(writer));

            } else {
                renderScript(request, response, writer, engine, script);
            }

        } finally {
            if (object != null) {
                Static.popObject(request);
            }

            if (lazyWriter != null) {
                lazyWriter.writeLazily("<span class=\"cms-objectEnd\" style=\"display: none;\"></span>");
            }
        }
    }

    // Renders the given script using the given engine.
    private static void renderScript(
            HttpServletRequest request,
            HttpServletResponse response,
            Writer writer,
            String engine,
            String script)
            throws IOException, ServletException {

        try {
            if ("RawText".equals(engine)) {
                writer.write(script);
                return;

            } else if (!ObjectUtils.isBlank(script)) {
                JspUtils.include(request, response, writer, StringUtils.ensureStart(script, "/"));
                return;
            }

        // Always catch the error so the page never looks broken
        // in production.
        } catch (Throwable ex) {
            if (Settings.isProduction()) {
                LOGGER.warn(String.format("Can't render [%s]!", script), ex);

            } else if (ex instanceof IOException) {
                throw (IOException) ex;
            } else if (ex instanceof ServletException) {
                throw (ServletException) ex;
            } else if (ex instanceof RuntimeException) {
                throw (RuntimeException) ex;
            } else if (ex instanceof Error) {
                throw (Error) ex;
            } else {
                throw new RuntimeException(ex);
            }
        }
    }

    /** {@link PageFilter} utility methods. */
    public static final class Static {

        private Static() {
        }

        /** Returns the path used to find the main object. */
        public static String getPath(HttpServletRequest request) {
            getSite(request);
            String path = (String) request.getAttribute(PATH_ATTRIBUTE);
            return path != null ? path : request.getServletPath();
        }

        /** Sets the path used to find the main object. */
        public static void setPath(HttpServletRequest request, String path) {
            request.setAttribute(PATH_ATTRIBUTE, path);
        }

        private static void fixPath(HttpServletRequest request, String path) {
            request.setAttribute(FIXED_PATH_ATTRIBUTE, path);
        }

        /** Returns the site associated with the given {@code request}. */
        public static Site getSite(HttpServletRequest request) {
            if (Boolean.TRUE.equals(request.getAttribute(SITE_CHECKED_ATTRIBUTE))) {
                return (Site) request.getAttribute(SITE_ATTRIBUTE);
            }

            request.setAttribute(SITE_CHECKED_ATTRIBUTE, Boolean.TRUE);

            Site site = null;
            String servletPath = request.getServletPath();
            String absoluteUrl = JspUtils.getAbsoluteUrl(request, servletPath);
            Map.Entry<String, Site> entry = Site.Static.findByUrl(absoluteUrl);

            if (entry != null) {
                String path = absoluteUrl.substring(entry.getKey().length() - 1);
                if (path.length() == 0) {
                    fixPath(request, servletPath + "/");
                }

                site = Query.from(Site.class).where("_id = ?", entry.getValue()).first();
                setSite(request, site);
                setPath(request, path);
            }

            return site;
        }

        /** Sets the site associated with the given {@code request}. */
        public static void setSite(HttpServletRequest request, Site site) {
            request.setAttribute(SITE_CHECKED_ATTRIBUTE, Boolean.TRUE);
            request.setAttribute(SITE_ATTRIBUTE, site);
            request.setAttribute("site", site);
        }

        /**
         * Returns {@code true} if the given {@code request} is for a preview.
         *
         * @param request Can't be {@code null}.
         */
        public static boolean isPreview(HttpServletRequest request) {
            return Boolean.TRUE.equals(request.getAttribute(PREVIEW_ATTRIBUTE));
        }

        /** Returns the main object associated with the given {@code request}. */
        public static Object getMainObject(HttpServletRequest request) {
            if (Boolean.TRUE.equals(request.getAttribute(MAIN_OBJECT_CHECKED_ATTRIBUTE))) {
                return request.getAttribute(MAIN_OBJECT_ATTRIBUTE);
            }

            VaryingDatabase varying = new VaryingDatabase();
            varying.setDelegate(Database.Static.getDefault());
            varying.setRequest(request);
            varying.setProfile(getProfile(request));
            Database.Static.overrideDefault(varying);

            try {
                request.setAttribute(MAIN_OBJECT_CHECKED_ATTRIBUTE, Boolean.TRUE);

                Object mainObject = null;
                String servletPath = request.getServletPath();
                String path = getPath(request);
                Site site = getSite(request);

                // On preview request, manually create the main object based on
                // the post data.
                if (path.startsWith("/_preview")) {
                    request.setAttribute(PREVIEW_ATTRIBUTE, Boolean.TRUE);

                    UUID previewId = ObjectUtils.to(UUID.class, request.getParameter(PREVIEW_ID_PARAMETER));

                    if (previewId == null) {
                        String previewIdString = path.substring(10);
                        int slashAt = previewIdString.indexOf('/');

                        if (slashAt > -1) {
                            previewIdString = previewIdString.substring(0, slashAt);
                        }

                        previewId = ObjectUtils.to(UUID.class, previewIdString);
                    }

                    if (previewId != null) {
                        Map<UUID, Object> substitutions = getSubstitutions(request);

                        if (ObjectUtils.to(Date.class, request.getParameter("_date")) == null) {
                            String[] objectStrings = request.getParameterValues(PREVIEW_OBJECT_PARAMETER);

                            if (objectStrings != null) {
                                for (String objectString : objectStrings) {
                                    if (!ObjectUtils.isBlank(objectString)) {
                                        @SuppressWarnings("unchecked")
                                        Map<String, Object> objectMap = (Map<String, Object>) ObjectUtils.fromJson(objectString.trim());
                                        ObjectType type = ObjectType.getInstance(ObjectUtils.to(UUID.class, objectMap.remove("_typeId")));

                                        if (type != null) {
                                            Object object = type.createObject(ObjectUtils.to(UUID.class, objectMap.remove("_id")));
                                            State objectState = State.getInstance(object);

                                            objectState.setValues(objectMap);
                                            substitutions.put(objectState.getId(), object);
                                        }
                                    }
                                }
                            }
                        }

                        UUID mainObjectId = ObjectUtils.to(UUID.class, request.getParameter("_mainObjectId"));
                        Object preview = Query.
                                fromAll().
                                where("_id = ?", mainObjectId).
                                first();

                        if (preview == null) {
                            preview = Query.
                                    fromAll().
                                    where("_id = ?", previewId).
                                    first();
                        }

                        if (preview instanceof Draft) {
                            mainObject = ((Draft) preview).getObject();

                        } else if (preview instanceof History) {
                            mainObject = ((History) preview).getObject();

                        } else if (preview instanceof Preview) {
                            Preview previewPreview = (Preview) preview;
                            mainObject = previewPreview.getObject();
                            Site previewSite = previewPreview.getSite();

                            if (previewSite != null) {
                                site = previewSite;
                                setSite(request, site);
                            }

                            AuthenticationFilter.Static.setCurrentPreview(request, PageContextFilter.Static.getResponse(), previewPreview);

                        } else if (mainObjectId != null) {
                            mainObject = preview;

                        } else {
                            mainObject = substitutions.get(previewId);

                            if (mainObject == null) {
                                mainObject = preview;
                            }
                        }
                    }

                    if (mainObject != null) {
                        Directory.Data dirData = State.getInstance(mainObject).as(Directory.Data.class);

                        if (dirData.getRawPaths().isEmpty()) {
                            dirData.addPath(null, "/_preview-" + previewId, Directory.PathType.PERMALINK);
                        }

                        Site previewSite = Query.
                                from(Site.class).
                                where("_id = ?", request.getParameter(PREVIEW_SITE_ID_PARAMETER)).
                                first();

                        if (previewSite != null) {
                            setSite(request, previewSite);
                        }
                    }

                } else {
                    mainObject = Directory.Static.findByPath(site, path);

                    if (mainObject == null) {
                        mainObject = Directory.Static.findByPath(site, path + "/index");

                        // Index pages should have a trailing slash.
                        if (mainObject != null) {

                            // Except when told not to.
                            if (Query.from(CmsTool.class).first().isRemoveTrailingSlashes()) {
                                if (path.length() > 1 && path.endsWith("/")) {
                                    fixPath(request, servletPath.substring(0, servletPath.length() - 1));
                                }

                            } else if (!path.endsWith("/")) {
                                fixPath(request, servletPath + "/");
                            }
                        }

                    // Normal pages shouldn't have a trailing slash.
                    } else if (path.endsWith("/")) {
                        fixPath(request, servletPath.substring(0, servletPath.length() - 1));
                    }
                }

                // Case-insensitive path look-up.
                for (int i = 0, length = path.length(); i < length; ++ i) {
                    if (Character.isUpperCase(path.charAt(i))) {
                        String pathLc = path.toLowerCase(Locale.ENGLISH);
                        if (Directory.Static.findObject(site, pathLc) != null) {
                            fixPath(request, pathLc);
                        }
                        break;
                    }
                }

                // Special fallback names. For example, given /path/to/file,
                // the following are checked:
                //
                // - /path/to/file/*
                // - /path/to/file/**
                // - /path/to/*
                // - /path/to/**
                // - /path/**
                // - /**
                String checkPath;
                int endMarker;

                if (path.endsWith("/")) {
                    checkPath = path;
                    endMarker = 0;

                } else {
                    checkPath = path + "/";
                    endMarker = 1;
                }

                for (int i = 0; mainObject == null; ++ i) {
                    int slashAt = checkPath.lastIndexOf("/");

                    if (slashAt < 0) {
                        break;
                    } else {
                        checkPath = checkPath.substring(0, slashAt);
                    }

                    if (i <= endMarker) {
                        mainObject = Directory.Static.findObject(site, checkPath + "/*");
                    }

                    if (mainObject == null) {
                        mainObject = Directory.Static.findObject(site, checkPath + "/**");
                    }

                    if (mainObject instanceof Directory) {
                        mainObject = null;
                    }

                    if (mainObject != null) {
                        final String pathInfo = path.substring(checkPath.length());

                        if (Query.from(CmsTool.class).first().isRemoveTrailingSlashes()) {
                            if ("/".equals(pathInfo)) {
                                fixPath(request, servletPath.substring(0, servletPath.length() - 1));
                            }

                        } else if (pathInfo.length() < 1) {
                            fixPath(request, servletPath + "/");
                        }

                        request.setAttribute(NEW_REQUEST_ATTRIBUTE, new HttpServletRequestWrapper(request) {
                            @Override
                            public String getPathInfo() {
                                return pathInfo;
                            }
                        });
                    }
                }

                if (!Static.isPreview(request) && mainObject != null) {
                    Preview preview = AuthenticationFilter.Static.getCurrentPreview(request);

                    if (preview != null) {
                        State mainState = State.getInstance(mainObject);

                        if (mainState.getId().equals(preview.getObjectId())) {
                            request.setAttribute(PREVIEW_ATTRIBUTE, Boolean.TRUE);
                            request.setAttribute(PERSISTENT_PREVIEW_ATTRIBUTE, Boolean.TRUE);
                            mainState.putAll(preview.getObjectValues());
                        }
                    }
                }

                setMainObject(request, mainObject);
                return mainObject;

            } finally {
                Database.Static.restoreDefault();
            }
        }

        /** Sets the main object associated with the given {@code request}. */
        public static void setMainObject(HttpServletRequest request, Object mainObject) {
            request.setAttribute(MAIN_OBJECT_CHECKED_ATTRIBUTE, Boolean.TRUE);
            request.setAttribute(MAIN_OBJECT_ATTRIBUTE, mainObject);
            request.setAttribute("mainObject", mainObject);
            request.setAttribute("mainRecord", mainObject);
            request.setAttribute("mainContent", mainObject);
        }

        /** Returns the page used to render the given {@code request}. */
        public static Page getPage(HttpServletRequest request) {
            if (Boolean.TRUE.equals(request.getAttribute(PAGE_CHECKED_ATTRIBUTE))) {
                return (Page) request.getAttribute(PAGE_ATTRIBUTE);
            }

            request.setAttribute(PAGE_CHECKED_ATTRIBUTE, Boolean.TRUE);

            Page page = null;
            Object mainObject = getMainObject(request);

            if (mainObject instanceof Page) {
                page = (Page) mainObject;
            } else {
                page = Template.Static.findRenderable(mainObject, getSite(request));
            }

            setPage(request, page);
            return page;
        }

        /** Sets the page used to render the given {@code request}. */
        public static void setPage(HttpServletRequest request, Page page) {
            request.setAttribute(PAGE_CHECKED_ATTRIBUTE, Boolean.TRUE);
            request.setAttribute(PAGE_ATTRIBUTE, page);
            request.setAttribute("template", page);
        }

        /** Returns the profile used to process the given {@code request}. */
        public static Profile getProfile(HttpServletRequest request) {
            if (Boolean.TRUE.equals(request.getAttribute(PROFILE_CHECKED_ATTRIBUTE))) {
                return (Profile) request.getAttribute(PROFILE_ATTRIBUTE);
            }

            request.setAttribute(PROFILE_CHECKED_ATTRIBUTE, Boolean.TRUE);

            Profile profile = new Profile();
            profile.setUserAgent(request.getHeader("User-Agent"));

            setProfile(request, profile);
            return profile;
        }

        /** Sets the profile used to process the given {@code request}. */
        public static void setProfile(HttpServletRequest request, Profile profile) {
            request.setAttribute(PROFILE_CHECKED_ATTRIBUTE, Boolean.TRUE);
            request.setAttribute(PROFILE_ATTRIBUTE, profile);
            request.setAttribute("profile", profile);
        }

        /**
         * Pushes the given {@code object} to the list of objects that
         * are currently being rendered.
         */
        public static void pushObject(HttpServletRequest request, Object object) {
            ErrorUtils.errorIfNull(object, "object");

            @SuppressWarnings("unchecked")
            List<Object> objects = (List<Object>) request.getAttribute(OBJECTS_ATTRIBUTE);

            if (objects == null) {
                objects = new ArrayList<Object>();
                request.setAttribute(OBJECTS_ATTRIBUTE, objects);
            }

            objects.add(object);
            request.setAttribute("content", object);
            request.setAttribute("record", object);
            request.setAttribute("object", object);
            request.setAttribute(CURRENT_OBJECT_ATTRIBUTE, object);
        }

        /**
         * Pops the last object from the list of objects that are currently
         * being rendered.
         */
        public static Object popObject(HttpServletRequest request) {
            @SuppressWarnings("unchecked")
            List<Object> objects = (List<Object>) request.getAttribute(OBJECTS_ATTRIBUTE);

            if (objects == null || objects.isEmpty()) {
                return null;

            } else {
                Object popped = objects.remove(objects.size() - 1);
                Object object = peekObject(request);
                request.setAttribute("content", object);
                request.setAttribute("record", object);
                request.setAttribute("object", object);
                return popped;
            }
        }

        /**
         * Returns the last object from the list of objects that are currently
         * being rendered.
         */
        public static Object peekObject(HttpServletRequest request) {
            @SuppressWarnings("unchecked")
            List<Object> objects = (List<Object>) request.getAttribute(OBJECTS_ATTRIBUTE);

            if (objects == null || objects.isEmpty()) {
                return null;

            } else {
                return objects.get(objects.size() - 1);
            }
        }

        /**
         * Returns the last concrete object from the list of objects that are
         * currently being rendered.
         */
        public static Object peekConcreteObject(HttpServletRequest request) {
            @SuppressWarnings("unchecked")
            List<Object> objects = (List<Object>) request.getAttribute(OBJECTS_ATTRIBUTE);

            if (objects != null) {
                for (int i = objects.size() - 1; i >= 0; -- i) {
                    Object object = objects.get(i);

                    if (!State.getInstance(object).isNew()) {
                        return object;
                    }
                }
            }

            return null;
        }

        /**
         * Returns {@code true} if the tool user has requested for inline
         * editing to be fully enabled.
         *
         * @param request Can't be {@code null}.
         * @return {@code false} if a tool user isn't logged in.
         */
        public static boolean isInlineEditingAllContents(HttpServletRequest request) {
            if (Settings.isDebug()) {
                return false;

            } else {
                ToolUser user = AuthenticationFilter.Static.getInsecureToolUser(request);

                return user != null && user.getInlineEditing() == null;
            }
        }

        /** @deprecated Use {@link ElFunctionUtils#plainResource} instead. */
        @Deprecated
        public static String getPlainResource(String servletPath) {
            return ElFunctionUtils.plainResource(servletPath);
        }

        /** @deprecated Use {@link ElFunctionUtils#resource} instead. */
        @Deprecated
        public static String getResource(String servletPath) {
            return ElFunctionUtils.resource(servletPath);
        }
    }

    public static class PathPattern extends Rule {

        private String pattern;

        public String getPattern() {
            return pattern;
        }

        public void setPattern(String pattern) {
            this.pattern = pattern;
        }

        // --- Rule support ---

        @Override
        public boolean evaluate(Variation variation, Profile profile, Object object) {
            HttpServletRequest request = PageContextFilter.Static.getRequest();
            if (request == null) {
                return false;
            }

            String path = request.getServletPath();
            Matcher matcher = Pattern.compile(getPattern()).matcher(path);

            if (!matcher.matches()) {
                return false;
            }

            List<String> matches = new ArrayList<String>();
            StringBuilder pathBuilder = new StringBuilder();
            int lastEnd = 0;

            for (int i = 1, count = matcher.groupCount(); i <= count; ++ i) {
                matches.add(matcher.group(i));
                pathBuilder.append(path.substring(lastEnd, matcher.start(i)));
                lastEnd = matcher.end(i);
            }

            request.setAttribute(PATH_MATCHES_ATTRIBUTE, matches);
            request.setAttribute("pathMatches", matches);

            pathBuilder.append(path.substring(lastEnd));
            PageFilter.Static.setPath(request, pathBuilder.toString());
            return true;
        }
    }

    // --- Deprecated ---

    /** @deprecated No replacement. */
    @Deprecated
    public static final String EXCEPTION_CSS_INJECTED_ATTRIBUTE = ATTRIBUTE_PREFIX + ".exceptionCssInjected";

    /** @deprecated Use {@link Static#getSite} instead. */
    @Deprecated
    public static Site getSite(HttpServletRequest request) {
        return Static.getSite(request);
    }

    /** @deprecated Use {@link Static#setSite} instead. */
    @Deprecated
    public static void setSite(HttpServletRequest request, Site site) {
        Static.setSite(request, site);
    }

    /** @deprecated Use {@link Static#getMainObject} instead. */
    @Deprecated
    public static Object getMainObject(HttpServletRequest request) {
        return Static.getMainObject(request);
    }

    /** @deprecated Use {@link Static#setMainObject} instead. */
    @Deprecated
    public static void setMainObject(HttpServletRequest request, Object mainObject) {
        Static.setMainObject(request, mainObject);
    }

    /**
     * @deprecated Use {@link Static#getPage} instead. To maintain
     *             backward compatibility, this method will return a
     *             {@code null} instead of looking for the most
     *             appropriate page to render with, if it's called
     *             before {@link #doRequest}.
     */
    @Deprecated
    public static Page getPage(HttpServletRequest request) {
        return (Page) request.getAttribute(PAGE_ATTRIBUTE);
    }

    /** @deprecated Use {@link Static#setPage} instead. */
    @Deprecated
    public static void setPage(HttpServletRequest request, Page page) {
        Static.setPage(request, page);
    }

    /** @deprecated Use {@link Static#getProfile} instead. */
    @Deprecated
    public static Profile getProfile(HttpServletRequest request) {
        return Static.getProfile(request);
    }

    /** @deprecated Use {@link Static#setProfile} instead. */
    @Deprecated
    public static void setProfile(HttpServletRequest request, Profile profile) {
        Static.setProfile(request, profile);
    }

    /**
     * @deprecated Use {@link Query#from} and {@link Site#itemsPredicate}
     *             together instead.
     */
    @Deprecated
    public static <T> Query<T> queryFrom(HttpServletRequest request, Class<T> objectClass) {
        return Query.from(objectClass).where(Site.OWNER_FIELD + " = ?", getSite(request));
    }

    /**
     * @deprecated You should let the exception propagate up naturally
     *             instead of catching it and using this method.
     */
    @Deprecated
    public static void writeException(HttpServletRequest request, Writer writer, Exception exception) throws IOException {
        if (exception instanceof RuntimeException) {
            throw (RuntimeException) exception;
        } else if (exception instanceof IOException) {
            throw (IOException) exception;
        } else {
            throw new RuntimeException(exception);
        }
    }

    /** @deprecated Use {@link Static#getPlainResource} instead. */
    @Deprecated
    public static String getPlainResource(String servletPath) {
        return Static.getPlainResource(servletPath);
    }

    /** @deprecated Use {@link Static#getResource} instead. */
    @Deprecated
    public static String getResource(String servletPath) {
        return Static.getResource(servletPath);
    }

    /** Renders the beginning of the given {@code container}. */
    @Deprecated
    protected static void beginContainer(
            HttpServletRequest request,
            HttpServletResponse response,
            Writer writer,
            ContainerSection container)
            throws IOException, ServletException {

        renderScript(request, response, writer, container.getBeginEngine(), container.getBeginScript());
    }

    /** Renders the end of the given {@code container}. */
    @Deprecated
    protected static void endContainer(
            HttpServletRequest request,
            HttpServletResponse response,
            Writer writer,
            ContainerSection container)
            throws IOException, ServletException {

        renderScript(request, response, writer, container.getEndEngine(), container.getEndScript());
    }

    /** @deprecated No replacement. */
    @Deprecated
    public static final String CURRENT_OBJECT_ATTRIBUTE = ATTRIBUTE_PREFIX + ".currentObject";

    /** @deprecated Use {@link Static#peekObject} instead. */
    @Deprecated
    public static Object getCurrentObject(HttpServletRequest request) {
        return Static.peekObject(request);
    }

    /** @deprecated Use {@link Static#pushObject} instead. */
    @Deprecated
    public static void setCurrentObject(HttpServletRequest request, Object object) {
        request.setAttribute(OBJECTS_ATTRIBUTE, null);
        Static.pushObject(request, object);
    }
}
TOP

Related Classes of com.psddev.cms.db.PageFilter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.