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
038import com.thetransactioncompany.cors.CORSFilter;
039
040/**
041 * Runtime component that implements the {@link RequestControllerManager} interface. Contains both the Extension point
042 * logic and the service implementation.
043 *
044 * @author tiry
045 */
046public class RequestControllerService extends DefaultComponent implements RequestControllerManager {
047
048    public static final String FILTER_CONFIG_EP = "filterConfig";
049
050    public static final String CORS_CONFIG_EP = "corsConfig";
051
052    /**
053     * @since 6.0
054     */
055    public static final String HEADERS_CONFIG_EP = "responseHeaders";
056
057    private static final Log log = LogFactory.getLog(RequestControllerService.class);
058
059    protected final Map<String, FilterConfigDescriptor> grantPatterns = new LinkedHashMap<String, FilterConfigDescriptor>();
060
061    protected final Map<String, FilterConfigDescriptor> denyPatterns = new LinkedHashMap<String, FilterConfigDescriptor>();
062
063    // @GuardedBy("itself")
064    protected final Map<String, RequestFilterConfig> configCache = new LRUCachingMap<String, RequestFilterConfig>(250);
065
066    protected final NuxeoCorsFilterDescriptorRegistry corsFilterRegistry = new NuxeoCorsFilterDescriptorRegistry();
067
068    protected final NuxeoHeaderDescriptorRegistry headersRegistry = new NuxeoHeaderDescriptorRegistry();
069
070    @Override
071    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
072        if (FILTER_CONFIG_EP.equals(extensionPoint)) {
073            FilterConfigDescriptor desc = (FilterConfigDescriptor) contribution;
074            registerFilterConfig(desc);
075        } else if (CORS_CONFIG_EP.equals(extensionPoint)) {
076            corsFilterRegistry.addContribution((NuxeoCorsFilterDescriptor) contribution);
077        } else if (HEADERS_CONFIG_EP.equals(extensionPoint)) {
078            headersRegistry.addContribution((NuxeoHeaderDescriptor) contribution);
079        } else {
080            log.error("Unknown ExtensionPoint " + extensionPoint);
081        }
082    }
083
084    public void registerFilterConfig(String name, String pattern, boolean grant, boolean tx, boolean sync,
085            boolean cached, boolean isPrivate, String cacheTime) {
086        FilterConfigDescriptor desc = new FilterConfigDescriptor(name, pattern, grant, tx, sync, cached, isPrivate,
087                cacheTime);
088        registerFilterConfig(desc);
089    }
090
091    public void registerFilterConfig(FilterConfigDescriptor desc) {
092        if (desc.isGrantRule()) {
093            grantPatterns.put(desc.getName(), desc);
094            log.debug("Registered grant filter config");
095        } else {
096            denyPatterns.put(desc.getName(), desc);
097            log.debug("Registered deny filter config");
098        }
099    }
100
101    @Override
102    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
103        if (CORS_CONFIG_EP.equals(extensionPoint)) {
104            corsFilterRegistry.removeContribution((NuxeoCorsFilterDescriptor) contribution);
105        }
106    }
107
108    /* Service interface */
109
110    @Override
111    public CORSFilter getCorsFilterForRequest(HttpServletRequest request) {
112        String uri = request.getRequestURI();
113        NuxeoCorsFilterDescriptor descriptor = corsFilterRegistry.getFirstMatchingDescriptor(uri);
114        return descriptor == null ? null : descriptor.getFilter();
115    }
116
117    @Override
118    @Deprecated
119    public FilterConfig getCorsConfigForRequest(HttpServletRequest request) {
120        String uri = request.getRequestURI();
121        NuxeoCorsFilterDescriptor descriptor = corsFilterRegistry.getFirstMatchingDescriptor(uri);
122        return descriptor != null ? descriptor.buildFilterConfig() : null;
123    }
124
125    @Override
126    public RequestFilterConfig getConfigForRequest(HttpServletRequest request) {
127        String uri = request.getRequestURI();
128        RequestFilterConfig config = null;
129
130        synchronized (configCache) {
131            config = configCache.get(uri);
132        }
133        if (config == null) {
134            config = computeConfigForRequest(uri);
135            synchronized (configCache) {
136                configCache.put(uri, config);
137            }
138        }
139        return config;
140    }
141
142    public RequestFilterConfig computeConfigForRequest(String uri) {
143        // handle deny patterns
144        for (FilterConfigDescriptor desc : denyPatterns.values()) {
145            Pattern pat = desc.getCompiledPattern();
146            Matcher m = pat.matcher(uri);
147            if (m.matches()) {
148                return new RequestFilterConfigImpl(false, false, false, false, false, "");
149            }
150        }
151
152        // handle grant patterns
153        for (FilterConfigDescriptor desc : grantPatterns.values()) {
154            Pattern pat = desc.getCompiledPattern();
155            Matcher m = pat.matcher(uri);
156            if (m.matches()) {
157                return new RequestFilterConfigImpl(desc.useSync(), desc.useTx(), desc.useTxBuffered(), desc.isCached(),
158                        desc.isPrivate(), desc.getCacheTime());
159            }
160        }
161
162        // return deny by default
163        return new RequestFilterConfigImpl(false, false, false, false, false, "");
164    }
165
166    @Override
167    public Map<String, String> getResponseHeaders() {
168        Map<String, String> headersCache = new HashMap<String, String>();
169        for (NuxeoHeaderDescriptor header : headersRegistry.descs.values()) {
170            headersCache.put(header.name, header.getValue());
171        }
172        return headersCache;
173    }
174}