/* * 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.felix.http.whiteboard.internal.manager; import org.osgi.framework.Bundle; import org.osgi.service.http.HttpContext; import org.apache.felix.http.base.internal.logger.SystemLogger; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; public final class HttpContextManager { /** * HttpContextHolders indexed by context ID fully configured * with an HttpContext and optional servlets and filters. * <p> * The context ID either includes the bundle ID as the first part of the * name, such as <i>123-sample.context</i> in the case of non-shared * contexts. IDs of shared contexts are prefixed with the fixed string * <code>shared</code> to not mix them with per-bundle contexts. */ private final HashMap<String, HttpContextHolder> idMap; /** * Reverse mapping of HttpContext services to the context ID with * which they are registered. */ private final HashMap<HttpContext, String> contextMap; /** * Map of servlets and filters registered referring to unregistered * contexts as of yet. */ private final HashMap<String, Set<AbstractMapping>> orphanMappings; public HttpContextManager() { this.idMap = new HashMap<String, HttpContextHolder>(); this.contextMap = new HashMap<HttpContext, String>(); this.orphanMappings = new HashMap<String, Set<AbstractMapping>>(); } private static String createId(Bundle bundle, String contextId) { if (bundle != null) { return bundle.getBundleId() + "-" + ((contextId == null) ? "" : contextId); } return createId(contextId); } private static String createId(String contextId) { return "shared-" + ((contextId == null) ? "" : contextId); } private static String getContextId(String id) { final int dash = id.indexOf('-'); return (dash < 0) ? id : id.substring(dash + 1); } public synchronized HttpContext getHttpContext(Bundle bundle, String contextId, AbstractMapping mapping) { // per-bundle context String id = createId(bundle, contextId); HttpContextHolder holder = this.idMap.get(id); // shared context if (holder == null) { id = createId(contextId); holder = this.idMap.get(id); } // no context yet, put the mapping on hold if (holder == null) { // care for default context if no context ID if (ExtenderManager.isEmpty(contextId)) { addHttpContext(bundle, "", new DefaultHttpContext(bundle)); return getHttpContext(bundle, "", mapping); } // otherwise context is not here yet Set<AbstractMapping> orphaned = this.orphanMappings.get(contextId); if (orphaned == null) { orphaned = new HashSet<AbstractMapping>(); this.orphanMappings.put(contextId, orphaned); } if (contextId != null) { // Only log something when an actual context ID is used. Should solve FELIX-4307... SystemLogger.debug("Holding off mapping with unregistered context with id [" + contextId + "]"); } orphaned.add(mapping); return null; } // otherwise use the context if (contextId != null) { // Only log something when an actual context ID is used. Should solve FELIX-4307... SystemLogger.debug("Reusing context with id [" + contextId + "]"); } holder.addMapping(mapping); return holder.getContext(); } public synchronized void ungetHttpContext(Bundle bundle, String contextId, AbstractMapping mapping) { // per-bundle context String id = createId(bundle, contextId); HttpContextHolder context = this.idMap.get(id); // shared context if (context == null) { id = createId(contextId); context = this.idMap.get(id); } // remove the mapping if there is a mapped context if (context != null) { context.removeMapping(mapping); } else { Set<AbstractMapping> orphans = this.orphanMappings.get(contextId); if (orphans != null) { orphans.remove(mapping); if (orphans.isEmpty()) { this.orphanMappings.remove(contextId); } } // it is not expected but make sure there is no reference mapping.setContext(null); } } public synchronized Collection<AbstractMapping> addHttpContext(Bundle bundle, String contextId, HttpContext context) { String id = createId(bundle, contextId); HttpContextHolder holder = new HttpContextHolder(context); Set<AbstractMapping> orphans = this.orphanMappings.remove(contextId); if (orphans != null) { for (Iterator<AbstractMapping> mi = orphans.iterator(); mi.hasNext();) { AbstractMapping mapping = mi.next(); if (bundle == null || bundle.equals(mapping.getBundle())) { holder.addMapping(mapping); mi.remove(); } } // put any remaining orphans back if (!orphans.isEmpty()) { this.orphanMappings.put(contextId, orphans); } } this.idMap.put(id, holder); this.contextMap.put(context, id); // return a copy to prevent concurrent modification return new HashSet<AbstractMapping>(holder.getMappings()); } public synchronized Collection<AbstractMapping> removeHttpContext(HttpContext context) { String id = this.contextMap.remove(context); if (id != null) { HttpContextHolder holder = this.idMap.remove(id); if (holder != null) { Set<AbstractMapping> mappings = holder.getMappings(); if (mappings != null && !mappings.isEmpty()) { // keep the orphans around final String contextId = getContextId(id); Set<AbstractMapping> orphans = this.orphanMappings.get(contextId); if (orphans == null) { orphans = new HashSet<AbstractMapping>(); this.orphanMappings.put(getContextId(id), orphans); } for (AbstractMapping mapping : mappings) { mapping.setContext(null); orphans.add(mapping); } } return mappings; } } return null; } synchronized Map<String, HttpContextHolder> getHttpContexts() { return new HashMap<String, HttpContextHolder>(this.idMap); } synchronized Map<String, Set<AbstractMapping>> getOrphanMappings() { return new HashMap<String, Set<AbstractMapping>>(this.orphanMappings); } static class HttpContextHolder { private final HttpContext context; private final Set<AbstractMapping> mappings; HttpContextHolder(final HttpContext context) { this.context = context; this.mappings = new HashSet<AbstractMapping>(); } public HttpContext getContext() { return context; } void addMapping(AbstractMapping mapping) { this.mappings.add(mapping); mapping.setContext(this.getContext()); } void removeMapping(AbstractMapping mapping) { mapping.setContext(null); this.mappings.remove(mapping); } public Set<AbstractMapping> getMappings() { return mappings; } } }