/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2012 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id: JerseyApplicationPublisher.java 22238 2012-03-13 13:15:13Z sauthieg $
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.jaxrs.jersey.internal;

import com.sun.jersey.spi.container.servlet.ServletContainer;
import org.osgi.framework.ServiceReference;
import org.osgi.service.http.HttpService;
import org.osgi.service.http.NamespaceException;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;

import javax.servlet.ServletException;
import javax.ws.rs.core.Application;
import javax.ws.rs.ext.RuntimeDelegate;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * <p>The {@code JerseyApplicationPublisher} is responsible of publishing
 * JAX-RS {@code Application} registered in the OSGi service registry
 * onto the {@code HttpService}.</p>
 *
 * <p>Without any configuration (and any dependency on a JAX-RS implementation),
 * a client can easily expose his {@code Application} with the following code:</p>
 * <pre>
 *     bundleContext.registerService(Application.class, new MyApplication(), null);
 * </pre>
 *
 * <p>This RESTful application will be available under the following path:
 * {@literal /{bundle-symbolic-name}/{bundle-version}}.</p>
 *
 * <p>Name collision could happen if the same {@code Bundle} provides more than
 * 1 {@code Application}.</p>
 *
 * <p>To avoid collisions, the client should provide a unique value into the
 * {@code jonas.jaxrs.context-path} service property when registering his services.</p>
 *
 * <pre>
 *     Properties sp = new Properties();
 *     sp.setProperty("jonas.jaxrs.context-path", "/my-app");
 *     bundleContext.registerService(Application.class, new MyApplication(), sp);
 * </pre>                                                         O
 *
 * <p>Notice that {@code @ApplicationPath} (if provided) will be respected
 * (appended to the context path).</p>
 *
 * @author Loic Albertin
 */
public class JerseyApplicationPublisher {

    private static final Log LOGGER = LogFactory.getLog(JerseyApplicationPublisher.class);

    /**
     * Optional service property that defines the web context name of the REST application.
     */
    public static final String CONTEXT_PROPERTY = "jonas.jaxrs.context-path";

    /**
     * HttpService used to register the Jersey Servlet(s).
     */
    private HttpService httpService;

    /**
     * Bind ServiceReference to the registered context alias.
     */
    private Map<ServiceReference, String> aliases = new HashMap<ServiceReference, String>();

    /**
     * Dependency on the JAX-RS RuntimeDelegate to ensure that the
     * JAX-RS container is up and running.
     */
    private RuntimeDelegate delegate;

    /**
     * Bind the required OSGi HTTP Service.
     * @param httpService required service
     */
    public void setHttpService(final HttpService httpService) {
        this.httpService = httpService;
    }

    /**
     * Stop the publisher, un-registering all managed REST applications.
     */
    public void stop() {
        // Un-register all managed Application(s)
        // Need to copy the content first to avoid ConcurrentModificationException
        Set<ServiceReference> references = new HashSet<ServiceReference>(aliases.keySet());
        for (ServiceReference reference : references) {
            unregisterServletContainer(reference);
        }
    }

    /**
     * Manage a newly registered {@code Application} service.
     * @param application REST application
     * @param reference the associated {@code ServiceReference}.
     */
    public void bindApplication(final Application application,
                                final ServiceReference reference) {

        // Compute the Web context path
        String context = PathUtils.getContextPath(application, reference);
        try {
            // Try to register the Servlet
            httpService.registerServlet(context, new ServletContainer(application), null, null);
            aliases.put(reference, context);
            LOGGER.debug("REST Application {0} published under context ''{1}''",
                    application.getClass().getName(),
                    context);
        } catch (ServletException e) {
            LOGGER.error("REST Application {0} in error",
                         application.getClass().getName(),
                         e);
        } catch (NamespaceException e) {
            LOGGER.error("REST Application {0} is using an already bound web context name ''{}''",
                         application.getClass().getName(),
                         context,
                         e);
        }
    }

    /**
     * Un-manage the given Application.
     * @param reference reference of the Application service.
     */
    public void unbindApplication(final ServiceReference reference) {
        unregisterServletContainer(reference);
    }

    /**
     * Un-register the alias associated to the given {@code ServiceReference}.
     * @param reference associated to a web context alias
     */
    private void unregisterServletContainer(final ServiceReference reference) {
        String context = aliases.remove(reference);
        httpService.unregister(context);
        LOGGER.debug("Un-registered REST Application ''{0}''", context);
    }

}