com.kurento.kmf.content.internal.base.AbstractHttpContentSession.java Source code

Java tutorial

Introduction

Here is the source code for com.kurento.kmf.content.internal.base.AbstractHttpContentSession.java

Source

/*
 * (C) Copyright 2013 Kurento (http://kurento.org/)
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public License
 * (LGPL) version 2.1 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl-2.1.html
 *
 * 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.
 *
 */
package com.kurento.kmf.content.internal.base;

import java.util.concurrent.Future;

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

import org.springframework.beans.factory.annotation.Autowired;

import com.kurento.kmf.common.exception.Assert;
import com.kurento.kmf.common.exception.KurentoMediaFrameworkException;
import com.kurento.kmf.common.exception.internal.ExceptionUtils;
import com.kurento.kmf.common.exception.internal.ServletUtils;
import com.kurento.kmf.content.ContentHandler;
import com.kurento.kmf.content.ContentSession;
import com.kurento.kmf.content.HttpContentSession;
import com.kurento.kmf.content.internal.ContentSessionManager;
import com.kurento.kmf.content.internal.StreamingProxy;
import com.kurento.kmf.content.internal.StreamingProxyListener;
import com.kurento.kmf.content.jsonrpc.JsonRpcResponse;
import com.kurento.kmf.media.HttpEndpoint;
import com.kurento.kmf.media.events.ErrorEvent;
import com.kurento.kmf.media.events.MediaEventListener;
import com.kurento.kmf.media.events.MediaSessionStartedEvent;
import com.kurento.kmf.media.events.MediaSessionTerminatedEvent;
import com.kurento.kmf.repository.HttpSessionErrorEvent;
import com.kurento.kmf.repository.HttpSessionStartedEvent;
import com.kurento.kmf.repository.HttpSessionTerminatedEvent;
import com.kurento.kmf.repository.RepositoryHttpEndpoint;
import com.kurento.kmf.repository.RepositoryHttpEventListener;
import com.kurento.kmf.repository.RepositoryItem;

/**
 * 
 * Abstract definition for HTTP based content request.
 * 
 * @author Luis Lpez (llopez@gsyc.es)
 * @version 1.0.0
 */
public abstract class AbstractHttpContentSession extends AbstractContentSession implements HttpContentSession {

    @Autowired
    private StreamingProxy proxy;

    protected boolean useControlProtocol;

    protected boolean redirect;

    protected volatile Future<?> tunnellingProxyFuture;

    private RepositoryHttpEndpoint repositoryHttpEndpoint;

    public AbstractHttpContentSession(ContentHandler<? extends ContentSession> handler,
            ContentSessionManager manager, AsyncContext asyncContext, String contentId, boolean redirect,
            boolean useControlProtocol) {
        super(handler, manager, asyncContext, contentId);
        this.useControlProtocol = useControlProtocol;
        this.redirect = redirect;
        if (!useControlProtocol) {
            goToState(STATE.HANDLING,
                    "Cannot go to STATE.HANDLING from state " + getState() + ". This condition should never happen",
                    1); // TODO
        }
    }

    protected abstract RepositoryHttpEndpoint createRepositoryHttpEndpoint(RepositoryItem repositoryItem);

    public void start(HttpEndpoint httpEndpoint) {
        try {
            releaseOnTerminate(httpEndpoint);
            Assert.notNull(httpEndpoint, "Illegal null httpEndpoint provided", 10028);
            goToState(STATE.STARTING, "Cannot start HttpEndpoint in state " + getState()
                    + ". This is probably due to an explicit session termination comming from another thread", 1); // TODO

            getLogger().info(
                    "Activating media for " + this.getClass().getSimpleName() + " with httpEndpoint provided ");

            activateMedia(httpEndpoint, null);

        } catch (KurentoMediaFrameworkException ke) {
            internalTerminateWithError(null, ke.getCode(), ke.getMessage(), null);
            throw ke;
        } catch (Throwable t) {
            KurentoMediaFrameworkException kmfe = new KurentoMediaFrameworkException(t.getMessage(), t, 20029);
            internalTerminateWithError(null, kmfe.getCode(), kmfe.getMessage(), null);
            throw kmfe;
        }
    }

    @Override
    public void start(RepositoryItem repositoryItem) {
        try {
            Assert.notNull(repositoryItem, "Illegal null repositoryItem provided", 10027);

            goToState(STATE.STARTING, "Cannot start HttpPlayerSession in state " + getState()
                    + ". This is probably due to an explicit session termination comming from another thread", 1); // TODO

            getLogger().info("Activating media for " + this.getClass().getSimpleName() + " with repositoryItemId "
                    + repositoryItem.getId());
            repositoryHttpEndpoint = createRepositoryHttpEndpoint(repositoryItem);

            activateMedia(repositoryHttpEndpoint);
        } catch (KurentoMediaFrameworkException ke) {
            internalTerminateWithError(null, ke.getCode(), ke.getMessage(), null);
            throw ke;
        } catch (Throwable t) {
            KurentoMediaFrameworkException kmfe = new KurentoMediaFrameworkException(t.getMessage(), t, 20029);
            internalTerminateWithError(null, kmfe.getCode(), kmfe.getMessage(), null);
            throw kmfe;
        }
    }

    /*
     * This is an utility method designed for minimizing code replication. For
     * it to work, one and only one of the two parameters must be null;
     */
    protected void activateMedia(HttpEndpoint httpEndpoint, final Runnable runOnContentStart) {

        Assert.isTrue(httpEndpoint != null, "Internal error. Cannot activate null HttpEndpoint reference", 1); // TODO

        // Manage fatal errors occurring in the pipeline
        httpEndpoint.getMediaPipeline().addErrorListener(new MediaEventListener<ErrorEvent>() {
            @Override
            public void onEvent(ErrorEvent error) {
                getLogger().error(error.getDescription()); // TODO:
                // improve
                // message
                internalTerminateWithError(null, error.getErrorCode(), error.getDescription(), null);
            }
        });

        // Generate appropriate actions when content is started
        httpEndpoint.addMediaSessionStartedListener(new MediaEventListener<MediaSessionStartedEvent>() {
            @Override
            public void onEvent(MediaSessionStartedEvent event) {
                callOnContentStartedOnHanlder();
                getLogger().info("Received event with type " + event.getType());
                if (runOnContentStart != null) {
                    runOnContentStart.run();
                }
            }
        });

        // Manage end of media session
        httpEndpoint.addMediaSessionTerminatedListener(new MediaEventListener<MediaSessionTerminatedEvent>() {

            @Override
            public void onEvent(MediaSessionTerminatedEvent event) {
                internalTerminateWithoutError(null, 1, "TODO", null);// TODO

            }
        });

        // Generate appropriate actions when media session is terminated

        String answerUrl = httpEndpoint.getUrl();
        getLogger().info("HttpEndpoint.getUrl = " + answerUrl);

        Assert.notNull(answerUrl, "Received null url from HttpEndpoint", 20012);
        Assert.isTrue(answerUrl.length() > 0, "Received invalid empty url from media server", 20012);

        goToState(STATE.ACTIVE, "Cannot start session in sate " + getState()
                + ". This is probably due to an explicit termination of the session from a different threaad", 1);

        if (useControlProtocol) {
            answerActivateMediaRequest4JsonControlProtocolConfiguration(answerUrl);
        } else if (redirect) {
            answerActivateMediaRequest4SimpleHttpConfigurationWithRedirect(answerUrl);
        } else {
            answerActivateMediaRequest4SimpleHttpConfigurationWithTunnel(answerUrl);
        }
    }

    protected void activateMedia(RepositoryHttpEndpoint repositoryHttpEndpoint) {

        // Manage fatal errors occurring in the pipeline
        repositoryHttpEndpoint.addSessionErrorListener(new RepositoryHttpEventListener<HttpSessionErrorEvent>() {
            @Override
            public void onEvent(HttpSessionErrorEvent event) {
                getLogger().error(event.getDescription()); // TODO:
                internalTerminateWithError(null, 1, // TODO
                        event.getDescription(), null);
            }
        });

        // Generate appropriate actions when content is started
        // TODO: addMediaSessionStartListener (symmetry)
        repositoryHttpEndpoint
                .addSessionStartedListener(new RepositoryHttpEventListener<HttpSessionStartedEvent>() {

                    @Override
                    public void onEvent(HttpSessionStartedEvent event) {
                        callOnContentStartedOnHanlder();
                        getLogger().info("Received event with type " + event.getClass().getSimpleName());
                    }
                });

        repositoryHttpEndpoint
                .addSessionTerminatedListener(new RepositoryHttpEventListener<HttpSessionTerminatedEvent>() {

                    @Override
                    public void onEvent(HttpSessionTerminatedEvent event) {
                        getLogger().info("Received event with type " + event.getClass().getSimpleName());
                        internalTerminateWithoutError(null, 1, "MediaServer MediaSessionTerminated", null); // TODO
                    }
                });

        String answerUrl = repositoryHttpEndpoint.getURL();
        getLogger().info("RepoItemHttpElement.getUrl = " + answerUrl);

        Assert.notNull(answerUrl, "Received null url from RepoItemHttpElement", 20012);
        Assert.isTrue(answerUrl.length() > 0, "Received invalid empty url from RepoItemHttpElement", 20012);

        goToState(STATE.ACTIVE, "Cannot start session in sate " + getState()
                + ". This is probably due to an explicit termination of the session from a different threaad", 1);

        if (useControlProtocol) {
            answerActivateMediaRequest4JsonControlProtocolConfiguration(answerUrl);
        } else if (redirect) {
            answerActivateMediaRequest4SimpleHttpConfigurationWithRedirect(answerUrl);
        } else {
            answerActivateMediaRequest4SimpleHttpConfigurationWithDispatch(repositoryHttpEndpoint.getDispatchURL());
        }

    }

    /**
     * Provide an HTTP response, depending of which redirect strategy is used:
     * it could a redirect (HTTP 307, Temporary Redirect), or a tunneled
     * response using the Streaming Proxy.
     * 
     * @param url
     *            Content URL
     * @throws ContentException
     *             Exception in the media server
     */
    private void answerActivateMediaRequest4SimpleHttpConfigurationWithRedirect(String url) {
        try {
            HttpServletResponse response = (HttpServletResponse) initialAsyncCtx.getResponse();
            getLogger().info("Sending redirect to " + url);
            response.setStatus(HttpServletResponse.SC_TEMPORARY_REDIRECT);
            response.setHeader("Location", url);

        } catch (Throwable t) {
            throw new KurentoMediaFrameworkException(t.getMessage(), t, 20013);
        } finally {
            initialAsyncCtx.complete();
            initialAsyncCtx = null;
        }
    }

    private void answerActivateMediaRequest4SimpleHttpConfigurationWithTunnel(String url) {
        try {
            HttpServletResponse response = (HttpServletResponse) initialAsyncCtx.getResponse();
            final HttpServletRequest request = (HttpServletRequest) initialAsyncCtx.getRequest();
            getLogger().info("Activating tunneling proxy to " + url);
            tunnellingProxyFuture = proxy.tunnelTransaction(request, response, url, new StreamingProxyListener() {

                @Override
                public void onProxySuccess() {
                    tunnellingProxyFuture = null;
                    // Parameters no matter, no answer will be sent
                    // given that we are already in ACTIVE state
                    terminate(0, "");
                    request.getAsyncContext().complete();
                }

                @Override
                public void onProxyError(String message, int errorCode) {
                    tunnellingProxyFuture = null;
                    // Parameters no matter, no answer will be sent
                    // given that we are already in ACTIVE state
                    terminate(errorCode, message);
                    request.getAsyncContext().complete();
                }
            });

        } catch (Throwable t) {
            throw new KurentoMediaFrameworkException(t.getMessage(), t, 20013);
        } finally {
            initialAsyncCtx = null;
        }
    }

    private void answerActivateMediaRequest4SimpleHttpConfigurationWithDispatch(String path) {
        try {
            initialAsyncCtx.dispatch(path);
        } catch (Throwable t) {
            throw new KurentoMediaFrameworkException(t.getMessage(), t, 20013);
        } finally {
            initialAsyncCtx = null;
        }
    }

    /**
     * Provide an HTTP response, when a JSON signaling protocol strategy is
     * used.
     * 
     * @param url
     *            Content URL
     * @throws ContentException
     *             Exception in the media server
     */
    private void answerActivateMediaRequest4JsonControlProtocolConfiguration(String url) {
        protocolManager.sendJsonAnswer(initialAsyncCtx,
                JsonRpcResponse.newStartUrlResponse(url, sessionId, initialJsonRequest.getId()));
        initialAsyncCtx = null;
        initialJsonRequest = null;
    }

    /**
     * Control protocol accessor (getter).
     * 
     * @return Control protocol strategy
     */
    @Override
    public boolean useControlProtocol() {
        return useControlProtocol;
    }

    @Override
    protected void sendErrorAnswerOnInitialContext(int code, String description) {
        if (useControlProtocol) {
            super.sendErrorAnswerOnInitialContext(code, description);
        } else {
            try {
                ServletUtils.sendHttpError((HttpServletRequest) initialAsyncCtx.getRequest(),
                        (HttpServletResponse) initialAsyncCtx.getResponse(), ExceptionUtils.getHttpErrorCode(code),
                        description);
            } catch (ServletException e) {
                getLogger().error(e.getMessage(), e);
                throw new KurentoMediaFrameworkException(e, 20026);
            }
        }
    }

    @Override
    protected void sendRejectOnInitialContext(int code, String description) {
        if (useControlProtocol) {
            super.sendRejectOnInitialContext(code, description);
        } else {
            try {
                ServletUtils.sendHttpError((HttpServletRequest) initialAsyncCtx.getRequest(),
                        (HttpServletResponse) initialAsyncCtx.getResponse(), ExceptionUtils.getHttpErrorCode(code),
                        description);
            } catch (ServletException e) {
                getLogger().error(e.getMessage(), e);
                throw new KurentoMediaFrameworkException(e, 20026);
            }
        }
    }

    /**
     * Release Streaming proxy.
     */
    @Override
    protected synchronized void destroy() {
        super.destroy();

        if (repositoryHttpEndpoint != null) {
            repositoryHttpEndpoint.stop();
            repositoryHttpEndpoint = null;
        }

        Future<?> localTunnelingProxyFuture = tunnellingProxyFuture;
        if (localTunnelingProxyFuture != null) {
            localTunnelingProxyFuture.cancel(true);
            tunnellingProxyFuture = null;
        }
    }
}