org.brixcms.web.nodepage.BrixNodePageUrlCodingStrategy.java Source code

Java tutorial

Introduction

Here is the source code for org.brixcms.web.nodepage.BrixNodePageUrlCodingStrategy.java

Source

/**
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.brixcms.web.nodepage;

import org.apache.wicket.Application;
import org.apache.wicket.Component;
import org.apache.wicket.IRedirectListener;
import org.apache.wicket.IRequestTarget;
import org.apache.wicket.Page;
import org.apache.wicket.RequestCycle;
import org.apache.wicket.Session;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.model.IModel;
import org.apache.wicket.request.RequestParameters;
import org.apache.wicket.request.target.coding.IRequestTargetUrlCodingStrategy;
import org.apache.wicket.request.target.component.BookmarkableListenerInterfaceRequestTarget;
import org.apache.wicket.request.target.component.IBookmarkablePageRequestTarget;
import org.apache.wicket.request.target.component.listener.ListenerInterfaceRequestTarget;
import org.apache.wicket.util.string.AppendingStringBuffer;
import org.apache.wicket.util.string.StringValue;
import org.apache.wicket.util.string.Strings;
import org.brixcms.Brix;
import org.brixcms.BrixNodeModel;
import org.brixcms.Path;
import org.brixcms.jcr.wrapper.BrixNode;
import org.brixcms.web.BrixRequestCycleProcessor;
import org.brixcms.web.nodepage.BrixNodePageRequestTarget.PageFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author Matej Knopp
 */
public class BrixNodePageUrlCodingStrategy implements IRequestTargetUrlCodingStrategy {
    // ------------------------------ FIELDS ------------------------------

    private static final Logger log = LoggerFactory.getLogger(BrixNodePageUrlCodingStrategy.class);

    // ------------------------ INTERFACE METHODS ------------------------

    // --------------------- Interface IRequestTargetUrlCodingStrategy ---------------------

    public String getMountPath() {
        return "--brix-internal";
    }

    public CharSequence encode(IRequestTarget requestTarget) {
        if (requestTarget instanceof BrixNodeRequestTarget) {
            BrixNodeRequestTarget target = (BrixNodeRequestTarget) requestTarget;
            PageInfo info = null;
            Page page = target.getPage();
            if (page != null && !page.isPageStateless()) {
                info = new PageInfo(page.getNumericId(), page.getCurrentVersionNumber(), page.getPageMapName());
            }
            String nodeURL = target.getNodeURL();
            return encode(nodeURL, target.getParameters(), info, null);
        } else if (requestTarget instanceof ListenerInterfaceRequestTarget) {
            ListenerInterfaceRequestTarget target = (ListenerInterfaceRequestTarget) requestTarget;
            BrixNodeWebPage page = (BrixNodeWebPage) target.getPage();
            return encode(page);
        } else if (requestTarget instanceof BookmarkableListenerInterfaceRequestTarget) {
            BookmarkableListenerInterfaceRequestTarget target = (BookmarkableListenerInterfaceRequestTarget) requestTarget;
            BrixNodeWebPage page = (BrixNodeWebPage) target.getPage();
            BrixNode node = page.getModelObject();
            PageInfo info = new PageInfo(page.getNumericId(), page.getCurrentVersionNumber(),
                    page.getPageMapName());
            String componentPath = target.getComponentPath();

            // remove the page id from component path, we don't really need it
            componentPath = componentPath.substring(componentPath.indexOf(':') + 1);
            String iface = componentPath + ":" + target.getInterfaceName();
            return encode(node, page.getBrixPageParameters(), info, iface);
        } else if (requestTarget instanceof IBookmarkablePageRequestTarget
                && ((IBookmarkablePageRequestTarget) requestTarget).getPageClass().equals(HomePage.class)) {
            BrixNode node = ((BrixRequestCycleProcessor) RequestCycle.get().getProcessor())
                    .getNodeForUriPath(Path.ROOT);
            return encode(new BrixNodeRequestTarget(new BrixNodeModel(node)));
        } else {
            return null;
        }
    }

    public IRequestTarget decode(RequestParameters requestParameters) {
        throw new UnsupportedOperationException();
    }

    public boolean matches(IRequestTarget requestTarget) {
        if (requestTarget instanceof ListenerInterfaceRequestTarget) {
            ListenerInterfaceRequestTarget target = (ListenerInterfaceRequestTarget) requestTarget;
            return isBrixPage(target.getPage())
                    && target.getRequestListenerInterface().equals(IRedirectListener.INTERFACE);
        } else if (requestTarget instanceof BookmarkableListenerInterfaceRequestTarget) {
            BookmarkableListenerInterfaceRequestTarget target = (BookmarkableListenerInterfaceRequestTarget) requestTarget;
            return isBrixPage(target.getPage());
        } else if (requestTarget instanceof BrixNodeRequestTarget) {
            return true;
        }
        return false;
    }

    public boolean matches(String path, boolean caseSensitive) {
        return false;
    }

    // -------------------------- OTHER METHODS --------------------------

    public IRequestTarget decode(RequestParameters requestParameters, final IModel<BrixNode> nodeModel) {
        PageInfo pageInfo = null;
        String query = requestParameters.getQueryString();
        final BrixPageParameters pageParameters = new BrixPageParameters();

        String iface = null;

        if (query != null) {
            pageInfo = extractPageInfo(query);
        }
        iface = addQueryStringParameters(pageParameters, pageInfo, requestParameters);

        addIndexedParameters(requestParameters.getPath(), pageParameters, nodeModel);

        BrixNodeWebPage page = null;
        PageFactory factory = null;

        if (pageInfo != null) {
            page = (BrixNodeWebPage) getPage(pageInfo);
        }

        if (page == null) {
            factory = new PageFactory() {
                public BrixNodeWebPage newPage() {
                    BrixNodeWebPage page = newPageInstance(nodeModel, pageParameters);
                    return page;
                }

                public BrixPageParameters getPageParameters() {
                    return pageParameters;
                }
            };
        } else {
            page.getBrixPageParameters().assign(pageParameters);
        }

        if (factory == null) {
            if (iface == null) {
                return new BrixNodePageRequestTarget(nodeModel, page);
            } else {
                return new BrixNodePageListenerRequestTarget(nodeModel, page, iface);
            }
        } else {
            if (iface == null) {
                return new BrixNodePageRequestTarget(nodeModel, factory);
            } else {
                return new BrixNodePageListenerRequestTarget(nodeModel, factory, iface);
            }
        }
    }

    private PageInfo extractPageInfo(String query) {
        // try to extract page info
        int i1 = query.indexOf("=");
        int i2 = query.indexOf("&");
        String part = null;

        if (i1 == -1 && i2 == -1) {
            part = query;
        } else if (i2 != -1 && (i1 == -1 || i2 < i1)) {
            part = query.substring(0, i2);
        }

        return PageInfo.parsePageInfo(part);
    }

    @SuppressWarnings("unchecked")
    private String addQueryStringParameters(BrixPageParameters pageParameters, PageInfo pageInfo,
            RequestParameters requestParameters) {
        final String pageInfoString = pageInfo != null ? pageInfo.toString() : null;

        String iface = null;

        for (Iterator i = requestParameters.getParameters().entrySet().iterator(); i.hasNext();) {
            Map.Entry entry = (Map.Entry) i.next();
            String p = (String) entry.getKey();
            String v[] = (String[]) entry.getValue();
            if (p.equals(getInterfaceParameter()) && v.length > 0) {
                iface = v[0];
            } else if (p.equals(pageInfoString) && v.length == 1 && "".equals(v[0])) {
                // don't add this to page parameters
            } else {
                for (int j = 0; j < v.length; ++j) {
                    pageParameters.addQueryParam(p, v[j]);
                }
            }
        }

        return iface;
    }

    ;

    private String getInterfaceParameter() {
        return Brix.NS_PREFIX + "i";
    }

    private void addIndexedParameters(String requestPathString, BrixPageParameters parameters,
            IModel<BrixNode> nodeModel) {
        if (!requestPathString.startsWith("/"))
            requestPathString = "/" + requestPathString;

        BrixRequestCycleProcessor processor = (BrixRequestCycleProcessor) RequestCycle.get().getProcessor();
        Path nodePath = processor.getUriPathForNode(nodeModel.getObject());
        Path requestPath = new Path(requestPathString, false);

        if (nodePath.isAncestorOf(requestPath)) {
            Path remaining = new Path(requestPathString, false).toRelative(nodePath);
            int i = 0;
            for (String s : remaining) {
                parameters.setIndexedParam(i, urlDecode(s));
                ++i;
            }
        }
    }

    /**
     * Returns a decoded value of the given value
     *
     * @param value
     * @return Decodes the value
     */
    public static String urlDecode(String value) {
        try {
            value = URLDecoder.decode(value,
                    Application.get().getRequestCycleSettings().getResponseRequestEncoding());
        } catch (UnsupportedEncodingException ex) {
            log.error("error decoding parameter", ex);
        }
        return value;
    }

    private Page getPage(PageInfo info) {
        Page page;

        if (Strings.isEmpty(info.getPageMapName()) && Application.exists()
                && Application.get().getSessionSettings().isPageIdUniquePerSession()) {
            page = Session.get().getPage(info.getPageId().intValue(),
                    info.getVersionNumber() != null ? info.getVersionNumber().intValue() : 0);
        } else {
            page = Session.get().getPage(info.getPageMapName(), "" + info.getPageId(),
                    info.getVersionNumber() != null ? info.getVersionNumber().intValue() : 0);
        }

        if (page != null && isBrixPage(page)) {
            return page;
        } else {
            return null;
        }
    }

    private boolean isBrixPage(Page page) {
        return page instanceof BrixNodeWebPage;
    }

    protected BrixNodeWebPage newPageInstance(IModel<BrixNode> nodeModel, BrixPageParameters pageParameters) {
        throw new UnsupportedOperationException();
    }

    private CharSequence encode(BrixNodeWebPage page) {
        BrixNode node = (BrixNode) page.getModelObject();
        PageInfo info = new PageInfo(page.getNumericId(), page.getCurrentVersionNumber(), page.getPageMapName());

        // This is a URL for redirect. Allow components to contribute state to
        // URL if they want to
        final BrixPageParameters parameters = page.getBrixPageParameters();
        page.visitChildren(PageParametersAware.class, new Component.IVisitor<Component>() {
            public Object component(Component component) {
                ((PageParametersAware) component).contributeToPageParameters(parameters);
                return Component.IVisitor.CONTINUE_TRAVERSAL;
            }
        });

        return encode(node, parameters, info, null);
    }

    private CharSequence encode(BrixNode node, BrixPageParameters parameters, PageInfo info, String iface) {
        BrixRequestCycleProcessor processor = (BrixRequestCycleProcessor) RequestCycle.get().getProcessor();
        return encode(processor.getUriPathForNode(node).toString(), parameters, info, iface);
    }

    private CharSequence encode(String nodeURL, BrixPageParameters parameters, PageInfo info, String iface) {
        StringBuilder builder = new StringBuilder();

        if (nodeURL.startsWith("/")) {
            nodeURL = nodeURL.substring(1);
        }

        builder.append(urlEncodePath(new Path(nodeURL, false)));

        boolean skipFirstSlash = builder.charAt(builder.length() - 1) == '/';

        for (int i = 0; i < parameters.getIndexedParamsCount(); ++i) {
            if (!skipFirstSlash)
                builder.append('/');
            else
                skipFirstSlash = false;

            final StringValue value = parameters.getIndexedParam(i);
            final String s = value.toString();

            if (s != null)
                builder.append(urlEncode(s));
        }

        Set<String> keys = parameters.getQueryParamKeys();
        if (info != null || !keys.isEmpty()) {
            builder.append("?");
        }

        if (info != null) {
            builder.append(info.toString());
        }

        boolean first = (info == null);

        for (String key : keys) {
            List<StringValue> values = parameters.getQueryParams(key);
            for (StringValue value : values) {
                if (first) {
                    first = false;
                } else {
                    builder.append("&");
                }
                builder.append(urlEncode(key));
                builder.append("=");
                builder.append(urlEncode(value.toString()));
            }
        }

        if (iface != null) {
            if (!first) {
                builder.append("&");
            }
            builder.append(getInterfaceParameter());
            builder.append("=");
            builder.append(iface);
        }

        return builder.toString();
    }

    private String urlEncodePath(Path path) {
        StringBuilder res = new StringBuilder(path.size());
        boolean first = true;
        for (String s : path) {
            if (first) {
                first = false;
            } else {
                res.append("/");
            }
            res.append(urlEncode(s));
        }
        return res.toString();
    }

    /**
     * Url encodes a string
     *
     * @param string string to be encoded
     * @return encoded string
     */
    public static String urlEncode(String string) {
        try {
            return URLEncoder.encode(string,
                    Application.get().getRequestCycleSettings().getResponseRequestEncoding());
        } catch (UnsupportedEncodingException e) {
            log.error(e.getMessage(), e);
            return string;
        }
    }

    // -------------------------- INNER CLASSES --------------------------

    public static final class HomePage extends WebPage {
    }

    /**
     * Possible string representation of PageInfo: <ul> <li>pageId <li>pageId.version <li>pageMap (only if pageMap starts
     * with a letter) <li>.pageMap <li>pageMap.pageId.version <li>pageMap.pageId (only if pageMap name starts with a
     * letter) </ul>
     *
     * @author Matej Knopp
     */
    protected static class PageInfo {
        private final Integer pageId;
        private final Integer versionNumber;
        private final String pageMapName;

        /**
         * Construct.
         *
         * @param pageId
         * @param versionNumber
         * @param pageMapName
         */
        public PageInfo(Integer pageId, Integer versionNumber, String pageMapName) {
            if ((pageId == null && (versionNumber != null || pageMapName == null))
                    || (versionNumber == null && (pageId != null || pageMapName == null))) {
                throw new IllegalArgumentException(
                        "Either both pageId and versionNumber must be null or none of them.");
            }
            this.pageId = pageId;
            this.versionNumber = versionNumber;
            this.pageMapName = pageMapName;
        }

        /**
         * @return
         */
        public Integer getPageId() {
            return pageId;
        }

        /**
         * @return
         */
        public Integer getVersionNumber() {
            return versionNumber;
        }

        /**
         * @return
         */
        public String getPageMapName() {
            return pageMapName;
        }

        private static char getPageInfoSeparator() {
            return '.';
        }

        /**
         * <ul> <li>pageId <li>pageId.version <li>pageMap (only in if pagemap starts with a letter) <li>.pageMap
         * <li>pageMap.pageId (only in if pageMap name starts with a letter) <li>pageMap.pageId.version </ul>
         */
        public String toString() {
            String pageMapName = this.pageMapName;

            // we don't need to encode the pageMapName when the pageId is unique
            // per session
            if (pageMapName != null && pageId != null && Application.exists()
                    && Application.get().getSessionSettings().isPageIdUniquePerSession()) {
                pageMapName = null;
            }

            AppendingStringBuffer buffer = new AppendingStringBuffer(5);

            final boolean pmEmpty = Strings.isEmpty(pageMapName);
            final boolean pmContainsLetter = !pmEmpty && !isNumber(pageMapName);

            if (pageId != null && pmEmpty && versionNumber.intValue() == 0) {
                // pageId
                buffer.append(pageId);
            } else if (pageId != null && pmEmpty && versionNumber.intValue() != 0) {
                // pageId.version
                buffer.append(pageId);
                buffer.append(getPageInfoSeparator());
                buffer.append(versionNumber);
            } else if (pageId == null && pmContainsLetter) {
                // pageMap (must start with letter)
                buffer.append(pageMapName);
            } else if (pageId == null && !pmEmpty && !pmContainsLetter) {
                // .pageMap
                buffer.append(getPageInfoSeparator());
                buffer.append(pageMapName);
            } else if (pmContainsLetter && pageId != null && versionNumber.intValue() == 0) {
                // pageMap.pageId (pageMap must start with a letter)
                buffer.append(pageMapName);
                buffer.append(getPageInfoSeparator());
                buffer.append(pageId);
            } else if (!pmEmpty && pageId != null) {
                // pageMap.pageId.pageVersion
                buffer.append(pageMapName);
                buffer.append(getPageInfoSeparator());
                buffer.append(pageId);
                buffer.append(getPageInfoSeparator());
                buffer.append(versionNumber);
            }

            return buffer.toString();
        }

        /**
         * Method that rigidly checks if the string consists of digits only.
         *
         * @param string
         * @return
         */
        private static boolean isNumber(String string) {
            if (string == null || string.length() == 0) {
                return false;
            }
            for (int i = 0; i < string.length(); ++i) {
                if (Character.isDigit(string.charAt(i)) == false) {
                    return false;
                }
            }
            return true;
        }

        /**
         * <ul> <li>pageId <li>pageId.version <li>pageMap (only in if pagemap starts with a letter) <li>.pageMap
         * <li>pageMap.pageId (only in if pageMap name starts with a letter) <li>pageMap.pageId.version </ul>
         *
         * @param src
         * @return
         */
        public static PageInfo parsePageInfo(String src) {
            if (src == null || src.length() == 0) {
                return null;
            }

            String segments[] = Strings.split(src, getPageInfoSeparator());

            if (segments.length > 3) {
                return null;
            }

            // go through the segments to determine if they don't contain
            // invalid characters
            for (int i = 0; i < segments.length; ++i) {
                for (int j = 0; j < segments[i].length(); ++j) {
                    char c = segments[i].charAt(j);
                    if (!Character.isLetterOrDigit(c) && c != '-' && c != '_') {
                        return null;
                    }
                }
            }

            if (segments.length == 1 && isNumber(segments[0])) {
                // pageId
                return new PageInfo(Integer.valueOf(segments[0]), new Integer(0), null);
            } else if (segments.length == 2 && isNumber(segments[0]) && isNumber(segments[1])) {
                // pageId:pageVersion
                return new PageInfo(Integer.valueOf(segments[0]), Integer.valueOf(segments[1]), null);
            } else if (segments.length == 1 && !isNumber(segments[0])) {
                // pageMap (starts with letter)
                return new PageInfo(null, null, segments[0]);
            } else if (segments.length == 2 && segments[0].length() == 0) {
                // .pageMap
                return new PageInfo(null, null, segments[1]);
            } else if (segments.length == 2 && !isNumber(segments[0]) && isNumber(segments[1])) {
                // pageMap.pageId (pageMap starts with letter)
                return new PageInfo(Integer.valueOf(segments[1]), new Integer(0), segments[0]);
            } else if (segments.length == 3) {
                if (segments[2].length() == 0 && isNumber(segments[1])) {
                    // we don't encode it like this, but we still should be able
                    // to parse it
                    // pageMapName.pageId.
                    return new PageInfo(Integer.valueOf(segments[1]), new Integer(0), segments[0]);
                } else if (isNumber(segments[1]) && isNumber(segments[2])) {
                    // pageMapName.pageId.pageVersion
                    return new PageInfo(Integer.valueOf(segments[1]), Integer.valueOf(segments[2]), segments[0]);
                }
            }

            return null;
        }
    }
}