org.zodiark.service.publisher.PublisherServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.zodiark.service.publisher.PublisherServiceImpl.java

Source

/*
 * Copyright 2013-2014 High-Level Technologies
 *
 * 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.zodiark.service.publisher;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.atmosphere.cpr.AtmosphereResource;
import org.atmosphere.cpr.AtmosphereResourceEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zodiark.protocol.Envelope;
import org.zodiark.protocol.Message;
import org.zodiark.protocol.Paths;
import org.zodiark.server.Context;
import org.zodiark.server.EventBus;
import org.zodiark.server.Reply;
import org.zodiark.server.ReplyException;
import org.zodiark.server.annotation.On;
import org.zodiark.service.EndpointUtils;
import org.zodiark.service.Error;
import org.zodiark.service.RetrieveMessage;
import org.zodiark.service.Session;
import org.zodiark.service.db.result.ModeId;
import org.zodiark.service.db.result.ShowId;
import org.zodiark.service.db.result.Status;
import org.zodiark.service.state.EndpointState;
import org.zodiark.service.subscriber.SubscriberEndpoint;
import org.zodiark.service.wowza.WowzaUUID;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import static org.atmosphere.cpr.AtmosphereResourceEventListenerAdapter.OnDisconnect;
import static org.zodiark.protocol.Paths.BROADCASTER_CREATE;
import static org.zodiark.protocol.Paths.DB_ENDPOINT_STATE;
import static org.zodiark.protocol.Paths.DB_GET_WORD_PASSTHROUGH;
import static org.zodiark.protocol.Paths.DB_POST_PUBLISHER_ONDEMAND_END;
import static org.zodiark.protocol.Paths.DB_POST_PUBLISHER_ONDEMAND_START;
import static org.zodiark.protocol.Paths.DB_POST_PUBLISHER_SESSION_CREATE;
import static org.zodiark.protocol.Paths.DB_PUBLISHER_ACTIONS;
import static org.zodiark.protocol.Paths.DB_PUBLISHER_AVAILABLE_ACTIONS_PASSTHROUGHT;
import static org.zodiark.protocol.Paths.DB_PUBLISHER_ERROR_REPORT;
import static org.zodiark.protocol.Paths.DB_PUBLISHER_LOAD_CONFIG;
import static org.zodiark.protocol.Paths.DB_PUBLISHER_LOAD_CONFIG_ERROR_PASSTHROUGHT;
import static org.zodiark.protocol.Paths.DB_PUBLISHER_LOAD_CONFIG_GET;
import static org.zodiark.protocol.Paths.DB_PUBLISHER_PUBLIC_MODE;
import static org.zodiark.protocol.Paths.DB_PUBLISHER_PUBLIC_MODE_END;
import static org.zodiark.protocol.Paths.DB_PUBLISHER_SAVE_CONFIG;
import static org.zodiark.protocol.Paths.DB_PUBLISHER_SAVE_CONFIG_PUT;
import static org.zodiark.protocol.Paths.DB_PUBLISHER_SETTINGS_SHOW;
import static org.zodiark.protocol.Paths.DB_PUBLISHER_SETTINGS_SHOW_GET_PASSTHROUGHT;
import static org.zodiark.protocol.Paths.DB_PUBLISHER_SETTINGS_SHOW_SAVE;
import static org.zodiark.protocol.Paths.DB_PUBLISHER_SHARED_PRIVATE_END;
import static org.zodiark.protocol.Paths.DB_PUBLISHER_SHARED_PRIVATE_START;
import static org.zodiark.protocol.Paths.DB_PUBLISHER_SHARED_PRIVATE_START_POST;
import static org.zodiark.protocol.Paths.DB_PUBLISHER_SHOW_END;
import static org.zodiark.protocol.Paths.DB_PUBLISHER_SHOW_START;
import static org.zodiark.protocol.Paths.DB_PUBLISHER_SUBSCRIBER_PROFILE_GET_PASSTHROUGH;
import static org.zodiark.protocol.Paths.DB_PUBLISHER_SUBSCRIBER_PROFILE_PUT;
import static org.zodiark.protocol.Paths.DB_SUBSCRIBER_BLOCK;
import static org.zodiark.protocol.Paths.DB_SUBSCRIBER_EJECT;
import static org.zodiark.protocol.Paths.ERROR_STREAMING_SESSION;
import static org.zodiark.protocol.Paths.FAILED_PUBLISHER_STREAMING_SESSION;
import static org.zodiark.protocol.Paths.PUBLISHER_ABOUT_READY;
import static org.zodiark.protocol.Paths.RETRIEVE_PUBLISHER;
import static org.zodiark.protocol.Paths.RETRIEVE_SUBSCRIBER;
import static org.zodiark.protocol.Paths.VALIDATE_PUBLISHER_STREAMING_SESSION;
import static org.zodiark.protocol.Paths.WOWZA_CONNECT;

/**
 * The Publisher's application logic for validating, creating and starting a {@link org.zodiark.service.session.StreamingSession}
 */
@On(Paths.SERVICE_PUBLISHER)
public class PublisherServiceImpl implements PublisherService, Session<PublisherEndpoint> {

    private final ConcurrentHashMap<String, PublisherEndpoint> endpoints = new ConcurrentHashMap<>();
    private final Logger logger = LoggerFactory.getLogger(PublisherServiceImpl.class);

    @Inject
    public EventBus eventBus;

    @Inject
    public ObjectMapper mapper;

    @Inject
    public Context context;

    private EndpointUtils<PublisherEndpoint> utils;

    @PostConstruct
    public void init() {
        utils = new EndpointUtils(eventBus, mapper, endpoints);
    }

    @Override
    public void reactTo(Envelope e, AtmosphereResource r, Reply reply) {
        logger.trace("Handling Publisher Envelop {} to Service {}", e, r.uuid());
        String path = e.getMessage().getPath();
        switch (path) {
        case DB_POST_PUBLISHER_SESSION_CREATE:
            createSession(e, r);
            break;
        case VALIDATE_PUBLISHER_STREAMING_SESSION:
            createOrJoinStreamingSession(e, r);
            break;
        case DB_PUBLISHER_SHOW_START:
            startStreamingSession(e, r);
            break;
        case DB_PUBLISHER_ERROR_REPORT:
            reportError(e, r);
            break;
        case FAILED_PUBLISHER_STREAMING_SESSION:
            errorStreamingSession(e);
            break;
        case DB_PUBLISHER_SHOW_END:
            terminateStreamingSession(e, r);
            break;
        case DB_PUBLISHER_SAVE_CONFIG:
            saveConfig(e, r);
            break;
        case DB_PUBLISHER_SETTINGS_SHOW:
            loadOrSaveShow(e, r);
            break;
        case DB_POST_PUBLISHER_ONDEMAND_START:
            onDemandStart(e, r);
            break;
        case DB_POST_PUBLISHER_ONDEMAND_END:
            onDemandEnd(e, r);
            break;
        case DB_GET_WORD_PASSTHROUGH:
            getMotd(e, r);
            break;
        case DB_PUBLISHER_SUBSCRIBER_PROFILE_GET_PASSTHROUGH:
            getSubscriberProfile(path, e);
            break;
        case DB_PUBLISHER_SUBSCRIBER_PROFILE_PUT:
            updateSubscriberProfile(path, e);
            break;
        case DB_PUBLISHER_SHARED_PRIVATE_START:
            sharedPrivateSession(e, r);
            break;
        case DB_PUBLISHER_SHARED_PRIVATE_END:
            endSharedPrivateSession(e, r);
            break;
        case DB_SUBSCRIBER_EJECT:
        case DB_SUBSCRIBER_BLOCK:
            validateAndStatusEvent(path, e);
            break;
        case DB_PUBLISHER_PUBLIC_MODE:
        case DB_PUBLISHER_PUBLIC_MODE_END:
            publisherPublicMode(path, e);
            break;
        case DB_PUBLISHER_ACTIONS:
            savesAction(path, e);
            break;
        default:
            throw new IllegalStateException("Invalid Message Path " + path);
        }
    }

    private void savesAction(String path, Envelope e) {
        utils.statusEvent(path, e);
    }

    private void publisherPublicMode(final String path, final Envelope e) {

        final PublisherEndpoint p = utils.retrieve(e.getUuid());
        if (!utils.validateAll(p, e))
            ;

        utils.statusEvent(path, e, p, new Reply<Status, String>() {
            @Override
            public void ok(Status status) {
                logger.trace("Status {}", status);

                String[] pathSegments = path.split("/");
                if (pathSegments[6].endsWith("start")) {
                    p.state().modeId(ModeId.PUBLIC);
                } else {
                    p.state().modeId(ModeId.VOID);
                }
                response(e, p, utils.constructMessage(path, utils.writeAsString(status), e.getMessage().getUUID()));
            }

            @Override
            public void fail(ReplyException replyException) {
                error(e, p,
                        utils.constructMessage(path,
                                utils.writeAsString(new org.zodiark.service.Error().error("Unauthorized")),
                                e.getMessage().getUUID()));
            }
        });
    }

    private void validateAndStatusEvent(String path, Envelope e) {

        final PublisherEndpoint p = utils.retrieve(e.getUuid());
        if (!utils.validate(p, e))
            return;

        String[] paths = e.getMessage().getPath().split("/");

        boolean subscriberOk = validateSubscriberState(paths[5], p);

        // TODO: utils.validate Subscriber
        //        if (!isValid.get()) {
        //            error(e, p, utils.constructMessage("/error", "No Subscriber for " + paths[5]));
        //        }

        utils.statusEvent(path, e);
    }

    private boolean validateSubscriberState(String subscriberId, final PublisherEndpoint p) {
        final AtomicBoolean isValid = new AtomicBoolean();
        // DAangerous if the path change.
        eventBus.message(RETRIEVE_SUBSCRIBER, subscriberId, new Reply<SubscriberEndpoint, String>() {
            @Override
            public void ok(SubscriberEndpoint s) {
                isValid.set(s.publisherEndpoint().equals(p));
            }

            @Override
            public void fail(ReplyException replyException) {
                logger.error("No Endpoint");
            }
        });
        return isValid.get();
    }

    private void endSharedPrivateSession(Envelope e, AtmosphereResource r) {
        utils.statusEvent(DB_PUBLISHER_SHARED_PRIVATE_END, e);
    }

    private void sharedPrivateSession(Envelope e, AtmosphereResource r) {
        utils.statusEvent(DB_PUBLISHER_SHARED_PRIVATE_START_POST, e);
    }

    private void getSubscriberProfile(String path, Envelope e) {
        utils.passthroughEvent(path, e);
    }

    private void updateSubscriberProfile(String path, Envelope e) {
        String[] paths = path.split("/");

        PublisherEndpoint p = utils.retrieve(e.getUuid());
        if (!utils.validate(p, e))
            return;

        // TODO: utils.validate Subscriber
        boolean subscriberOk = validateSubscriberState(paths[5], p);

        utils.statusEvent(DB_PUBLISHER_SUBSCRIBER_PROFILE_PUT, e, p);
    }

    private void getMotd(Envelope e, AtmosphereResource r) {
        utils.passthroughEvent(DB_GET_WORD_PASSTHROUGH, e);
    }

    private void onDemandEnd(Envelope e, AtmosphereResource r) {
        utils.statusEvent(DB_POST_PUBLISHER_ONDEMAND_END, e);
    }

    private void onDemandStart(Envelope e, AtmosphereResource r) {
        utils.statusEvent(DB_POST_PUBLISHER_ONDEMAND_START, e);
    }

    public void saveConfig(final Envelope e, AtmosphereResource r) {
        utils.statusEvent(DB_PUBLISHER_SAVE_CONFIG_PUT, e);
    }

    public void loadOrSaveShow(final Envelope e, AtmosphereResource r) {
        Message m = e.getMessage();
        if (!m.hasData()) {
            utils.passthroughEvent(DB_PUBLISHER_SETTINGS_SHOW_GET_PASSTHROUGHT, e);
        } else {
            utils.statusEvent(DB_PUBLISHER_SETTINGS_SHOW_SAVE, e);
        }

    }

    public void reportError(final Envelope e, AtmosphereResource r) {
        utils.statusEvent(DB_PUBLISHER_ERROR_REPORT, e);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void retrieveEndpoint(Object uuid, Reply reply) {
        if (String.class.isAssignableFrom(uuid.getClass())) {
            PublisherEndpoint p = endpoints.get(uuid.toString());
            if (p != null) {
                reply.ok(p);
                return;
            }
        }
        reply.fail(ReplyException.DEFAULT);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void errorStreamingSession(Envelope e) {
        try {
            PublisherResults result = mapper.readValue(e.getMessage().getData(), PublisherResults.class);
            PublisherEndpoint p = endpoints.get(result.getUuid());

            Message m = new Message();
            m.setPath(ERROR_STREAMING_SESSION);
            try {
                m.setData(mapper.writeValueAsString(new PublisherResults("ERROR")));
            } catch (JsonProcessingException e1) {
                //
            }
            error(e, p, m);
        } catch (IOException e1) {
            logger.warn("{}", e1);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void terminateStreamingSession(final Envelope e, AtmosphereResource r) {
        final PublisherEndpoint p = utils.retrieve(e.getUuid());
        if (!utils.validate(p, e))
            return;

        utils.statusEvent(DB_PUBLISHER_SHOW_END.replace("{showId}", String.valueOf(p.state().showId().showId())),
                e);
    }

    /**
     * {@inheritDoc}
     */
    public void createOrJoinStreamingSession(final Envelope e, AtmosphereResource r) {
        final PublisherEndpoint p = utils.retrieve(e.getUuid());

        if (!utils.validate(p, e))
            return;

        try {
            p.wowzaServerUUID(mapper.readValue(e.getMessage().getData(), WowzaUUID.class).getUuid());
        } catch (IOException e1) {
            logger.warn("{}", e1);
        }

        // TODO: Callback is not called at the moment as the dispatching to Wowza is asynchronous
        eventBus.message(WOWZA_CONNECT, new RetrieveMessage(p.uuid(), e.getMessage()), new Reply<String, String>() {
            @Override
            public void ok(String uuid) {
                // TODO: Proper Message
                Message m = new Message();
                response(e, p, m);
            }

            @Override
            public void fail(ReplyException replyException) {
                error(e, p, utils.constructMessage(WOWZA_CONNECT, "error", e.getMessage().getUUID()));
            }
        });
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void startStreamingSession(final Envelope e, AtmosphereResource r) {
        String uuid = e.getUuid();

        final PublisherEndpoint p = utils.retrieve(uuid);
        if (!utils.validate(p, e))
            return;

        eventBus.message(DB_PUBLISHER_SHOW_START, new RetrieveMessage(p.uuid(), e.getMessage()),
                new Reply<ShowId, String>() {
                    @Override
                    public void ok(ShowId showId) {
                        logger.trace("Publisher ready {}", p);
                        p.state().showId(showId);
                        response(e, p, utils.constructMessage(DB_PUBLISHER_SHOW_START,
                                utils.writeAsString(p.state().showId()), e.getMessage().getUUID()));
                    }

                    @Override
                    public void fail(ReplyException replyException) {
                        // TODO: Wrong error message
                        error(e, p, utils.constructMessage(DB_PUBLISHER_SHOW_START,
                                utils.writeAsString(new Error().error("Unauthorized")), e.getMessage().getUUID()));
                    }
                }).message(BROADCASTER_CREATE, p);
    }

    @Override
    public void response(Envelope e, PublisherEndpoint endpoint, Message m) {
        utils.response(e, endpoint, m);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void reactTo(String path, Object message, Reply reply) {
        switch (path) {
        case RETRIEVE_PUBLISHER:
            retrieveEndpoint(message, reply);
            break;
        case PUBLISHER_ABOUT_READY:
            resetEndpoint(message, reply);
            break;
        default:
            logger.error("Can't react to {}", path);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void resetEndpoint(Object publisherEndpointUuid, Reply reply) {
        try {
            PublisherEndpoint p = endpoints.get(publisherEndpointUuid.toString());
            AtmosphereResource r = p.resource();
            Message m = new Message();
            m.setPath(PUBLISHER_ABOUT_READY);
            m.setData(mapper.writeValueAsString(new PublisherResults("READY")));

            Envelope e = Envelope.newPublisherRequest(p.uuid(), m);
            r.write(mapper.writeValueAsString(e));
        } catch (JsonProcessingException e1) {
            logger.debug("Unable to write {} {}", publisherEndpointUuid);
            reply.fail(ReplyException.DEFAULT);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PublisherEndpoint createSession(final Envelope e, final AtmosphereResource r) {

        if (!e.getMessage().hasData()) {
            error(e, r, utils.errorMessage("error", e.getMessage().getUUID()));
            return null;
        }

        final String uuid = e.getUuid();
        PublisherEndpoint p = endpoints.get(uuid);
        if (p == null) {
            p = context.newInstance(PublisherEndpoint.class);
            p.uuid(uuid).resource(r);
            endpoints.put(uuid, p);
            final AtomicReference<PublisherEndpoint> publisher = new AtomicReference<>(p);
            String data = e.getMessage().getData();

            e.getMessage().setData(injectIp(r.getRequest().getRemoteAddr(), data));

            eventBus.message(DB_POST_PUBLISHER_SESSION_CREATE, new RetrieveMessage(p.uuid(), e.getMessage()),
                    new Reply<Status, String>() {
                        @Override
                        public void ok(final Status status) {
                            final PublisherEndpoint p = publisher.get();
                            logger.trace("{} succeed for {}", DB_POST_PUBLISHER_SESSION_CREATE, p);

                            eventBus.message(DB_ENDPOINT_STATE, new RetrieveMessage(p.uuid(), e.getMessage()),
                                    new Reply<EndpointState, String>() {
                                        @Override
                                        public void ok(EndpointState state) {
                                            p.state(state);
                                            eventBus.message(DB_PUBLISHER_AVAILABLE_ACTIONS_PASSTHROUGHT,
                                                    new RetrieveMessage(p.uuid(), e.getMessage()),
                                                    new Reply<String, String>() {
                                                        @Override
                                                        public void ok(String passthrough) {
                                                            utils.succesPassThrough(e, p,
                                                                    DB_PUBLISHER_AVAILABLE_ACTIONS_PASSTHROUGHT,
                                                                    passthrough);

                                                            eventBus.message(DB_PUBLISHER_LOAD_CONFIG_GET,
                                                                    new RetrieveMessage(p.uuid(), e.getMessage()),
                                                                    new Reply<String, String>() {
                                                                        @Override
                                                                        public void ok(String passthrough) {
                                                                            utils.succesPassThrough(e, p,
                                                                                    DB_PUBLISHER_LOAD_CONFIG,
                                                                                    passthrough);
                                                                            utils.passthroughEvent(
                                                                                    DB_PUBLISHER_LOAD_CONFIG_ERROR_PASSTHROUGHT,
                                                                                    e, p);
                                                                        }

                                                                        @Override
                                                                        public void fail(
                                                                                ReplyException replyException) {
                                                                            utils.failPassThrough(e, p,
                                                                                    replyException);
                                                                        }
                                                                    });
                                                        }

                                                        @Override
                                                        public void fail(ReplyException replyException) {
                                                            utils.failPassThrough(e, p, replyException);
                                                        }
                                                    });
                                        }

                                        @Override
                                        public void fail(ReplyException replyException) {
                                            error(e, publisher.get(),
                                                    utils.errorMessage("error", e.getMessage().getUUID()));
                                        }
                                    });

                        }

                        @Override
                        public void fail(ReplyException replyException) {
                            error(e, publisher.get(), utils.constructMessage(DB_POST_PUBLISHER_SESSION_CREATE,
                                    "error", e.getMessage().getUUID()));
                        }
                    });

        }

        r.addEventListener(new OnDisconnect() {
            @Override
            public void onDisconnect(AtmosphereResourceEvent event) {
                logger.debug("Publisher {} disconnected", uuid);
                endpoints.remove(uuid);
            }
        });
        return p;
    }

    private String injectIp(String remoteAddr, String data) {
        return data.replace("ip\":\"\"", "ip\":\"" + remoteAddr + "\"");
    }

    @Override
    public void error(Envelope e, PublisherEndpoint endpoint, Message m) {
        utils.error(e, endpoint, m);
    }

    @Override
    public void error(Envelope e, AtmosphereResource r, Message m) {
        utils.error(e, r, m);
    }

}