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