Package org.jahia.services.render

Source Code of org.jahia.services.render.URLResolver

/**
* This file is part of Jahia, next-generation open source CMS:
* Jahia's next-generation, open source CMS stems from a widely acknowledged vision
* of enterprise application convergence - web, search, document, social and portal -
* unified by the simplicity of web content management.
*
* For more information, please visit http://www.jahia.com.
*
* Copyright (C) 2002-2011 Jahia Solutions Group SA. All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* As a special exception to the terms and conditions of version 2.0 of
* the GPL (or any later version), you may redistribute this Program in connection
* with Free/Libre and Open Source Software ("FLOSS") applications as described
* in Jahia's FLOSS exception. You should have received a copy of the text
* describing the FLOSS exception, and it is also available here:
* http://www.jahia.com/license
*
* Commercial and Supported Versions of the program (dual licensing):
* alternatively, commercial and supported versions of the program may be used
* in accordance with the terms and conditions contained in a separate
* written agreement between you and Jahia Solutions Group SA.
*
* If you are unsure which license is appropriate for your use,
* please contact the sales department at sales@jahia.com.
*/

package org.jahia.services.render;

import static org.jahia.api.Constants.LIVE_WORKSPACE;

import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.jcr.AccessDeniedException;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.jahia.services.cache.Cache;
import org.jahia.services.sites.JahiaSite;
import org.slf4j.Logger;
import org.jahia.bin.Render;
import org.jahia.exceptions.JahiaException;
import org.jahia.exceptions.JahiaNotFoundException;
import org.jahia.registries.ServicesRegistry;
import org.jahia.services.SpringContextSingleton;
import org.jahia.services.content.JCRCallback;
import org.jahia.services.content.JCRContentUtils;
import org.jahia.services.content.JCRNodeWrapper;
import org.jahia.services.content.JCRSessionFactory;
import org.jahia.services.content.JCRSessionWrapper;
import org.jahia.services.content.JCRTemplate;
import org.jahia.services.content.decorator.JCRSiteNode;
import org.jahia.services.seo.VanityUrl;
import org.jahia.services.seo.jcr.VanityUrlManager;
import org.jahia.services.seo.jcr.VanityUrlService;
import org.jahia.settings.SettingsBean;
import org.jahia.utils.LanguageCodeConverters;

/**
* Class to resolve URLs and URL paths, so that the workspace / locale / node-path information is
* returned.
*
* There are also convenience methods to directly return the Node or Resource where the URL points to.
*
* The method also considers vanity URL mappings and resolves them to the mapped nodes, so that
* the URL resolver will return the info as if no mapping have been used.
*
* @author Benjamin Papez
*
*/
public class URLResolver {

    private static final Locale DEFAULT_LOCALE = Locale.ENGLISH;

    private static final String DEFAULT_WORKSPACE = LIVE_WORKSPACE;

    private static final String VANITY_URL_NODE_PATH_SEGMENT = "/" + VanityUrlManager.VANITYURLMAPPINGS_NODE + "/";

    private static Logger logger = org.slf4j.LoggerFactory.getLogger(URLResolver.class);

    private static String[] servletsAllowingUrlMapping = new String[] {
            StringUtils.substringAfterLast(Render.getRenderServletPath(), "/")
    };

    private String urlPathInfo = null;
    private String servletPart = "";
    private String workspace;
    private Locale locale;
    private String path = "";
    private String siteKey;
    private String siteKeyByServerName;
    private boolean mappable = false;

    private String redirectUrl;
    private String vanityUrl;
    private String method;
    private Date versionDate;
    private String versionLabel;
    private static final String CACHE_KEY_SEPARATOR = "___";

    public void setRenderContext(RenderContext renderContext) {
        this.renderContext = renderContext;
    }

    private RenderContext renderContext;
    private Map<String, JCRNodeWrapper> resolvedNodes = new ConcurrentHashMap<String, JCRNodeWrapper>();
    private Cache nodePathCache;
    private Cache siteInfoCache;

    /**
     * Initializes an instance of this class. This constructor is mainly used when
     * resolving URLs of incoming requests.
     *
     * @param urlPathInfo  the path info (usually obtained with @link javax.servlet.http.HttpServletRequest.getPathInfo())
     * @param serverName  the server name (usually obtained with @link javax.servlet.http.HttpServletRequest.getServerName())
     * @param request  the current HTTP servlet request object
     */
    protected URLResolver(String urlPathInfo, String serverName, HttpServletRequest request, Cache nodePathCache, Cache siteInfoCache) {
        super();
        this.nodePathCache = nodePathCache;
        this.siteInfoCache = siteInfoCache;
        this.method = request.getMethod();
        this.urlPathInfo = urlPathInfo;
        if (urlPathInfo != null) {
            servletPart = StringUtils.substring(getUrlPathInfo(), 1,
                    StringUtils.indexOf(getUrlPathInfo(), "/", 1));
            path = StringUtils.substring(getUrlPathInfo(), servletPart.length() + 2,
                    getUrlPathInfo().length());
        }
        if (!resolveUrlMapping(serverName)) {
            init();
            if (!URLGenerator.isLocalhost(serverName) && isMappable()
                    && SettingsBean.getInstance().isPermanentMoveForVanityURL()) {
                try {
                    if (siteKeyByServerName != null && siteKeyByServerName.equals(getNode().getResolveSite().getSiteKey()) ) {
                        VanityUrl defaultVanityUrl = getVanityUrlService()
                                .getVanityUrlForWorkspaceAndLocale(getNode(),
                                        workspace, locale);
                        if (defaultVanityUrl != null && defaultVanityUrl.isActive()) {
                            if (request == null || StringUtils.isEmpty(request.getQueryString())) {
                                setRedirectUrl(defaultVanityUrl.getUrl());
                            } else {
                                setRedirectUrl(defaultVanityUrl.getUrl() + "?" + request.getQueryString());
                            }
                        }
                    }
                } catch (PathNotFoundException e) {
                    logger.debug("Path not found : " + urlPathInfo);
                } catch (AccessDeniedException e) {
                    logger.debug("User has no access to the resource, so there will not be a redirection");
                } catch (RepositoryException e) {
                    logger.warn("Error when trying to check whether there is a vanity URL mapping", e);
                }
            }
        }
    }

    /**
     * Initializes an instance of this class. This constructor is mainly used when
     * trying to find mapping for URLs in outgoing requests.
     *
     * @param url   URL in HTML links of outgoing requests
     * @param context  The current request in order to obtain the context path
     */
    protected URLResolver(String url, RenderContext context, Cache nodePathCache, Cache siteInfoCache) {
        this.nodePathCache = nodePathCache;
        this.siteInfoCache = siteInfoCache;
        method = context.getRequest().getMethod();
        renderContext = context;
        String contextPath = context.getRequest().getContextPath();

        this.urlPathInfo = StringUtils.substringAfter(url, contextPath + context.getServletPath());
        this.servletPart = StringUtils.substringAfterLast(context.getServletPath(), "/");

        if (!StringUtils.isEmpty(urlPathInfo)) {
            path = getUrlPathInfo().substring(1);
            init();
        }
    }

    private void init() {
        workspace = verifyWorkspace(StringUtils.substringBefore(path, "/"));
        path = StringUtils.substringAfter(path, "/");
        String langCode = StringUtils.substringBefore(path, "/");

        locale = StringUtils.isEmpty(langCode) ? DEFAULT_LOCALE
                : LanguageCodeConverters.languageCodeToLocale(langCode);
        path = "/" + StringUtils.substringAfter(path, "/");

        // TODO: this is perhaps a temporary limitation as URL points to special templates, when
        // there are more than one dots - and the path needs to end with .html
        String lastPart = StringUtils.substringAfterLast(path, "/");
        int indexOfHTMLSuffix = lastPart.indexOf(".html");
        if (isServletAllowingUrlMapping() && indexOfHTMLSuffix > 0
                && lastPart.indexOf(".") == indexOfHTMLSuffix && lastPart.endsWith(".html")) {
            mappable = true;
        }
    }

    private boolean isServletAllowingUrlMapping() {
        boolean isServletAllowingUrlMapping = false;
        for (String servletAllowingUrlMapping : servletsAllowingUrlMapping) {
            if (servletAllowingUrlMapping.equals(servletPart)) {
                isServletAllowingUrlMapping = true;
                break;
            }
        }
        return isServletAllowingUrlMapping;
    }

    protected boolean resolveUrlMapping(String serverName) {
        boolean mappingResolved = false;

        try {
            JahiaSite site = ServicesRegistry.getInstance().getJahiaSitesService().getSiteByServerName(serverName);
            if (site != null) {
                siteKeyByServerName = site.getSiteKey();
            }
        } catch (JahiaException e) {
            logger.warn("Error finding site via servername: " + serverName, e);
        }

        if (getSiteKey() == null) {
            String siteKeyInPath = StringUtils.substringBetween(getPath(), "/sites/", "/");
            if (!StringUtils.isEmpty(siteKeyInPath)) {
                setSiteKey(siteKeyInPath);
            } else if (!URLGenerator.isLocalhost(serverName)) {
                if (siteKeyByServerName != null) {
                    setSiteKey(siteKeyByServerName);
                }
            }
        }

        if (isServletAllowingUrlMapping() && !URLGenerator.isLocalhost(serverName)) {
            String tempPath = null;
            try {
                String tempWorkspace = verifyWorkspace(StringUtils.substringBefore(getPath(), "/"));
                tempPath = StringUtils.substringAfter(getPath(), "/");
                List<VanityUrl> vanityUrls = getVanityUrlService()
                        .findExistingVanityUrls("/" + tempPath,
                                StringUtils.EMPTY, tempWorkspace);
                VanityUrl resolvedVanityUrl = null;
                for (VanityUrl vanityUrl : vanityUrls) {
                    if (vanityUrl.isActive()
                            && (StringUtils.isEmpty(getSiteKey()) || getSiteKey().equals(
                            vanityUrl.getSite()))) {
                        resolvedVanityUrl = vanityUrl;
                        break;
                    }
                }
                if (resolvedVanityUrl != null) {
                    workspace = tempWorkspace;
                    locale = StringUtils.isEmpty(resolvedVanityUrl
                            .getLanguage()) ? DEFAULT_LOCALE
                            : LanguageCodeConverters
                            .languageCodeToLocale(resolvedVanityUrl
                                    .getLanguage());
                    path = StringUtils.substringBefore(resolvedVanityUrl
                            .getPath(), VANITY_URL_NODE_PATH_SEGMENT)
                            + ".html";
                    setVanityUrl(resolvedVanityUrl.getUrl());
                    if (SettingsBean.getInstance()
                            .isPermanentMoveForVanityURL()
                            && !resolvedVanityUrl.isDefaultMapping()) {
                        VanityUrl defaultVanityUrl = getVanityUrlService()
                                .getVanityUrlForWorkspaceAndLocale(getNode(),
                                        workspace, locale);
                        if (defaultVanityUrl != null
                                && defaultVanityUrl.isActive() && !resolvedVanityUrl.equals(defaultVanityUrl)) {
                            setRedirectUrl(defaultVanityUrl.getUrl());
                        }
                    }
                    mappingResolved = true;
                }
            } catch (RepositoryException e) {
                logger.warn("Error when trying to resolve URL mapping: "
                        + tempPath, e);
            }
        }
        return mappingResolved;
    }

    /**
     * Gets the pathInfo of the given URL (@link javax.servlet.http.HttpServletRequest.getPathInfo())
     * @return the pathInfo of the given URL
     */
    public String getUrlPathInfo() {
        return urlPathInfo;
    }

    public String getServletPart() {
        return servletPart;
    }

    /**
     * Gets the workspace of the request resolved by the URL
     * @return the workspace of the given URL
     */
    public String getWorkspace() {
        return workspace;
    }

    /**
     * Gets the locale of the request resolved by the URL
     * @return the locale of the given URL
     */
    public Locale getLocale() {
        return locale;
    }

    /**
     * Gets the content node path of the request resolved by the URL
     * @return the content node path of the given URL
     */
    public String getPath() {
        return path;
    }

    /**
     * Creates a node from the path in the URL.
     *
     * @return The node, if found
     * @throws PathNotFoundException
     *             if the resource cannot be resolved
     * @throws RepositoryException
     */
    public JCRNodeWrapper getNode() throws RepositoryException {
        return resolveNode(getWorkspace(), getLocale(), getPath());
    }

    /**
     * Creates a resource from the path in the URL.
     * <p/>
     * The path should looks like : [nodepath][.templatename].[templatetype] or [nodepath].[templatetype]
     *
     * Workspace, locale and path are taken from the given resolved URL.
     *
     * @return The resource, if found
     * @throws PathNotFoundException
     *             if the resource cannot be resolved
     * @throws RepositoryException
     */
    public Resource getResource() throws RepositoryException {
        return resolveResource(getWorkspace(), getLocale(), getPath());
    }

    public Resource getResource(String path) throws RepositoryException {
        return resolveResource(getWorkspace(), getLocale(), path);
    }

    /**
     * Creates a node from the specified path.
     * <p/>
     * The path should looks like : [nodepath][.templatename].[templatetype] or [nodepath].[templatetype]
     *
     * @param workspace
     *            The workspace where to get the node
     * @param locale
     *            current locale
     * @param path
     *            The path of the node, in the specified workspace
     * @return The node, if found
     * @throws PathNotFoundException
     *             if the resource cannot be resolved
     * @throws RepositoryException
     */
    protected JCRNodeWrapper resolveNode(final String workspace,
                                         final Locale locale, final String path) throws RepositoryException {
        if (logger.isDebugEnabled()) {
            logger.debug("Resolving node for workspace '" + workspace
                    + "' locale '" + locale + "' and path '" + path + "'");
        }
        final String cacheKey = getCacheKey(workspace, locale, path);
        if (resolvedNodes.containsKey(cacheKey)) {
            return resolvedNodes.get(cacheKey);
        }
        JCRNodeWrapper node = null;
        String nodePath = path.endsWith("/*") ? path.substring(0, path.lastIndexOf("/*")) : path;
        SiteInfo siteInfo = null;
        if (nodePathCache.containsKey(cacheKey) && siteInfoCache.containsKey(cacheKey)) {
            nodePath = (String) nodePathCache.get(cacheKey);
            siteInfo = (SiteInfo) siteInfoCache.get(cacheKey);
        } else {
            nodePath = JCRTemplate.getInstance().doExecuteWithSystemSession(null,
                    workspace, new JCRCallback<String>() {
                        public String doInJCR(JCRSessionWrapper session)
                                throws RepositoryException {
                            String nodePath = JCRContentUtils.escapeNodePath(path.endsWith("/*") ? path.substring(0, path.lastIndexOf("/*")) : path);
                            JCRNodeWrapper node = null;
                            while (true) {
                                try {
                                    node = session.getNode(nodePath);
                                    break;
                                } catch (PathNotFoundException ex) {
                                    if (nodePath.lastIndexOf("/") < nodePath.lastIndexOf(".")) {
                                        nodePath = nodePath.substring(0, nodePath.lastIndexOf("."));
                                    } else {
                                        throw new PathNotFoundException("'" + nodePath + "'not found");
                                    }
                                }
                            }
                            nodePathCache.put(cacheKey, nodePath);
                            SiteInfo siteInfo = new SiteInfo(node.getResolveSite());
                            siteInfoCache.put(cacheKey, siteInfo);
                            return nodePath;
                        }
                    });
            siteInfo = (SiteInfo) siteInfoCache.get(cacheKey);
        }

        JCRSessionWrapper userSession = siteInfo != null
                && siteInfo.getDefaultLanguage() != null
                && siteInfo.isMixLanguagesActive() ? JCRSessionFactory
                .getInstance().getCurrentUserSession(workspace,
                        locale)
                : JCRSessionFactory
                .getInstance()
                .getCurrentUserSession(
                        workspace,
                        locale,
                        null);
        if (userSession.getVersionDate() == null)
            userSession.setVersionDate(versionDate);
        if (userSession.getVersionLabel() == null)
            userSession.setVersionLabel(versionLabel);
        try {
            node = userSession.getNode(nodePath);
        } catch (PathNotFoundException e) {
            throw new AccessDeniedException(path);
        }
        resolvedNodes.put(cacheKey, node);
        return node;

    }

    private String getCacheKey(final String workspace, final Locale locale, final String path) {
        StringBuilder builder = new StringBuilder(workspace);
        builder.append(CACHE_KEY_SEPARATOR);
        builder.append(locale.toString());
        builder.append(CACHE_KEY_SEPARATOR);
        builder.append(path);
        return builder.toString();
    }

    /**
     * Creates a resource from the specified path.
     * <p/>
     * The path should looks like : [nodepath][.templatename].[templatetype] or [nodepath].[templatetype]
     *
     * @param workspace
     *            The workspace where to get the node
     * @param locale
     *            current locale
     * @param path
     *            The path of the node, in the specified workspace
     * @return The resource, if found
     * @throws PathNotFoundException
     *             if the resource cannot be resolved
     * @throws RepositoryException
     */
    protected Resource resolveResource(final String workspace, final Locale locale, final String path)
            throws RepositoryException {
        if (logger.isDebugEnabled()) {
            logger.debug("Resolving resource for workspace '" + workspace
                    + "' locale '" + locale + "' and path '" + path + "'");
        }
        final URLResolver urlResolver = this;
        return JCRTemplate.getInstance().doExecuteWithSystemSession(null,
                workspace, locale, new JCRCallback<Resource>() {
                    public Resource doInJCR(JCRSessionWrapper session)
                            throws RepositoryException {
                        String ext = null;
                        String tpl = null;
                        String nodePath = JCRContentUtils.escapeNodePath(path);
                        JCRNodeWrapper node;
                        while (true) {
                            int i = nodePath.lastIndexOf('.');
                            if (i > nodePath.lastIndexOf('/')) {
                                if (ext == null) {
                                    ext = nodePath.substring(i + 1);
                                    if ("ajax".equals(ext)) {
                                        ext = null;
                                        if (renderContext != null) {
                                            renderContext.setAjaxRequest(true);
                                            HttpServletRequest req = renderContext.getRequest();
                                            if (req.getParameter("mainResource") != null) {
                                                Resource resource = urlResolver.getResource(req.getParameter(
                                                        "mainResource"));
                                                renderContext.setAjaxResource(resource);
                                            }
                                        }
                                    }
                                } else if (tpl == null) {
                                    tpl = nodePath.substring(i + 1);
                                } else {
                                    tpl = nodePath.substring(i + 1) + "." + tpl;
                                }
                                nodePath = nodePath.substring(0, i);
                            } else {
                                throw new PathNotFoundException("not found");
                            }
                            try {
                                node = session.getNode(nodePath);
                                break;
                            } catch (PathNotFoundException ex) {
                                // ignore it
                            }
                        }

                        JCRSiteNode site = node.getResolveSite();
                        JCRSessionWrapper userSession;

                        if (site != null) {
                            String defaultLanguage = site.getDefaultLanguage();
                            boolean mixLanguagesActive = site
                                    .isMixLanguagesActive();

                            if (defaultLanguage != null && mixLanguagesActive) {
                                userSession = JCRSessionFactory
                                        .getInstance()
                                        .getCurrentUserSession(
                                                workspace,
                                                locale,
                                                LanguageCodeConverters
                                                        .languageCodeToLocale(defaultLanguage));
                            } else {
                                userSession = JCRSessionFactory.getInstance()
                                        .getCurrentUserSession(workspace,
                                                locale);
                            }
                        } else {
                            userSession = JCRSessionFactory.getInstance()
                                    .getCurrentUserSession(workspace, locale);
                        }
                        if(userSession.getVersionDate()==null)
                            userSession.setVersionDate(versionDate);
                        if(userSession.getVersionLabel()==null)
                            userSession.setVersionLabel(versionLabel);

                        try {
                            node = userSession.getNode(nodePath);
                        } catch (PathNotFoundException e) {
                            throw new AccessDeniedException(path);
                        }

                        Resource r = new Resource(node, ext, tpl, Resource.CONFIGURATION_PAGE);
                        if (logger.isDebugEnabled()) {
                            logger.debug("Resolved resource: " + r);
                        }
                        return r;
                    }
                });
    }

    /**
     * Checks whether the URL points to a Jahia content object, which can be mapped to vanity URLs.
     * @return true if current node can be mapped to vanity URLs, otherwise false
     */
    public boolean isMappable() {
        return mappable;
    }


    /**
     * Checks whether the URL points to a Jahia content object, which can be mapped to vanity
     * URLs. If this is the case then also check if there already is a mapping for the current node.
     * @return true if mappings exist(ed) for the current node, otherwise false
     */
    public boolean isMapped() {
        boolean mapped = mappable;
        if (mapped) {
            try {
                JCRNodeWrapper node = getNode();
                if (node != null && !node.isNodeType(VanityUrlManager.JAHIAMIX_VANITYURLMAPPED)) {
                    mapped = false;
                }
            } catch (RepositoryException e) {
                logger.debug("Cannot check if node has the jmix:vanityUrlMapped mixin", e);
            }
        }
        return mapped;
    }

    private VanityUrlService getVanityUrlService() {
        return (VanityUrlService) SpringContextSingleton
                .getBean(VanityUrlService.class.getName());
    }

    /**
     * Gets the site-key of the request resolved by the URL
     * @return the site-key of the given URL
     */
    public String getSiteKey() {
        return siteKey;
    }

    /**
     * Sets the site-key resolved for the current URL
     * @param siteKey the site-key resolved for the current URL
     */
    public void setSiteKey(String siteKey) {
        this.siteKey = siteKey;
    }

    /**
     * If value is not null, then URL resolving encountered that the URL has
     * permanently been changed and thus a server-side redirect to the new
     * location should be triggered.
     * @return URL for the new location
     */
    public String getRedirectUrl() {
        return redirectUrl;
    }

    /**
     * Set the URL location for a redirection suggestion.
     * @param redirectUrl suggested vanity URL to redirect to
     */
    public void setRedirectUrl(String redirectUrl) {
        this.redirectUrl = redirectUrl;
    }

    public String getVanityUrl() {
        return vanityUrl;
    }

    public void setVanityUrl(String vanityUrl) {
        this.vanityUrl = vanityUrl;
    }
   
    protected String verifyWorkspace(String workspace) {
        if (StringUtils.isEmpty(workspace)) {
            workspace = DEFAULT_WORKSPACE;
        } else {
            if (!JCRContentUtils.isValidWorkspace(workspace)) {
                throw new JahiaNotFoundException("Unknown workspace '" + workspace + "'");
            }
        }
       
        return workspace;
    }

    public void setVersionDate(Date versionDate) {
        this.versionDate = versionDate;
    }

    public void setVersionLabel(String versionLabel) {
        this.versionLabel = versionLabel;
    }

    public Date getVersionDate() {
        return versionDate;
    }

    public String getVersionLabel() {
        return versionLabel;
    }
}
TOP

Related Classes of org.jahia.services.render.URLResolver

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.