Java tutorial
/* * 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.action; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.atmosphere.cpr.AtmosphereResource; import org.jboss.netty.util.internal.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.zodiark.protocol.Envelope; import org.zodiark.protocol.Message; 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.publisher.PublisherEndpoint; import org.zodiark.service.publisher.PublisherResults; import org.zodiark.service.subscriber.SubscriberEndpoint; import org.zodiark.service.subscriber.SubscriberResults; import javax.inject.Inject; import java.io.IOException; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import static org.zodiark.protocol.Paths.ACTION_ACCEPT_REFUSED; import static org.zodiark.protocol.Paths.ACTION_COMPLETED; import static org.zodiark.protocol.Paths.ACTION_START_OK; import static org.zodiark.protocol.Paths.ACTION_TIMER; import static org.zodiark.protocol.Paths.MESSAGE_ACTION_VALIDATE; import static org.zodiark.protocol.Paths.DB_SUBSCRIBER_VALIDATE_STATE; import static org.zodiark.protocol.Paths.PUBLISHER_ACTION_ACCEPT; import static org.zodiark.protocol.Paths.RETRIEVE_PUBLISHER; import static org.zodiark.protocol.Paths.RETRIEVE_SUBSCRIBER; import static org.zodiark.protocol.Paths.SERVICE_ACTION; import static org.zodiark.protocol.Paths.STREAMING_COMPLETE_ACTION; import static org.zodiark.protocol.Paths.STREAMING_EXECUTE_ACTION; import static org.zodiark.protocol.Paths.ZODIARK_ACTION_ACCEPTED; @On(SERVICE_ACTION) public class ActionServiceImpl implements ActionService { private final Logger logger = LoggerFactory.getLogger(ActionServiceImpl.class); // TODO: Timer to clear action that were never accepted. private final ConcurrentHashMap<String, Reply> handshakingActions = new ConcurrentHashMap<>(); @Inject public EventBus eventBus; @Inject public ObjectMapper mapper; @Inject public ScheduledExecutorService timer; @Override public void reactTo(Envelope e, AtmosphereResource r, Reply reply) { switch (e.getMessage().getPath()) { case ZODIARK_ACTION_ACCEPTED: actionAccepted(e); break; case ACTION_ACCEPT_REFUSED: actionRefused(e); break; case ACTION_START_OK: actionStarted(e); break; } } /** * {@inheritDoc} */ public void actionStarted(Envelope e) { try { final PublisherResults results = mapper.readValue(e.getMessage().getData(), PublisherResults.class); eventBus.message(RETRIEVE_PUBLISHER, results.getUuid(), new Reply<PublisherEndpoint, String>() { @Override public void ok(final PublisherEndpoint p) { final AtomicInteger time = new AtomicInteger(p.action().time()); final AtmosphereResource publisher = p.resource(); final AtmosphereResource subscriber = p.action().subscriber().resource(); final Future<?> timerFuture = timer.scheduleAtFixedRate(new Runnable() { @Override public void run() { if (time.get() == 0) return; Message m = new Message(); m.setPath(ACTION_TIMER); try { m.setData(mapper.writeValueAsString(time.getAndDecrement())); Envelope e = Envelope.newPublisherMessage(p.uuid(), m); String w = mapper.writeValueAsString(e); publisher.write(w); e = Envelope.newSubscriberMessage(p.uuid(), m); w = mapper.writeValueAsString(e); subscriber.write(w); } catch (JsonProcessingException e1) { logger.error("", e1); } } }, 1, 1, TimeUnit.SECONDS); timer.schedule(new Runnable() { @Override public void run() { timerFuture.cancel(false); Message m = new Message(); m.setPath(ACTION_COMPLETED); try { m.setData(mapper.writeValueAsString(new PublisherResults("OK"))); Envelope e = Envelope.newPublisherMessage(p.uuid(), m); String w = mapper.writeValueAsString(e); publisher.write(w); m.setData(mapper.writeValueAsString(new SubscriberResults("OK"))); e = Envelope.newSubscriberMessage(p.uuid(), m); w = mapper.writeValueAsString(e); subscriber.write(w); } catch (JsonProcessingException e1) { logger.error("", e1); } finally { eventBus.message(STREAMING_COMPLETE_ACTION, p); } } }, p.action().time(), TimeUnit.SECONDS); } @Override public void fail(ReplyException replyException) { logger.error("Unable to retrieve Publishere for {}", results.getUuid()); } }); } catch (IOException e1) { logger.error("", e1); } } /** * {@inheritDoc} */ @Override public void reactTo(String path, Object message, Reply reply) { switch (path) { case MESSAGE_ACTION_VALIDATE: if (Action.class.isAssignableFrom(message.getClass())) { Action action = Action.class.cast(message); validateAction(action, reply); } break; } } /** * {@inheritDoc} */ @Override public void validateAction(final Action action, final Reply reply) { final SubscriberEndpoint s = action.subscriber(); final PublisherEndpoint p = s.publisherEndpoint(); if (p.actionInProgress()) { reply.fail(ReplyException.DEFAULT); return; } p.action(action); eventBus.message(DB_SUBSCRIBER_VALIDATE_STATE, s, new Reply<SubscriberEndpoint, String>() { @Override public void ok(SubscriberEndpoint s) { logger.trace("Action {} succeeded. Sending request to publisher {}", action, s); // No need to have a listener here since the response will be dispatched to EnvelopeDigester action.setPath(PUBLISHER_ACTION_ACCEPT); handshakingActions.put(s.uuid(), reply); requestForAction(p, action); } @Override public void fail(ReplyException replyException) { reply.fail(ReplyException.DEFAULT); } }); } /** * {@inheritDoc} */ @Override public void requestForAction(PublisherEndpoint p, Action action) { Message m = constructMessage(action); AtmosphereResource r = p.resource(); Envelope newResponse = Envelope.newPublisherRequest(p.uuid(), m); try { r.write(mapper.writeValueAsString(newResponse)); } catch (JsonProcessingException e1) { logger.error("", e1); } } Message constructMessage(Action action) { Message m = new Message(); m.setPath(action.getPath()); try { m.setData(mapper.writeValueAsString(action)); } catch (JsonProcessingException e1) { logger.error("", e1); } return m; } /** * {@inheritDoc} */ @Override public void actionAccepted(Envelope e) { try { final Action action = mapper.readValue(e.getMessage().getData(), Action.class); eventBus.message(RETRIEVE_SUBSCRIBER, action.getSubscriberUUID(), new Reply<SubscriberEndpoint, String>() { @Override public void ok(SubscriberEndpoint s) { action.subscriber(s); } @Override public void fail(ReplyException replyException) { logger.error("No Endpoint"); } }); Reply l = handshakingActions.remove(action.getSubscriberUUID()); if (l == null) { throw new IllegalStateException("Invalid state"); } // Send OK l.ok(action); eventBus.message(STREAMING_EXECUTE_ACTION, action, new Reply<Action, String>() { @Override public void ok(Action action) { } @Override public void fail(ReplyException replyException) { } }); } catch (IOException e1) { logger.error("", e1); } } /** * {@inheritDoc} */ @Override public void actionRefused(Envelope e) { try { Action action = mapper.readValue(e.getMessage().getData(), Action.class); Reply l = handshakingActions.remove(action.getSubscriberUUID()); if (l == null) { throw new IllegalStateException("Invalid state"); } l.fail(ReplyException.DEFAULT); } catch (IOException e1) { logger.error("", e1); } } }