/*
 * Copyright (c) OSGi Alliance (2000, 2018). All Rights Reserved.
 *
 * 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.osgi.service.http.context;

import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.osgi.annotation.versioning.ConsumerType;
import org.osgi.framework.Bundle;
import org.osgi.service.http.whiteboard.HttpWhiteboardConstants;

/**
 * Helper service for a servlet context used by a Http Whiteboard implementation
 * to serve HTTP requests.
 *
 * <p>
 * This service defines methods that the Http Whiteboard implementation may call
 * to get information for a request when dealing with whiteboard services.
 * 
 * <p>
 * Each {@code ServletContextHelper} is registered with a
 * {@link HttpWhiteboardConstants#HTTP_WHITEBOARD_CONTEXT_NAME
 * "osgi.http.whiteboard.context.name"} service property containing a name to
 * reference by servlets, servlet filters, resources, and listeners. If there is
 * more than one {@code ServletContextHelper} registered with the same context
 * name, the one with the highest service ranking is active, the others are
 * inactive.
 * 
 * <p>
 * A context is registered with the
 * {@link HttpWhiteboardConstants#HTTP_WHITEBOARD_CONTEXT_PATH
 * "osgi.http.whiteboard.context.path"} service property to define a path under
 * which all services registered with this context are reachable. If there is
 * more than one {@code ServletContextHelper} registered with the same path,
 * each duplicate context path is searched by service ranking order according to
 * {@link org.osgi.framework.ServiceReference#compareTo(Object)} until a
 * matching servlet or resource is found.
 * 
 * <p>
 * Servlets, servlet filters, resources, and listeners services may be
 * associated with a {@code ServletContextHelper} service with the
 * {@link HttpWhiteboardConstants#HTTP_WHITEBOARD_CONTEXT_SELECT
 * "osgi.http.whiteboard.context.select"} service property. If the referenced
 * {@code ServletContextHelper} service does not exist or is currently not
 * active, the whiteboard services for that {@code ServletContextHelper} are not
 * active either.
 * 
 * <p>
 * If no {@code ServletContextHelper} service is associated, that is no
 * {@link HttpWhiteboardConstants#HTTP_WHITEBOARD_CONTEXT_SELECT
 * "osgi.http.whiteboard.context.select"} service property is configured for a
 * whiteboard service, a default {@code ServletContextHelper} is used.
 * 
 * <p>
 * Those whiteboard services that are associated with the same
 * {@code ServletContextHelper} object will share the same
 * {@code ServletContext} object.
 * 
 * <p>
 * The behavior of the methods on the default {@code ServletContextHelper} is
 * defined as follows:
 * <ul>
 * <li>{@link #getMimeType(String) getMimeType} - Always returns {@code null}.</li>
 * <li>{@link #handleSecurity(HttpServletRequest, HttpServletResponse)
 * handleSecurity} - Always returns {@code true}.</li>
 * <li>{@link #getResource(String) getResource} - Assumes the named resource is
 * in the bundle of the whiteboard service, addressed from the root. This method
 * calls the whiteboard service bundle's {@code Bundle.getEntry} method, and
 * returns the appropriate URL to access the resource. On a Java runtime
 * environment that supports permissions, the Http Whiteboard implementation
 * needs to be granted {@code org.osgi.framework.AdminPermission[*,RESOURCE]}.</li>
 * <li>{@link #getResourcePaths(String) getResourcePaths} - Assumes that the
 * resources are in the bundle of the whiteboard service. This method calls
 * {@code Bundle.findEntries} method, and returns the found entries. On a Java
 * runtime environment that supports permissions, the Http Whiteboard
 * implementation needs to be granted
 * {@code org.osgi.framework.AdminPermission[*,RESOURCE]}.</li>
 * <li>{@link #getRealPath(String) getRealPath} - Always returns {@code null}.</li>
 * </ul>
 * 
 * @ThreadSafe
 * @author $Id: 3808a8548b4bc3878c068cfb80383228f0be9e74 $
 * @see HttpWhiteboardConstants#HTTP_WHITEBOARD_CONTEXT_NAME
 * @see HttpWhiteboardConstants#HTTP_WHITEBOARD_CONTEXT_PATH
 */
@ConsumerType
public abstract class ServletContextHelper {
	/**
	 * {@code HttpServletRequest} attribute specifying the name of the
	 * authenticated user. The value of the attribute can be retrieved by
	 * {@code HttpServletRequest.getRemoteUser}.
	 */
	public static final String	REMOTE_USER			= "org.osgi.service.http.authentication.remote.user";
	/**
	 * {@code HttpServletRequest} attribute specifying the scheme used in
	 * authentication. The value of the attribute can be retrieved by
	 * {@code HttpServletRequest.getAuthType}.
	 */
	public static final String	AUTHENTICATION_TYPE	= "org.osgi.service.http.authentication.type";
	/**
	 * {@code HttpServletRequest} attribute specifying the {@code Authorization}
	 * object obtained from the {@code org.osgi.service.useradmin.UserAdmin}
	 * service. The value of the attribute can be retrieved by
	 * {@code HttpServletRequest.getAttribute(ServletContextHelper.AUTHORIZATION)}
	 * .
	 */
	public static final String	AUTHORIZATION		= "org.osgi.service.useradmin.authorization";

	/** Bundle associated with this context. */
	private final Bundle		bundle;

	/**
	 * Construct a new context helper.
	 * 
	 * <p>
	 * If needed, the subclass will have to handle the association with a
	 * specific bundle.
	 */
	public ServletContextHelper() {
		this(null);
	}

	/**
	 * Construct a new context helper associated with the specified bundle.
	 * 
	 * @param bundle The bundle to be associated with this context helper.
	 */
	public ServletContextHelper(final Bundle bundle) {
		this.bundle = bundle;
	}

	/**
	 * Handles security for the specified request.
	 * <p>
	 * The Http Whiteboard implementation calls this method prior to servicing
	 * the specified request. This method controls whether the request is
	 * processed in the normal manner or an error is returned.
	 * <p>
	 * If the request requires authentication and the {@code Authorization}
	 * header in the request is missing or not acceptable, then this method
	 * should set the {@code WWW-Authenticate} header in the response object,
	 * set the status in the response object to Unauthorized(401) and return
	 * {@code false}. See also <a href="http://www.ietf.org/rfc/rfc2617.txt">RFC
	 * 2617: HTTP Authentication: Basic and Digest Access Authentication</a>.
	 * <p>
	 * If the request requires a secure connection and the {@code getScheme}
	 * method in the request does not return 'https' or some other acceptable
	 * secure protocol, then this method should set the status in the response
	 * object to Forbidden(403) and return {@code false}.
	 * <p>
	 * When this method returns {@code false}, the Http Whiteboard
	 * implementation will send the response back to the client, thereby
	 * completing the request. When this method returns {@code true}, the Http
	 * Whiteboard implementation will proceed with servicing the request.
	 * <p>
	 * If the specified request has been authenticated, this method must set the
	 * {@link #AUTHENTICATION_TYPE} request attribute to the type of
	 * authentication used, and the {@link #REMOTE_USER} request attribute to
	 * the remote user (request attributes are set using the
	 * {@code setAttribute} method on the request). If this method does not
	 * perform any authentication, it must not set these attributes.
	 * <p>
	 * If the authenticated user is also authorized to access certain resources,
	 * this method must set the {@link #AUTHORIZATION} request attribute to the
	 * {@code Authorization} object obtained from the
	 * {@code org.osgi.service.useradmin.UserAdmin} service.
	 * <p>
	 * The servlet responsible for servicing the specified request determines
	 * the authentication type and remote user by calling the
	 * {@code getAuthType} and {@code getRemoteUser} methods, respectively, on
	 * the request.
	 * <p>
	 * If there is the need to clean up resources at the end of the request, the
	 * method {@link #finishSecurity(HttpServletRequest, HttpServletResponse)}
	 * can be implemented. That method is only called if this method returns {@code true}.
	 *
	 * @param request The HTTP request.
	 * @param response The HTTP response.
	 * @return {@code true} if the request should be serviced, {@code false} if
	 *         the request should not be serviced and Http Whiteboard
	 *         implementation will send the response back to the client.
	 * @throws java.io.IOException May be thrown by this method. If this occurs,
	 *             the Http Whiteboard implementation will terminate the request
	 *             and close the socket.
	 * @see #finishSecurity(HttpServletRequest, HttpServletResponse)
	 */
	public boolean handleSecurity(final HttpServletRequest request,
			final HttpServletResponse response)
			throws IOException {
		return true;
	}

	/**
	 * Finishes the security context for the specified request.
	 * <p>
	 * Implementations of this service can implement this method to clean up
	 * resources which have been setup in
	 * {@link #handleSecurity(HttpServletRequest, HttpServletResponse)}.
	 * <p>
	 * This method is only called if
	 * {@link #handleSecurity(HttpServletRequest, HttpServletResponse)} returned
	 * {@code true} for the specified request. This method is called once the
	 * pipeline finishes processing or if an exception is thrown from within the
	 * pipeline execution.
	 * <p>
	 * The default implementation of this method does nothing.
	 * 
	 * @param request The HTTP request.
	 * @param response The HTTP response.
	 * @since 1.1
	 * @see #handleSecurity(HttpServletRequest, HttpServletResponse)
	 */
	public void finishSecurity(final HttpServletRequest request,
			final HttpServletResponse response) {
		// do nothing
	}

	/**
	 * Maps a resource name to a URL.
	 * <p>
	 * Called by the Http Whiteboard implementation to map the specified
	 * resource name to a URL. For servlets, the Http Whiteboard implementation
	 * will call this method to support the {@code ServletContext} methods
	 * {@code getResource} and {@code getResourceAsStream}. For resources, the
	 * Http Whiteboard implementation will call this method to locate the named
	 * resource.
	 * <p>
	 * The context can control from where resources come. For example, the
	 * resource can be mapped to a file in the bundle's persistent storage area
	 * via {@code BundleContext.getDataFile(name).toURI().toURL()} or to a
	 * resource in the context's bundle via {@code getClass().getResource(name)}
	 * 
	 * @param name The name of the requested resource.
	 * @return A URL that a Http Whiteboard implementation can use to read the
	 *         resource or {@code null} if the resource does not exist.
	 */
	public URL getResource(String name) {
		if ((name != null) && (bundle != null)) {
			if (name.startsWith("/")) {
				name = name.substring(1);
			}

			return bundle.getEntry(name);
		}
		return null;
	}

	/**
	 * Maps a name to a MIME type.
	 * 
	 * <p>
	 * Called by the Http Whiteboard implementation to determine the MIME type
	 * for the specified name. For whiteboard services, the Http Whiteboard
	 * implementation will call this method to support the
	 * {@code ServletContext} method {@code getMimeType}. For resource servlets,
	 * the Http Whiteboard implementation will call this method to determine the
	 * MIME type for the {@code Content-Type} header in the response.
	 *
	 * @param name The name for which to determine the MIME type.
	 * @return The MIME type (e.g. text/html) of the specified name or
	 *         {@code null} to indicate that the Http Whiteboard implementation
	 *         should determine the MIME type itself.
	 */
	public String getMimeType(final String name) {
		return null;
	}

	/**
	 * Returns a directory-like listing of all the paths to resources within the
	 * web application whose longest sub-path matches the supplied path
	 * argument.
	 * 
	 * <p>
	 * Called by the Http Whiteboard implementation to support the
	 * {@code ServletContext} method {@code getResourcePaths} for whiteboard
	 * services.
	 * 
	 * @param path The partial path used to match the resources, which must
	 *        start with a /.
	 * @return A Set containing the directory listing, or {@code null} if there
	 *         are no resources in the web application whose path begins with
	 *         the supplied path.
	 */
	public Set<String> getResourcePaths(final String path) {
		if ((path != null) && (bundle != null)) {
			final Enumeration<URL> e = bundle.findEntries(path, null, false);
			if (e != null) {
				final Set<String> result = new LinkedHashSet<String>();
				while (e.hasMoreElements()) {
					result.add(e.nextElement().getPath());
				}
				return result;
			}
		}
		return null;
	}

	/**
	 * Gets the real path corresponding to the given virtual path.
	 * 
	 * <p>
	 * Called by the Http Whiteboard implementation to support the
	 * {@code ServletContext} method {@code getRealPath} for whiteboard
	 * services.
	 * 
	 * @param path The virtual path to be translated to a real path.
	 * @return The real path, or {@code null} if the translation cannot be
	 *         performed.
	 */
	public String getRealPath(final String path) {
		return null;
	}
}
