org.eclipse.gyrex.http.application.Application.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.gyrex.http.application.Application.java

Source

/*******************************************************************************
 * Copyright (c) 2008, 2012 Gunnar Wagenknecht and others.
 * All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html.
 *
 * Contributors:
 *     Gunnar Wagenknecht - initial API and implementation
 *******************************************************************************/
package org.eclipse.gyrex.http.application;

import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

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

import org.eclipse.gyrex.context.IRuntimeContext;
import org.eclipse.gyrex.http.application.context.IApplicationContext;
import org.eclipse.gyrex.http.application.manager.IApplicationManager;
import org.eclipse.gyrex.http.application.provider.ApplicationProvider;
import org.eclipse.gyrex.http.internal.HttpActivator;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.PlatformObject;
import org.eclipse.core.runtime.Status;

import org.osgi.service.http.HttpContext;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A Gyrex HTTP application instance.
 * <p>
 * Gyrex defines HTTP applications as a point for bundling a set of
 * functionality offered via HTTP operating in a specific
 * {@link IRuntimeContext context}. Applications are contributed to the platform
 * by {@link ApplicationProvider providers}.
 * </p>
 * <p>
 * Applications are created lazily the first time a request for an application
 * is received. They are defined through an administrative API provided by
 * {@link IApplicationManager}. Applications are identified by an
 * {@link #getId() id}. At any time there is at most one single application
 * instance for the same {@link #getId() id}.
 * </p>
 * <p>
 * Although it may be possible to compare Gyrex HTTP applications to JavaEE web
 * applications, they do not provide the full set of functionality provided by a
 * JavaEE web application. The central point of entrance of a HTTP request is
 * {@link #handleRequest(HttpServletRequest, HttpServletResponse)}. From there
 * on its the responsibility of the application to deal with the request. This
 * allows for a great flexibility but also comes with big responsibility. It's
 * the task of the application to handle security and to deal with Servlets,
 * their registrations and filters and such things.
 * </p>
 * <p>
 * Similar to the OSGi Http Service, an application may allow to register
 * servlets and resources dynamically. However, it's the decision and
 * responsibility of the application to enable and support that dynamic
 * behavior. The creation of such dynamic behavior is supported by an
 * application through {@link IApplicationContext}.
 * </p>
 * <p>
 * An application defines a scope for all servlets and resources registered with
 * it. The underlying assumption is that resources and servlets are shared
 * within an application's boundaries. Therefore this class implements methods
 * similar to those defined in the {@link HttpContext} interface which - in
 * their default implementations - are delegated to the
 * {@link IApplicationContext}. It allows for better interoperability with OSGi,
 * the underlying servlet container and the registered servlets and resources.
 * </p>
 * <p>
 * Sharing servlets and resources across application instances is not supported
 * out-of-the-box for security and proper capsulation reasons.
 * </p>
 * <p>
 * This class may be subclassed by clients providing a custom application type.
 * Only references to methods defined in this class are valid.
 * </p>
 * 
 * @see ApplicationProvider
 */
public abstract class Application extends PlatformObject {

    private static final Logger LOG = LoggerFactory.getLogger(Application.class);

    private static String toFormattedDuration(final long duration) {
        if (duration < 1000L) {
            return String.format("%dms", duration);
        }
        if (duration < 60000L) {
            return String.format("%ds", duration / 1000L);
        }

        // this is likely too long
        return String.format("%dm", duration / 60000L);
    }

    /** the application id */
    private final String id;

    /** the context */
    private final IRuntimeContext context;

    /** the application context */
    private final AtomicReference<IApplicationContext> applicationContext = new AtomicReference<IApplicationContext>();

    /** the application status */
    private final AtomicReference<IStatus> status = new AtomicReference<IStatus>();
    /** deferred initialization */
    private final AtomicLong initTimestamp = new AtomicLong();

    private final Lock initLock = new ReentrantLock();

    /** the destroyed state */
    private final AtomicBoolean destroyed = new AtomicBoolean();

    /**
     * Creates a new application instance.
     * 
     * @param id
     *            the application id
     * @param context
     *            the context
     */
    protected Application(final String id, final IRuntimeContext context) {
        if (null == id) {
            throw new IllegalArgumentException("id must not be null");
        }
        if (null == context) {
            throw new IllegalArgumentException("context must not be null");
        }

        this.id = id.intern();
        this.context = context;
    }

    private void deferredInit() {
        try {
            if (!initLock.tryLock(2, TimeUnit.SECONDS)) {
                throw new ApplicationException(503, "Initialization In Progress");
            }
        } catch (final InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new ApplicationException(503, "Not Initialized");
        }
        try {
            if (initTimestamp.get() == 0) {
                try {
                    doInit();
                    initTimestamp.set(System.currentTimeMillis());
                } catch (final Exception e) {
                    throw new ApplicationException(503, "Inititalization Error", e);
                }
            }
        } finally {
            initLock.unlock();
        }
    }

    /**
     * Called by the platform to destroy the application when it's no longer
     * needed.
     * <p>
     * The implementation first sets an internal flag so that it stops receiving
     * requests. Next, it calls {@link #doDestroy()} and after that it releases
     * the reference to the {@link IApplicationContext} if available. Subclasses
     * may override {@link #doDestroy()} to perform necessary cleanup.
     * </p>
     * 
     * @noreference This method is not intended to be referenced by clients.
     */
    public final void destroy() {
        // set status
        destroyed.set(true);

        LOG.info("Stopping HTTP application {}...", getId());

        try {
            // destroy
            doDestroy();
        } finally {
            // unset
            applicationContext.set(null);
        }
    }

    /**
     * Called by {@link #destroy()} to release/destroy any application specific
     * resources.
     * <p>
     * The default implementation does nothing. Subclasses may override.
     * </p>
     */
    protected void doDestroy() {
        // empty
    }

    /**
     * Called by {@link #initialize(IApplicationContext)} to perform any
     * application specific initialization.
     * <p>
     * The default implementation does nothing. Subclasses may override.
     * </p>
     * 
     * @throws IllegalStateException
     *             in case the initialization can not be completed currently but
     *             may be repeated at a later time
     * @throws Exception
     *             in case of unrecoverable initialization failures
     */
    protected void doInit() throws IllegalStateException, Exception {
        // empty

    }

    /**
     * Returns the application context passed to
     * {@link #initialize(IApplicationContext)}.
     * 
     * @return the application context (maybe <code>null</code> if not supported
     *         by the application or the application has been destroyed)
     */
    protected IApplicationContext getApplicationContext() {
        return applicationContext.get();
    }

    /**
     * Returns the context the application operates in.
     * 
     * @return the context
     */

    public final IRuntimeContext getContext() {
        return context;
    }

    /**
     * Returns the application id.
     * 
     * @return the application id
     */
    public final String getId() {
        return id;
    }

    /**
     * Returns the status of the application.
     * <p>
     * The status indicates if the application will operate properly. If any
     * status with a severity other than {@link IStatus#OK} is returned the
     * platform may reject request to this application with a
     * {@link HttpServletResponse#SC_SERVICE_UNAVAILABLE} error and the message
     * provided in the status object returned.
     * </p>
     * <p>
     * Clients that want to modify the application status should do so in a
     * background operation and set it via {@link #setStatus(IStatus)}.
     * </p>
     * <p>
     * If the application is {@link #destroy() destroyed}, this method will
     * always return a status of severity {@link IStatus#CANCEL} to stop
     * receiving any further requests.
     * </p>
     * 
     * @return the application status
     */
    public final IStatus getStatus() {
        // check if destroyed
        if (destroyed.get()) {
            return Status.CANCEL_STATUS;
        }

        // get status
        final IStatus status = this.status.get();
        if (null != status) {
            return status;
        }

        // assume OK
        return Status.OK_STATUS;
    }

    /**
     * Called by the platform to allow the application to respond to a HTTP
     * request.
     * <p>
     * The default implementation first calls
     * {@link #handleSecurity(HttpServletRequest, HttpServletResponse)} to
     * ensure that the request is allowed to be handled. It then asks the
     * {@link IApplicationContext} to handle the request.
     * </p>
     * 
     * @param request
     *            the <code>HttpServletRequest</code> object that contains the
     *            client's request
     * @param response
     *            the <code>HttpServletResponse</code> object that contains the
     *            servlet's response
     * @exception ApplicationException
     *                if an exception occurs that interferes with the
     *                application's normal operation
     * @exception IOException
     *                if an input or output exception occurs
     * @see Servlet#service(javax.servlet.ServletRequest,
     *      javax.servlet.ServletResponse)
     */
    public void handleRequest(final HttpServletRequest request, final HttpServletResponse response)
            throws IOException, ApplicationException {
        // check for deferred initialization
        if (initTimestamp.get() == 0) {
            deferredInit();
        }

        // check security
        if (!handleSecurity(request, response)) {
            return;
        }

        // get the context
        final IApplicationContext context = getApplicationContext();
        if (null == context) {
            // if there is no context this method should not be overridden
            response.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
            return;
        }

        // let the application context handle the request
        if (context.handleRequest(request, response)) {
            return;
        }

        // return 404, no resource registered
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
    }

    /**
     * Handles security for the specified request.
     * <p>
     * The default implementation of
     * {@link #handleRequest(HttpServletRequest, HttpServletResponse)} 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>
     * <p>
     * The purpose of this method is similar to
     * {@link HttpContext#handleSecurity(HttpServletRequest, HttpServletResponse)}
     * but adapted to the {@link Application} scope. The following documentation
     * was inherited and adapted from {@link HttpContext}.
     * </p>
     * <p>
     * If the request requires authentication and the Authorization header in
     * the request is missing or not acceptable, then this method should set the
     * WWW-Authenticate header in the response object, set the status in the
     * response object to Unauthorized(401) and return <code>false</code>. See
     * also RFC 2617: <i>HTTP Authentication: Basic and Digest Access
     * Authentication </i> (available at http://www.ietf.org/rfc/rfc2617.txt).
     * </p>
     * <p>
     * If the request requires a secure connection and the
     * <code>getScheme</code> 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</code>.
     * </p>
     * <p>
     * When this method returns <code>false</code>, the application will send
     * the response back to the client, thereby completing the request. When
     * this method returns <code>true</code>, the Http Service will proceed with
     * servicing the request.
     * </p>
     * <p>
     * If the specified request has been authenticated, this method must set the
     * {@link HttpContext#AUTHENTICATION_TYPE} request attribute to the type of
     * authentication used, and the {@link HttpContext#REMOTE_USER} request
     * attribute to the remote user (request attributes are set using the
     * <code>setAttribute</code> method on the request). If this method does not
     * perform any authentication, it must not set these attributes.
     * </p>
     * <p>
     * If the authenticated user is also authorized to access certain resources,
     * this method must set the {@link HttpContext#AUTHORIZATION} request
     * attribute to the <code>Authorization</code> object obtained from the
     * <code>org.osgi.service.useradmin.UserAdmin</code> service.
     * </p>
     * <p>
     * The servlet responsible for servicing the specified request determines
     * the authentication type and remote user by calling the
     * <code>getAuthType</code> and <code>getRemoteUser</code> methods,
     * respectively, on the request.
     * </p>
     * <p>
     * The default implementation just returns <code>true</code>. Subclasses may
     * overwrite.
     * </p>
     * 
     * @param request
     *            the HTTP request
     * @param response
     *            the HTTP response
     * @return <code>true</code> if the request should be serviced,
     *         <code>false</code> if the request should not be serviced and Http
     *         Service will send the response back to the client.
     * @throws java.io.IOException
     *             may be thrown by this method. If this occurs, the Http
     *             Service will terminate the request and close the socket.
     * @see HttpContext#handleSecurity(HttpServletRequest, HttpServletResponse)
     */
    protected boolean handleSecurity(final HttpServletRequest request, final HttpServletResponse response)
            throws IOException {
        // TODO integrate with security from container
        return true;
    }

    /**
     * Called by the platform to initialize the application.
     * <p>
     * This implementations remembers the {@link IApplicationContext} and then
     * calls {@link #doInit()}. Subclasses may override {@link #doInit()}.
     * </p>
     * 
     * @param applicationContext
     *            the application context.
     * @throws Exception
     *             in case of unrecoverable initialization failures
     * @noreference This method is not intended to be referenced by clients.
     */
    public final void initialize(final IApplicationContext applicationContext) throws Exception {
        if (null == applicationContext) {
            throw new IllegalArgumentException("application context must not be null");
        }
        this.applicationContext.set(applicationContext);

        try {
            LOG.info("Starting HTTP application {}...", getId());
            final long started = System.currentTimeMillis();
            doInit();
            final long finished = System.currentTimeMillis();
            initTimestamp.set(finished);
            LOG.info("Finished starting HTTP application {} (in {}).", getId(),
                    toFormattedDuration(finished - started));
        } catch (final IllegalStateException e) {
            // log a warning
            LOG.warn("Unable to initialize application {} at this time ({}). Will retry at next opportunity.",
                    new Object[] { getId(), e.getMessage(), e });
            // and deferred initialization
            initTimestamp.set(0);
        } catch (final Exception e) {
            // log error
            LOG.error("Unable to initialize application {} due to unrecoverable error. {}",
                    new Object[] { getId(), ExceptionUtils.getRootCauseMessage(e), e });
            // and re-throw (wrapped into a core exception)
            if (e instanceof CoreException) {
                throw e;
            }
            throw new CoreException(new Status(IStatus.ERROR, HttpActivator.SYMBOLIC_NAME, String.format(
                    "Error while initializing applicaion %s. %s", id, ExceptionUtils.getRootCauseMessage(e)), e));
        }
    }

    /**
     * Sets or resets the application status.
     * 
     * @param status
     *            the status to set, or <code>null</code> to reset
     * @see #getStatus()
     */
    public final void setStatus(final IStatus status) {
        this.status.set(status);
    }

    @Override
    public String toString() {
        final StringBuilder toString = new StringBuilder();
        toString.append(getClass().getSimpleName());
        toString.append("[");
        toString.append(id);
        toString.append("]");
        return toString.toString();
    }
}