
org.glassfish.grizzly.osgi.httpservice.OSGiMainHandler Maven / Gradle / Ivy
/*
* Copyright (c) 2009, 2020 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package org.glassfish.grizzly.osgi.httpservice;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.glassfish.grizzly.http.server.HttpHandler;
import org.glassfish.grizzly.http.server.Request;
import org.glassfish.grizzly.http.server.Response;
import org.glassfish.grizzly.http.server.util.MappingData;
import org.glassfish.grizzly.osgi.httpservice.util.Logger;
import org.glassfish.grizzly.servlet.FilterRegistration;
import org.osgi.framework.Bundle;
import org.osgi.service.http.HttpContext;
import org.osgi.service.http.HttpService;
import org.osgi.service.http.NamespaceException;
import jakarta.servlet.Filter;
import jakarta.servlet.Servlet;
import jakarta.servlet.ServletException;
/**
* OSGi Main HttpHandler.
*
* Dispatching HttpHandler. Grizzly integration.
*
* Responsibilities:
*
* - Manages registration data.
* - Dispatching {@link HttpHandler#service(Request, Response)} method call to registered {@link HttpHandler}s.
*
*
* @author Hubert Iwaniuk
*/
public class OSGiMainHandler extends HttpHandler implements OSGiHandler {
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final Logger logger;
private final Bundle bundle;
private final OSGiCleanMapper mapper;
/**
* Constructor.
*
* @param logger Logger utility.
* @param bundle Bundle that we create if for, for local data reference.
*/
public OSGiMainHandler(Logger logger, Bundle bundle) {
this.logger = logger;
this.bundle = bundle;
this.mapper = new OSGiCleanMapper(logger);
}
/**
* Service method dispatching to registered handlers.
*
* {@inheritDoc}
*/
@Override
public void service(Request request, Response response) throws Exception {
boolean invoked = false;
String alias = request.getDecodedRequestURI();
String originalAlias = alias;
logger.debug("Serviceing URI: " + alias);
// first lookup needs to be done for full match.
boolean cutOff = false;
while (true) {
logger.debug("CutOff: " + cutOff + ", alias: " + alias);
alias = OSGiCleanMapper.map(alias, cutOff);
if (alias == null) {
if (cutOff) {
// not found
break;
} else {
// switching to reducing mapping mode (removing after last '/' and searching)
logger.debug("Swithcing to reducing mapping mode.");
cutOff = true;
alias = originalAlias;
}
} else {
HttpHandler httpHandler = OSGiCleanMapper.getHttpHandler(alias);
((OSGiHandler) httpHandler).getProcessingLock().lock();
try {
updateMappingInfo(request, alias, originalAlias);
httpHandler.service(request, response);
} finally {
((OSGiHandler) httpHandler).getProcessingLock().unlock();
}
invoked = true;
if (response.getStatus() != 404) {
break;
} else if ("/".equals(alias)) {
// 404 in "/", cutoff algo will not escape this one.
break;
} else if (!cutOff) {
// not found and haven't run in cutoff mode
cutOff = true;
}
}
}
if (!invoked) {
try {
response.sendError(404);
} catch (Exception e) {
logger.warn("Failed to commit 404 status.", e);
}
}
}
/**
* Registers {@link org.glassfish.grizzly.osgi.httpservice.OSGiServletHandler} in OSGi Http Service.
*
* Keeps truck of all registrations, takes care of thread safety.
*
* @param alias Alias to register, if wrong value than throws {@link org.osgi.service.http.NamespaceException}.
* @param servlet Servlet to register under alias, if fails to
* {@link jakarta.servlet.Servlet#init(jakarta.servlet.ServletConfig)} throws {@link jakarta.servlet.ServletException}.
* @param initparams Initial parameters to populate {@link jakarta.servlet.ServletContext} with.
* @param context OSGi {@link org.osgi.service.http.HttpContext}, provides mime handling, security and bundle specific
* resource access.
* @param httpService Used to {@link HttpService#createDefaultHttpContext()} if needed.
* @throws org.osgi.service.http.NamespaceException If alias was invalid or already registered.
* @throws jakarta.servlet.ServletException If {@link jakarta.servlet.Servlet#init(jakarta.servlet.ServletConfig)}
* fails.
*/
public void registerServletHandler(final String alias, final Servlet servlet, final Dictionary initparams, HttpContext context,
final HttpService httpService) throws NamespaceException, ServletException {
ReentrantLock lock = OSGiCleanMapper.getLock();
lock.lock();
try {
validateAlias4RegOk(alias);
validateServlet4RegOk(servlet);
if (context == null) {
logger.debug("No HttpContext provided, creating default");
context = httpService.createDefaultHttpContext();
}
OSGiServletHandler servletHandler = findOrCreateOSGiServletHandler(servlet, context, initparams);
servletHandler.setServletPath(alias);
logger.debug("Initializing Servlet been registered");
servletHandler.startServlet(); // this might throw ServletException, throw it to offending bundle.
mapper.addHttpHandler(alias, servletHandler);
} finally {
lock.unlock();
}
}
/**
*
* @param filter
* @param urlPattern
* @param initparams
* @param context
* @param httpService
* @throws NamespaceException
* @throws ServletException
*/
public void registerFilter(final Filter filter, final String urlPattern, final Dictionary initparams, HttpContext context, final HttpService httpService)
throws ServletException {
ReentrantLock lock = OSGiCleanMapper.getLock();
lock.lock();
try {
if (context == null) {
logger.debug("No HttpContext provided, creating default");
context = httpService.createDefaultHttpContext();
}
OSGiServletContext servletContext = mapper.getServletContext(context);
if (servletContext == null) {
mapper.addContext(context, null);
servletContext = mapper.getServletContext(context);
}
FilterRegistration registration = servletContext.addFilter(Integer.toString(filter.hashCode()), filter);
registration.addMappingForUrlPatterns(null, urlPattern);
filter.init(new OSGiFilterConfig(servletContext));
} finally {
lock.unlock();
}
}
/**
* Registers {@link OSGiResourceHandler} in OSGi Http Service.
*
* Keeps truck of all registrations, takes care of thread safety.
*
* @param alias Alias to register, if wrong value than throws {@link NamespaceException}.
* @param context OSGi {@link HttpContext}, provides mime handling, security and bundle specific resource access.
* @param internalPrefix Prefix to map request for this alias to.
* @param httpService Used to {@link HttpService#createDefaultHttpContext()} if needed.
* @throws NamespaceException If alias was invalid or already registered.
*/
public void registerResourceHandler(String alias, HttpContext context, String internalPrefix, HttpService httpService) throws NamespaceException {
ReentrantLock lock = OSGiCleanMapper.getLock();
lock.lock();
try {
validateAlias4RegOk(alias);
if (context == null) {
logger.debug("No HttpContext provided, creating default");
context = httpService.createDefaultHttpContext();
}
if (internalPrefix == null) {
internalPrefix = "";
}
OSGiServletContext servletContext = mapper.getServletContext(context);
if (servletContext == null) {
mapper.addContext(context, null);
servletContext = mapper.getServletContext(context);
}
mapper.addHttpHandler(alias, new OSGiResourceHandler(alias, internalPrefix, context, servletContext, logger));
} finally {
lock.unlock();
}
}
/**
* Unregisters previously registered alias.
*
* Keeps truck of all registrations, takes care of thread safety.
*
* @param alias Alias to unregister, if not owning alias {@link IllegalArgumentException} is thrown.
* @throws IllegalArgumentException If alias was not registered by calling bundle.
*/
public void unregisterAlias(String alias) {
ReentrantLock lock = OSGiCleanMapper.getLock();
lock.lock();
try {
if (mapper.isLocalyRegisteredAlias(alias)) {
mapper.doUnregister(alias, true);
} else {
logger.warn("Bundle: " + bundle + " tried to unregister not owned alias '" + alias + '\'');
throw new IllegalArgumentException("Alias '" + alias + "' was not registered by you.");
}
} finally {
lock.unlock();
}
}
public void unregisterFilter(final Filter filter) {
ReentrantLock lock = OSGiCleanMapper.getLock();
lock.lock();
try {
for (OSGiServletContext servletContext : mapper.httpContextToServletContextMap.values()) {
servletContext.unregisterFilter(filter);
}
} finally {
lock.unlock();
}
}
/**
* Unregisters all alias
es registered by owning bundle.
*/
public void uregisterAllLocal() {
logger.info("Unregistering all aliases registered by owning bundle");
ReentrantLock lock = OSGiCleanMapper.getLock();
lock.lock();
try {
for (String alias : mapper.getLocalAliases()) {
logger.debug("Unregistering '" + alias + "'");
// remember not to call Servlet.destroy() owning bundle might be stopped already.
mapper.doUnregister(alias, false);
for (OSGiServletContext servletContext : mapper.httpContextToServletContextMap.values()) {
servletContext.unregisterAllFilters();
}
mapper.httpContextToServletContextMap.clear();
}
} finally {
lock.unlock();
}
}
/**
* Part of Shutdown sequence. Unregister and clean up.
*/
public void unregisterAll() {
logger.info("Unregistering all registered aliases");
ReentrantLock lock = OSGiCleanMapper.getLock();
lock.lock();
try {
Set aliases = OSGiCleanMapper.getAllAliases();
while (!aliases.isEmpty()) {
String alias = ((TreeSet) aliases).first();
logger.debug("Unregistering '" + alias + "'");
// remember not to call Servlet.destroy() owning bundle might be stopped already.
mapper.doUnregister(alias, false);
}
} finally {
lock.unlock();
}
}
/**
* {@inheritDoc}
*/
@Override
public ReentrantReadWriteLock.ReadLock getProcessingLock() {
return lock.readLock();
}
/**
* {@inheritDoc}
*/
@Override
public ReentrantReadWriteLock.WriteLock getRemovalLock() {
return lock.writeLock();
}
/**
* Chek if alias
has been already registered.
*
* @param alias Alias to check.
* @throws NamespaceException If alias
has been registered.
*/
private void validateAlias4RegOk(String alias) throws NamespaceException {
if (!alias.startsWith("/")) {
// have to start with "/"
String msg = "Invalid alias '" + alias + "', have to start with '/'.";
logger.warn(msg);
throw new NamespaceException(msg);
}
if (alias.length() > 1 && alias.endsWith("/")) {
// if longer than "/", should not end with "/"
String msg = "Alias '" + alias + "' can't and with '/' with exception to alias '/'.";
logger.warn(msg);
throw new NamespaceException(msg);
}
if (OSGiCleanMapper.containsAlias(alias)) {
String msg = "Alias: '" + alias + "', already registered";
logger.warn(msg);
throw new NamespaceException(msg);
}
}
/**
* Check if servlet
has been already registered.
*
* An instance of {@link Servlet} can be registered only once, so in case of servlet been registered before will throw
* {@link ServletException} as specified in OSGi HttpService Spec.
*
* @param servlet {@link Servlet} to check if can be registered.
* @throws ServletException Iff servlet
has been registered before.
*/
private void validateServlet4RegOk(Servlet servlet) throws ServletException {
if (OSGiCleanMapper.containsServlet(servlet)) {
String msg = "Servlet: '" + servlet + "', already registered.";
logger.warn(msg);
throw new ServletException(msg);
}
}
/**
* Looks up {@link OSGiServletHandler}.
*
* If is already registered for httpContext
than create new instance based on already registered. Else
* Create new one.
*
*
* @param servlet {@link Servlet} been registered.
* @param httpContext {@link HttpContext} used for registration.
* @param initparams Init parameters that will be visible in {@link jakarta.servlet.ServletContext}.
* @return Found or created {@link OSGiServletHandler}.
*/
private OSGiServletHandler findOrCreateOSGiServletHandler(Servlet servlet, HttpContext httpContext, Dictionary initparams) {
OSGiServletHandler osgiServletHandler;
List servletHandlers = mapper.getContext(httpContext);
if (servletHandlers != null) {
logger.debug("Reusing ServletHandler");
// new servlet handler for same configuration, different servlet and alias
osgiServletHandler = servletHandlers.get(0).newServletHandler(servlet);
servletHandlers.add(osgiServletHandler);
} else {
logger.debug("Creating new ServletHandler");
HashMap params;
if (initparams != null) {
params = new HashMap<>(initparams.size());
Enumeration names = initparams.keys();
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
params.put(name, (String) initparams.get(name));
}
} else {
params = new HashMap<>(0);
}
servletHandlers = new ArrayList<>(1);
mapper.addContext(httpContext, mapper.getServletContext(httpContext), servletHandlers);
final OSGiServletContext servletContext = mapper.getServletContext(httpContext);
assert servletContext != null;
osgiServletHandler = new OSGiServletHandler(servlet, httpContext, servletContext, params, logger);
servletHandlers.add(osgiServletHandler);
osgiServletHandler.setFilterChainFactory(servletContext.getFilterChainFactory());
}
return osgiServletHandler;
}
private void updateMappingInfo(final Request request, final String alias, final String originalAlias) {
final MappingData mappingData = request.obtainMappingData();
mappingData.contextPath.setString("");
if (alias.equals("/")) {
mappingData.wrapperPath.setString("");
} else {
mappingData.wrapperPath.setString(alias);
}
if (alias.length() != originalAlias.length()) {
String pathInfo = originalAlias.substring(alias.length());
if (pathInfo.charAt(0) != '/') {
pathInfo = "/" + pathInfo;
}
mappingData.pathInfo.setString(pathInfo);
}
updatePaths(request, mappingData);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy