org.broadleafcommerce.common.web.resource.BroadleafResourceHttpRequestHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.broadleafcommerce.common.web.resource.BroadleafResourceHttpRequestHandler.java

Source

/*
 * #%L
 * BroadleafCommerce Common Libraries
 * %%
 * Copyright (C) 2009 - 2013 Broadleaf Commerce
 * %%
 * 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.
 * #L%
 */
package org.broadleafcommerce.common.web.resource;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.broadleafcommerce.common.classloader.release.ThreadLocalManager;
import org.broadleafcommerce.common.extension.ExtensionResultHolder;
import org.broadleafcommerce.common.resource.GeneratedResource;
import org.broadleafcommerce.common.resource.service.ResourceBundlingService;
import org.broadleafcommerce.common.resource.service.ResourceMinificationService;
import org.broadleafcommerce.common.web.BroadleafRequestContext;
import org.broadleafcommerce.common.web.BroadleafSandBoxResolver;
import org.broadleafcommerce.common.web.BroadleafSiteResolver;
import org.broadleafcommerce.common.web.BroadleafThemeResolver;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.util.StreamUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

public class BroadleafResourceHttpRequestHandler extends ResourceHttpRequestHandler {
    private static final Log LOG = LogFactory.getLog(BroadleafResourceHttpRequestHandler.class);

    // XML Configured generated resource handlers
    protected List<AbstractGeneratedResourceHandler> handlers;
    protected List<AbstractGeneratedResourceHandler> sortedHandlers;

    @javax.annotation.Resource(name = "blResourceBundlingService")
    protected ResourceBundlingService bundlingService;

    @javax.annotation.Resource(name = "blResourceMinificationService")
    protected ResourceMinificationService minifyService;

    @javax.annotation.Resource(name = "blResourceRequestExtensionManager")
    protected ResourceRequestExtensionManager extensionManager;

    @javax.annotation.Resource(name = "blSiteResolver")
    protected BroadleafSiteResolver siteResolver;

    @javax.annotation.Resource(name = "blSandBoxResolver")
    protected BroadleafSandBoxResolver sbResolver;

    @javax.annotation.Resource(name = "blThemeResolver")
    protected BroadleafThemeResolver themeResolver;

    @Value("${global.admin.prefix}")
    protected String globalAdminPrefix;

    @Value("${global.admin.url}")
    protected String globalAdminUrl;

    /**
     * Checks to see if the requested path corresponds to a registered bundle. If so, returns the generated bundle.
     * Otherwise, checks to see if any of the configured GeneratedResourceHandlers can handle the given request.
     * If neither of those cases match, delegates to the normal ResourceHttpRequestHandler
     */
    @Override
    protected Resource getResource(HttpServletRequest request) {
        establishThinRequestContext();
        return getResourceInternal(request);
    }

    protected Resource getResourceInternal(HttpServletRequest request) {
        String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
        if (bundlingService.hasBundle(path)) {
            return bundlingService.getBundle(path);
        }

        Resource unminifiedResource = null;

        if (sortedHandlers == null && handlers != null) {
            sortHandlers();
        }

        if (sortedHandlers != null) {
            for (AbstractGeneratedResourceHandler handler : sortedHandlers) {
                if (handler.canHandle(path)) {
                    unminifiedResource = handler.getResource(path, getLocations());
                    break;
                }
            }
        }

        if (unminifiedResource == null) {
            ExtensionResultHolder erh = new ExtensionResultHolder();
            extensionManager.getProxy().getOverrideResource(path, erh);
            if (erh.getContextMap().get(ResourceRequestExtensionHandler.RESOURCE_ATTR) != null) {
                unminifiedResource = (Resource) erh.getContextMap()
                        .get(ResourceRequestExtensionHandler.RESOURCE_ATTR);
            }
        }

        if (unminifiedResource == null) {
            unminifiedResource = super.getResource(request);
        }

        try {
            if (!minifyService.getEnabled() || !minifyService.getAllowSingleMinification()) {
                return unminifiedResource;
            }
        } finally {
            ThreadLocalManager.remove();
        }

        LOG.warn("Minifying individual file - this should only be used in development to trace down particular "
                + "files that are causing an exception in the minification service. The results of the minification "
                + "performed outside of a bundle are not stored to disk.");

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] bytes = null;
        InputStream is = null;
        try {
            is = unminifiedResource.getInputStream();
            StreamUtils.copy(is, baos);
            bytes = baos.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                is.close();
                baos.close();
            } catch (IOException e2) {
                throw new RuntimeException("Could not close input stream", e2);
            }
        }

        LOG.debug("Attempting to minifiy " + unminifiedResource.getFilename());
        byte[] minifiedBytes = minifyService.minify(unminifiedResource.getFilename(), bytes);

        return new GeneratedResource(minifiedBytes, unminifiedResource.getFilename());
    }

    public boolean isBundleRequest(HttpServletRequest request) {
        String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
        return bundlingService.hasBundle(path);
    }

    /**
     * @return a clone of the locations list that is in {@link ResourceHttpRequestHandler}. Note that we must use
     * reflection to access this field as it is marked private.
     */
    @SuppressWarnings("unchecked")
    public List<Resource> getLocations() {
        try {
            List<Resource> locations = (List<Resource>) FieldUtils.readField(this, "locations", true);
            return new ArrayList<Resource>(locations);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    protected void establishThinRequestContext() {
        BroadleafRequestContext oldBrc = BroadleafRequestContext.getBroadleafRequestContext();
        if (oldBrc == null || oldBrc.getSite() == null || oldBrc.getTheme() == null) {
            // Resolving sites and sandboxes is often dependent on having a security context present in the request.
            // For example, resolving a sandbox requires the current user to have the BLC_ADMIN_USER in his Authentication.
            // For performance reasons, we do not go through the entire Spring Security filter chain on requests
            // for resources like JavaScript and CSS files. However, when theming is enabled, we potentially have to
            // resolve a specific version of the theme for a sandbox so that we can replace variables appropriately. This
            // then depends on the sandbox being resolved, which requires the Authentication object to be present.
            // We will grab the Authentication object associated with this user's session and set it on the
            // SecurityContextHolder since Spring Security will be bypassed.
            HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                    .getRequest();
            HttpSession session = req.getSession(false);
            SecurityContext ctx = readSecurityContextFromSession(session);
            if (ctx != null) {
                SecurityContextHolder.setContext(ctx);
            }

            BroadleafRequestContext newBrc = new BroadleafRequestContext();
            if (!isGlobalAdmin(req)) {
                ServletWebRequest swr = new ServletWebRequest(req);
                newBrc.setSite(siteResolver.resolveSite(swr, true));
                newBrc.setSandBox(sbResolver.resolveSandBox(swr, newBrc.getSite()));
                BroadleafRequestContext.setBroadleafRequestContext(newBrc);
                newBrc.setTheme(themeResolver.resolveTheme(swr));
            }
        }
    }

    protected boolean isGlobalAdmin(HttpServletRequest request) {
        String uri = request.getRequestURI();
        if (!StringUtils.isEmpty(globalAdminPrefix)) {
            if (globalAdminPrefix.equals(getContextName(request))) {
                return true;
            } else {
                if (!StringUtils.isEmpty(globalAdminUrl)) {
                    return uri.startsWith(globalAdminUrl);
                }
            }
        }
        return false;
    }

    protected String getContextName(HttpServletRequest request) {
        String contextName = request.getServerName();
        int pos = contextName.indexOf('.');
        if (pos >= 0) {
            contextName = contextName.substring(0, contextName.indexOf('.'));
        }
        return contextName;
    }

    // **NOTE** This method is lifted from HttpSessionSecurityContextRepository
    protected SecurityContext readSecurityContextFromSession(HttpSession httpSession) {
        if (httpSession == null) {
            return null;
        }

        Object ctxFromSession = httpSession
                .getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
        if (ctxFromSession == null) {
            return null;
        }

        if (!(ctxFromSession instanceof SecurityContext)) {
            return null;
        }

        return (SecurityContext) ctxFromSession;
    }

    protected void sortHandlers() {
        sortedHandlers = new ArrayList<AbstractGeneratedResourceHandler>(handlers);
        Collections.sort(sortedHandlers, new Comparator<AbstractGeneratedResourceHandler>() {
            @Override
            public int compare(AbstractGeneratedResourceHandler o1, AbstractGeneratedResourceHandler o2) {
                return new Integer(o1.getOrder()).compareTo(o2.getOrder());
            }
        });
    }

    /* *********** */
    /* BOILERPLATE */
    /* *********** */

    public List<AbstractGeneratedResourceHandler> getHandlers() {
        if (sortedHandlers == null && handlers != null) {
            sortHandlers();
        }
        return sortedHandlers;
    }

    public void setHandlers(List<AbstractGeneratedResourceHandler> handlers) {
        this.handlers = handlers;
    }

}