001/*
002 * (C) Copyright 2006-2009 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Nuxeo - initial API and implementation
016 *
017 * $Id$
018 */
019
020package org.nuxeo.ecm.platform.web.common.requestcontroller.service;
021
022import java.util.HashMap;
023import java.util.LinkedHashMap;
024import java.util.Map;
025import java.util.regex.Matcher;
026import java.util.regex.Pattern;
027
028import javax.servlet.FilterConfig;
029import javax.servlet.http.HttpServletRequest;
030
031import org.apache.commons.logging.Log;
032import org.apache.commons.logging.LogFactory;
033import org.nuxeo.runtime.model.ComponentInstance;
034import org.nuxeo.runtime.model.DefaultComponent;
035
036/**
037 * Runtime component that implements the {@link RequestControllerManager} interface. Contains both the Extension point
038 * logic and the service implementation.
039 *
040 * @author tiry
041 */
042public class RequestControllerService extends DefaultComponent implements RequestControllerManager {
043
044    public static final String FILTER_CONFIG_EP = "filterConfig";
045
046    public static final String CORS_CONFIG_EP = "corsConfig";
047
048    /**
049     * @since 6.0
050     */
051    public static final String HEADERS_CONFIG_EP = "responseHeaders";
052
053    private static final Log log = LogFactory.getLog(RequestControllerService.class);
054
055    protected static final Map<String, FilterConfigDescriptor> grantPatterns = new LinkedHashMap<String, FilterConfigDescriptor>();
056
057    protected static final Map<String, FilterConfigDescriptor> denyPatterns = new LinkedHashMap<String, FilterConfigDescriptor>();
058
059    // @GuardedBy("itself")
060    protected static final Map<String, RequestFilterConfig> configCache = new LRUCachingMap<String, RequestFilterConfig>(
061            250);
062
063    protected static final Map<String, FilterConfig> filterConfigCache = new LRUCachingMap<>(250);
064
065    protected static final NuxeoCorsFilterDescriptorRegistry corsFilterRegistry = new NuxeoCorsFilterDescriptorRegistry();
066
067    protected static final NuxeoHeaderDescriptorRegistry headersRegistry = new NuxeoHeaderDescriptorRegistry();
068
069    protected Map<String, String> headersCache;
070
071    @Override
072    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
073        if (FILTER_CONFIG_EP.equals(extensionPoint)) {
074            FilterConfigDescriptor desc = (FilterConfigDescriptor) contribution;
075            registerFilterConfig(desc);
076        } else if (CORS_CONFIG_EP.equals(extensionPoint)) {
077            corsFilterRegistry.addContribution((NuxeoCorsFilterDescriptor) contribution);
078        } else if (HEADERS_CONFIG_EP.equals(extensionPoint)) {
079            headersRegistry.addContribution((NuxeoHeaderDescriptor) contribution);
080        } else {
081            log.error("Unknown ExtensionPoint " + extensionPoint);
082        }
083    }
084
085    public void registerFilterConfig(String name, String pattern, boolean grant, boolean tx, boolean sync,
086            boolean cached, boolean isPrivate, String cacheTime) {
087        FilterConfigDescriptor desc = new FilterConfigDescriptor(name, pattern, grant, tx, sync, cached, isPrivate,
088                cacheTime);
089        registerFilterConfig(desc);
090    }
091
092    public void registerFilterConfig(FilterConfigDescriptor desc) {
093        if (desc.isGrantRule()) {
094            grantPatterns.put(desc.getName(), desc);
095            log.debug("Registered grant filter config");
096        } else {
097            denyPatterns.put(desc.getName(), desc);
098            log.debug("Registered deny filter config");
099        }
100    }
101
102    @Override
103    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
104        if (CORS_CONFIG_EP.equals(extensionPoint)) {
105            corsFilterRegistry.removeContribution((NuxeoCorsFilterDescriptor) contribution);
106        }
107    }
108
109    /* Service interface */
110
111    @Override
112    public FilterConfig getCorsConfigForRequest(HttpServletRequest request) {
113        String uri = request.getRequestURI();
114        FilterConfig filterConfig = null;
115        synchronized (filterConfigCache) {
116            filterConfig = filterConfigCache.get(uri);
117        }
118
119        if (filterConfig == null) {
120            filterConfig = computeCorsFilterConfigForUri(uri);
121            synchronized (filterConfigCache) {
122                filterConfigCache.put(uri, filterConfig);
123            }
124        }
125
126        return filterConfig;
127    }
128
129    public FilterConfig computeCorsFilterConfigForUri(String uri) {
130        NuxeoCorsFilterDescriptor descriptor = corsFilterRegistry.getFirstMatchingDescriptor(uri);
131        return descriptor != null ? descriptor.buildFilterConfig() : null;
132    }
133
134    public RequestFilterConfig getConfigForRequest(HttpServletRequest request) {
135        String uri = request.getRequestURI();
136        RequestFilterConfig config = null;
137
138        synchronized (configCache) {
139            config = configCache.get(uri);
140        }
141        if (config == null) {
142            config = computeConfigForRequest(uri);
143            synchronized (configCache) {
144                configCache.put(uri, config);
145            }
146        }
147        return config;
148    }
149
150    public RequestFilterConfig computeConfigForRequest(String uri) {
151        // handle deny patterns
152        for (FilterConfigDescriptor desc : denyPatterns.values()) {
153            Pattern pat = desc.getCompiledPattern();
154            Matcher m = pat.matcher(uri);
155            if (m.matches()) {
156                return new RequestFilterConfigImpl(false, false, false, false, false, "");
157            }
158        }
159
160        // handle grant patterns
161        for (FilterConfigDescriptor desc : grantPatterns.values()) {
162            Pattern pat = desc.getCompiledPattern();
163            Matcher m = pat.matcher(uri);
164            if (m.matches()) {
165                return new RequestFilterConfigImpl(desc.useSync(), desc.useTx(), desc.useTxBuffered(), desc.isCached(),
166                        desc.isPrivate(), desc.getCacheTime());
167            }
168        }
169
170        // return deny by default
171        return new RequestFilterConfigImpl(false, false, false, false, false, "");
172    }
173
174    public Map<String, String> getResponseHeaders() {
175        if (headersCache == null) {
176            headersCache = buildHeadersCache();
177        }
178        return headersCache;
179    }
180
181    protected static Map<String, String> buildHeadersCache() {
182        Map<String, String> headersCache = new HashMap<String, String>();
183        for (NuxeoHeaderDescriptor header : headersRegistry.descs.values()) {
184            headersCache.put(header.name, header.getValue());
185        }
186        return headersCache;
187    }
188}