| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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. |
| */ |
| package org.apache.sling.engine.impl.filter; |
| |
| import java.util.Dictionary; |
| import java.util.Hashtable; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| import javax.servlet.Filter; |
| import javax.servlet.FilterConfig; |
| import javax.servlet.ServletException; |
| |
| import org.apache.sling.engine.EngineConstants; |
| import org.apache.sling.engine.impl.console.WebConsoleConfigPrinter; |
| import org.apache.sling.engine.impl.helper.SlingFilterConfig; |
| import org.apache.sling.engine.impl.helper.SlingServletContext; |
| import org.apache.sling.engine.jmx.FilterProcessorMBean; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.FrameworkUtil; |
| import org.osgi.framework.InvalidSyntaxException; |
| import org.osgi.framework.ServiceReference; |
| import org.osgi.framework.ServiceRegistration; |
| import org.osgi.util.converter.Converters; |
| import org.osgi.util.tracker.ServiceTracker; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| public class ServletFilterManager extends ServiceTracker<Filter, Filter> { |
| |
| private static final String JMX_OBJECTNAME = "jmx.objectname"; |
| |
| public static enum FilterChainType { |
| /** |
| * Indicates request level filters. |
| * |
| * @see EngineConstants#FILTER_SCOPE_REQUEST |
| */ |
| REQUEST("Request"), |
| |
| /** |
| * Indicates error level filters. |
| * |
| * @see EngineConstants#FILTER_SCOPE_ERROR |
| */ |
| ERROR("Error"), |
| |
| /** |
| * Indicates include level filters. |
| * |
| * @see EngineConstants#FILTER_SCOPE_INCLUDE |
| */ |
| INCLUDE("Include"), |
| |
| /** |
| * Indicates forward level filters. |
| * |
| * @see EngineConstants#FILTER_SCOPE_FORWARD |
| */ |
| FORWARD("Forward"), |
| |
| /** |
| * Indicates component level filters. |
| * |
| * @see EngineConstants#FILTER_SCOPE_COMPONENT |
| */ |
| COMPONENT("Component"); |
| |
| private final String message; |
| |
| private FilterChainType(final String message) { |
| this.message = message; |
| } |
| |
| @Override |
| public String toString() { |
| return message; |
| } |
| } |
| |
| /** default log */ |
| private final Logger log = LoggerFactory.getLogger(getClass()); |
| |
| private final SlingServletContext servletContext; |
| |
| private final SlingFilterChainHelper[] filterChains; |
| |
| private final Map<Long, MBeanReg> mbeanMap = new ConcurrentHashMap<>(); |
| |
| private volatile ServiceRegistration<WebConsoleConfigPrinter> printerRegistration; |
| |
| private static final org.osgi.framework.Filter SERVICE_FILTER; |
| static { |
| org.osgi.framework.Filter f = null; |
| try { |
| f = FrameworkUtil.createFilter("(&(" + Constants.OBJECTCLASS + "=" + Filter.class.getName() |
| + ")(|(" + EngineConstants.SLING_FILTER_SCOPE + "=*)(" + EngineConstants.FILTER_SCOPE + "=*)))"); |
| } catch (InvalidSyntaxException e) { |
| // we ignore this here as the above is a constant expression |
| } |
| SERVICE_FILTER = f; |
| } |
| |
| public ServletFilterManager(final BundleContext context, |
| final SlingServletContext servletContext) { |
| super(context, SERVICE_FILTER, null); |
| this.servletContext = servletContext; |
| this.filterChains = new SlingFilterChainHelper[FilterChainType.values().length]; |
| for (final FilterChainType type : FilterChainType.values()) { |
| this.filterChains[type.ordinal()] = new SlingFilterChainHelper(); |
| } |
| } |
| |
| @Override |
| public void open() { |
| super.open(); |
| // Setup configuration printer |
| printerRegistration = WebConsoleConfigPrinter.register(context, this); |
| |
| } |
| |
| @Override |
| public void close() { |
| if (this.printerRegistration != null) { |
| WebConsoleConfigPrinter.unregister(this.printerRegistration); |
| this.printerRegistration = null; |
| } |
| super.close(); |
| } |
| |
| public SlingFilterChainHelper getFilterChain(final FilterChainType chain) { |
| return filterChains[chain.ordinal()]; |
| } |
| |
| public FilterHandle[] getFilters(final FilterChainType chain) { |
| return getFilterChain(chain).getFilters(); |
| } |
| |
| @Override |
| public Filter addingService(ServiceReference<Filter> reference) { |
| Filter service = super.addingService(reference); |
| if (service != null) { |
| initFilter(reference, service); |
| } |
| return service; |
| } |
| |
| @Override |
| public void modifiedService(ServiceReference<Filter> reference, Filter service) { |
| // only if the filter name has changed, we need to do a service re-registration |
| final String newFilterName = SlingFilterConfig.getName(reference); |
| if (newFilterName.equals(getUsedFilterName(reference))) { |
| removeFilterFromChains((Long) reference.getProperty(Constants.SERVICE_ID)); |
| addFilterToChains(service, reference); |
| } else { |
| destroyFilter(reference, service); |
| initFilter(reference, service); |
| } |
| } |
| |
| @Override |
| public void removedService(ServiceReference<Filter> reference, Filter service) { |
| if ( service != null ) { |
| destroyFilter(reference, service); |
| super.removedService(reference, service); |
| } |
| } |
| |
| private void initFilter(final ServiceReference<Filter> reference, |
| final Filter filter) { |
| final String filterName = SlingFilterConfig.getName(reference); |
| final Long serviceId = (Long) reference.getProperty(Constants.SERVICE_ID); |
| |
| try { |
| |
| MBeanReg reg; |
| try { |
| final Dictionary<String, String> mbeanProps = new Hashtable<String, String>(); |
| mbeanProps.put(JMX_OBJECTNAME, "org.apache.sling:type=engine-filter,service=" + filterName); |
| reg = new MBeanReg(); |
| reg.mbean = new FilterProcessorMBeanImpl(); |
| |
| reg.registration = reference.getBundle().getBundleContext().registerService(FilterProcessorMBean.class, |
| reg.mbean, mbeanProps); |
| |
| mbeanMap.put(serviceId, reg); |
| } catch (Throwable t) { |
| log.debug("Unable to register mbean", t); |
| reg = null; |
| } |
| |
| // initialize the filter first |
| final FilterConfig config = new SlingFilterConfig(servletContext, reference, filterName); |
| filter.init(config); |
| |
| // add to chains |
| addFilterToChains(filter, reference); |
| } catch (ServletException ce) { |
| log.error("Filter " + filterName + " failed to initialize", ce); |
| } catch (Throwable t) { |
| log.error("Unexpected problem initializing filter " + filterName, t); |
| } |
| } |
| |
| private String getUsedFilterName(final ServiceReference<Filter> reference) { |
| final MBeanReg reg = mbeanMap.get(reference.getProperty(Constants.SERVICE_ID)); |
| if (reg != null) { |
| final String objectName = (String) reg.registration.getReference().getProperty(JMX_OBJECTNAME); |
| if (objectName != null) { |
| final int pos = objectName.indexOf(",service="); |
| if (pos != -1) { |
| return objectName.substring(pos + 9); |
| } |
| } |
| } |
| return null; |
| } |
| |
| private void destroyFilter(final ServiceReference<Filter> reference, |
| final Filter filter) { |
| // service id |
| Long serviceId = (Long) reference.getProperty(Constants.SERVICE_ID); |
| |
| final MBeanReg reg = mbeanMap.remove(serviceId); |
| if (reg != null) { |
| reg.registration.unregister(); |
| } |
| |
| // destroy it |
| if (removeFilterFromChains(serviceId)) { |
| try { |
| filter.destroy(); |
| } catch (Throwable t) { |
| log.error("Unexpected problem destroying Filter {}", filter, t); |
| } |
| } |
| } |
| |
| @SuppressWarnings("deprecation") |
| private void addFilterToChains(final Filter filter, final ServiceReference<Filter> reference) { |
| final Long serviceId = (Long) reference.getProperty(Constants.SERVICE_ID); |
| final MBeanReg mbeanReg = mbeanMap.get(serviceId); |
| final FilterProcessorMBeanImpl mbean = mbeanReg == null ? null : mbeanReg.mbean; |
| |
| // get the order, Integer.MAX_VALUE by default |
| final String orderSource; |
| Object orderObj = reference.getProperty(Constants.SERVICE_RANKING); |
| if (orderObj == null) { |
| // filter order is defined as lower value has higher |
| // priority while service ranking is the opposite In |
| // addition we allow different types than Integer |
| orderObj = reference.getProperty(EngineConstants.FILTER_ORDER); |
| if (orderObj != null) { |
| log.warn("Filter service {} is using deprecated property {}. Use {} instead.", |
| reference, EngineConstants.FILTER_ORDER, Constants.SERVICE_RANKING ); |
| // we can use 0 as the default as this will be applied |
| // in the next step anyway if this props contains an |
| // invalid value |
| orderSource = EngineConstants.FILTER_ORDER.concat("=").concat(orderObj.toString()); |
| orderObj = Integer.valueOf(-1 * Converters.standardConverter().convert(orderObj).defaultValue(0).to(Integer.class)); |
| } else { |
| orderSource = "none"; |
| } |
| } else { |
| orderSource = Constants.SERVICE_RANKING.concat("=").concat(orderObj.toString()); |
| } |
| final int order = (orderObj instanceof Integer) ? ((Integer) orderObj).intValue() : 0; |
| |
| // register by scope |
| Object scopeValue = reference.getProperty(EngineConstants.SLING_FILTER_SCOPE); |
| if ( scopeValue == null ) { |
| scopeValue = reference.getProperty(EngineConstants.FILTER_SCOPE); |
| log.warn("Filter service {} is using deprecated property {}. Use {} instead.", |
| reference, EngineConstants.FILTER_SCOPE, EngineConstants.SLING_FILTER_SCOPE ); |
| } |
| final String[] scopes = Converters.standardConverter().convert(scopeValue).to(String[].class); |
| final FilterPredicate predicate = new FilterPredicate(reference); |
| |
| boolean used = false; |
| for (String scope : scopes) { |
| scope = scope.toUpperCase(); |
| try { |
| FilterChainType type = FilterChainType.valueOf(scope.toString()); |
| getFilterChain(type).addFilter(filter, predicate, serviceId, order, orderSource, mbean); |
| |
| if (type == FilterChainType.COMPONENT) { |
| getFilterChain(FilterChainType.INCLUDE).addFilter(filter, predicate, serviceId, order, |
| orderSource, mbean); |
| getFilterChain(FilterChainType.FORWARD).addFilter(filter, predicate, serviceId, order, |
| orderSource, mbean); |
| } |
| |
| used = true; |
| } catch (final IllegalArgumentException iae) { |
| log.warn("Filter service {} has invalid value {} for scope. Value is ignored", reference, scope); |
| } |
| } |
| if ( !used ){ |
| log.warn("Filter service {} has been registered without a valid {} property. Using default value.", serviceId, |
| EngineConstants.SLING_FILTER_SCOPE); |
| getFilterChain(FilterChainType.REQUEST).addFilter(filter, predicate, serviceId, order, orderSource, mbean); |
| } |
| |
| } |
| |
| private boolean removeFilterFromChains(final Long serviceId) { |
| boolean removed = false; |
| for (SlingFilterChainHelper filterChain : filterChains) { |
| removed |= filterChain.removeFilterById(serviceId); |
| } |
| return removed; |
| } |
| |
| private static final class MBeanReg { |
| FilterProcessorMBeanImpl mbean; |
| ServiceRegistration<FilterProcessorMBean> registration; |
| } |
| } |