org.ops4j.pax.web.service.spi.model.ServerModel.java Source code

Java tutorial

Introduction

Here is the source code for org.ops4j.pax.web.service.spi.model.ServerModel.java

Source

/* Copyright 2007 Alin Dreghiciu.
 *
 * 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.
 */
package org.ops4j.pax.web.service.spi.model;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Pattern;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.framework.Bundle;
import org.osgi.service.http.HttpContext;
import org.osgi.service.http.NamespaceException;

/**
 * Holds web elements in a global context accross all services (all bundles usng the Http Service).
 *
 * @author Alin Dreghiciu
 */
public class ServerModel {

    /**
     * Logger.
     */
    private static final Log LOG = LogFactory.getLog(ServerModel.class);

    /**
     * Map between aliases used for registering a servlet and the registered servlet model.
     * Used to block registration of an alias more then one time.
     */
    private final Map<String, ServletModel> m_aliasMapping;
    /**
     * Set of all registered servlets.
     * Used to block registration of the same servlet more times.
     */
    private final Set<Servlet> m_servlets;
    /**
     * Mapping between full registration url patterns and servlet model. Full url pattern mean that it has the context
     * name prepended (if context name is set) to the actual url pattern.
     * Used to globally find (against all registered patterns) the right servlet context for the pattern.
     */
    private final Map<String, UrlPattern> m_servletUrlPatterns;
    /**
     * Mapping between full registration url patterns and filter model. Full url pattern mean that it has the context
     * name prepended (if context name is set) to the actual url pattern.
     * Used to globally find (against all registered patterns) the right filter context for the pattern.
     */
    private final ConcurrentMap<String, UrlPattern> m_filterUrlPatterns;
    /**
     * Map between http contexts and the bundle that registred a web element using that http context.
     * Used to block more bundles registering web elements udng the same http context.
     */
    private final ConcurrentMap<HttpContext, Bundle> m_httpContexts;
    /**
     * Servlet lock. Used to sychonchornize on servlet registration/unregistrationhat that works agains 3 maps
     * (m_servlets, m_aliasMaping, m_servletToUrlPattern).
     */
    private final Lock m_servletLock;

    /**
     * Constructor.
     */
    public ServerModel() {
        m_aliasMapping = new HashMap<String, ServletModel>();
        m_servlets = new HashSet<Servlet>();
        m_servletUrlPatterns = new HashMap<String, UrlPattern>();
        m_filterUrlPatterns = new ConcurrentHashMap<String, UrlPattern>();
        m_httpContexts = new ConcurrentHashMap<HttpContext, Bundle>();

        m_servletLock = new ReentrantLock();
    }

    /**
     * Registers a servlet model.
     *
     * @param model servlet model to register
     *
     * @throws ServletException   - If servlet is already registered
     * @throws NamespaceException - If servlet alias is already registered
     */
    public void addServletModel(final ServletModel model) throws NamespaceException, ServletException {
        m_servletLock.lock();
        try {
            if (m_servlets.contains(model.getServlet())) {
                throw new ServletException("servlet already registered with a different alias");
            }
            if (model.getAlias() != null) {
                final String alias = getFullPath(model.getContextModel(), model.getAlias());
                if (m_aliasMapping.containsKey(alias)) {
                    throw new NamespaceException("alias is already in use in this or another context");
                }
                m_aliasMapping.put(alias, model);
            }
            m_servlets.add(model.getServlet());
            for (String urlPattern : model.getUrlPatterns()) {
                m_servletUrlPatterns.put(model.getId() + urlPattern,
                        new UrlPattern(getFullPath(model.getContextModel(), urlPattern), model));
            }
        } finally {
            m_servletLock.unlock();
        }
    }

    /**
     * Unregisters a servlet model.
     *
     * @param model servlet model to unregister
     */
    public void removeServletModel(final ServletModel model) {
        m_servletLock.lock();
        try {
            if (model.getAlias() != null) {
                m_aliasMapping.remove(getFullPath(model.getContextModel(), model.getAlias()));
            }
            m_servlets.remove(model.getServlet());
            if (model.getUrlPatterns() != null) {
                for (String urlPattern : model.getUrlPatterns()) {
                    m_servletUrlPatterns.remove(model.getId() + urlPattern);
                }
            }
        } finally {
            m_servletLock.unlock();
        }
    }

    /**
     * Registers a filter model.
     *
     * @param model filter model to register
     */
    public void addFilterModel(final FilterModel model) {
        if (model.getUrlPatterns() != null) {
            for (String urlPattern : model.getUrlPatterns()) {
                final UrlPattern newUrlPattern = new UrlPattern(getFullPath(model.getContextModel(), urlPattern),
                        model);
                final UrlPattern existingPattern = m_filterUrlPatterns.putIfAbsent(model.getId() + urlPattern,
                        newUrlPattern);
                if (existingPattern != null) {
                    // this should never happen but is a good assertion
                    LOG.error("Internal error (please report): Cannot associate url mapping " + model.getId()
                            + urlPattern + " to " + newUrlPattern + " because is already associated to "
                            + existingPattern);
                }
            }
        }
    }

    /**
     * Unregister a filter model.
     *
     * @param model filter model to unregister
     */
    public void removeFilterModel(final FilterModel model) {
        if (model.getUrlPatterns() != null) {
            for (String urlPattern : model.getUrlPatterns()) {
                m_filterUrlPatterns.remove(model.getId() + urlPattern);
            }
        }
    }

    /**
     * Associates a http context with a bundle if the http service is not already associated to another bundle. This is
     * done in order to prevent sharinh http context between bundles. The implementation is not 100% correct as it can
     * be that at a certain moment in time when this method is called,another thread is processing a release of the
     * http service, process that will deassociate the bundle that releasd the http service, and that bundle could
     * actually be related to the http context that this method is trying to associate. But this is less likely to
     * happen as it should have as precondition that this is happening concurent and that the two bundles are sharing
     * the http context. But this solution has the benefits of not needing synchronization.
     *
     * @param httpContext         http context to be assicated to the bundle
     * @param bundle              bundle to be assiciated with the htp service
     * @param allowReAsssociation if it should allow a context to be reassiciated to a bundle
     *
     * @throws IllegalStateException - If htp context is already associated to another bundle.
     */
    public void associateHttpContext(final HttpContext httpContext, final Bundle bundle,
            final boolean allowReAsssociation) {
        final Bundle currentBundle = m_httpContexts.putIfAbsent(httpContext, bundle);
        if ((!!allowReAsssociation) && currentBundle != null && currentBundle != bundle) {
            throw new IllegalStateException(
                    "Http context " + httpContext + " is already associated to bundle " + currentBundle);
        }
    }

    /**
     * Deassociate all http context assiciated to the provided bundle. The bellow code is only correct in the context
     * that there is no other thread is calling the association method in the mean time. This should not happen as once
     * a bundle is releasing the HttpService the service is first entering a stopped state ( before the call to this
     * method is made), state that will not perform the registration calls anymore.
     *
     * @param bundle bundle to be deassociated from http contexts
     */
    public void deassociateHttpContexts(final Bundle bundle) {
        for (Map.Entry<HttpContext, Bundle> entry : m_httpContexts.entrySet()) {
            if (entry.getValue() == bundle) {
                m_httpContexts.remove(entry.getKey());
            }
        }
    }

    public ContextModel matchPathToContext(final String path) {
        final boolean debug = LOG.isDebugEnabled();
        if (debug) {
            LOG.debug("Matching [" + path + "]...");
        }
        UrlPattern urlPattern = null;
        // first match servlets
        m_servletLock.lock();
        try {
            urlPattern = matchPathToContext(m_servletUrlPatterns.values(), path);
        } finally {
            m_servletLock.unlock();
        }
        // then if there is no matched servlet look for filters
        if (urlPattern == null) {
            urlPattern = matchPathToContext(m_filterUrlPatterns.values(), path);
        }
        ContextModel matched = null;
        if (urlPattern != null) {
            matched = urlPattern.getModel().getContextModel();
        }
        if (debug) {
            if (matched != null) {
                LOG.debug("Path [" + path + "] matched to " + urlPattern);
            } else {
                LOG.debug("Path [" + path + "] does not match any context");
            }
        }
        return matched;
    }

    private static UrlPattern matchPathToContext(final Collection<UrlPattern> urlPatterns, final String path) {
        UrlPattern matched = null;
        if (urlPatterns != null) {
            for (UrlPattern urlPattern : urlPatterns) {
                //LOG.debug( "Matching against " + urlPattern.getPattern() );
                if (matched == null || urlPattern.isBetterMatchThen(matched)) {
                    if (urlPattern.getPattern().matcher(path).matches()) {
                        matched = urlPattern;
                        //LOG.debug( "Matched. Best match " + matched );
                    } else if (!path.endsWith("/") && urlPattern.getPattern().matcher(path + "/").matches()) {
                        matched = urlPattern;
                        //LOG.debug( "Matched. Best match " + matched );
                    }
                }
            }
        }
        return matched;
    }

    /**
     * Returns the full path (including the context name if set)
     *
     * @param model a context model
     * @param path  path to be prepended
     *
     * @return full path
     */
    private static String getFullPath(final ContextModel model, final String path) {
        String fullPath = path.trim();
        if (model.getContextName().length() > 0) {
            fullPath = "/" + model.getContextName();
            if (!"/".equals(path.trim())) {
                fullPath = fullPath + path;
            }
        }
        return fullPath;
    }

    /**
     * Touple of full url pattern and registered model (servlet/filter) for the model.
     */
    private static class UrlPattern {

        private final Pattern m_pattern;
        private final Model m_model;

        UrlPattern(final String pattern, final Model model) {
            m_model = model;
            String patternToUse = pattern;
            if (!patternToUse.contains("*")) {
                patternToUse = patternToUse + (pattern.endsWith("/") ? "*" : "/*");
            }
            patternToUse = patternToUse.replace(".", "\\.");
            patternToUse = patternToUse.replace("*", ".*");
            m_pattern = Pattern.compile(patternToUse);
        }

        Pattern getPattern() {
            return m_pattern;
        }

        Model getModel() {
            return m_model;
        }

        public boolean isBetterMatchThen(final UrlPattern urlPattern) {
            return urlPattern == null || (this != urlPattern
                    && m_pattern.pattern().length() > urlPattern.m_pattern.pattern().length());
        }

        @Override
        public String toString() {
            return new StringBuffer().append("{").append("pattern=").append(m_pattern.pattern()).append(",model=")
                    .append(m_model).append("}").toString();
        }
    }

}