package com.tesora.dve.smqplugin; /* * #%L * Tesora Inc. * Database Virtualization Engine * %% * Copyright (C) 2011 - 2014 Tesora Inc. * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ import java.util.Properties; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import com.tesora.dve.concurrent.ChainedNotifier; import com.tesora.dve.worker.agent.PluginProvider; import org.apache.log4j.Logger; import com.tesora.dve.common.RemoteException; import com.tesora.dve.comms.client.messages.ResponseMessage; import com.tesora.dve.exceptions.PEException; import com.tesora.dve.worker.agent.Envelope; import com.tesora.dve.worker.agent.Agent; import com.tesora.dve.worker.agent.AgentPlugin; public class SimpleMQPlugin implements AgentPlugin { public static final PluginProvider<SimpleMQPlugin> PROVIDER = new SimpleMQProvider(); static Logger logger = Logger.getLogger(Agent.class); static final boolean loggerDebug = logger.isDebugEnabled(); static class SMQEnvelope implements Envelope { static AtomicLong nextSequence = new AtomicLong(0); Object payload; String fromAddress; String toAddress; long exceptionFrame; long sequence; ChainedNotifier<ResponseMessage> response = new ChainedNotifier<>(); public SMQEnvelope(Object payload) { this.payload = payload; this.sequence = nextSequence.incrementAndGet(); } public SMQEnvelope(SMQEnvelope requestEnv, Object payload) { this.payload = payload; this.sequence = requestEnv.sequence; } public long getExceptionFrame() { return exceptionFrame; } public void setExceptionFrame(long sequence) { this.exceptionFrame = sequence; } public String getFromAddress() { return fromAddress; } public String getToAddress() { return toAddress; } @Override public Envelope from(String fromAddr) { fromAddress = fromAddr; return this; } @Override public Envelope to(String toAddr) { toAddress = toAddr; return this; } @Override public Object getPayload() { return payload; } @Override public String toString() { return display(new StringBuilder()).toString(); } public StringBuilder display(StringBuilder sb) { return sb.append("SMQEnvelope{").append("To:").append(getToAddress()) .append(", From: ").append(getFromAddress()) .append(", ").append((getPayload() == null)?"<empty payload>":getPayload().toString()) .append(", Seq: ").append(sequence) .append("}"); } @Override public String getReplyAddress() { return getFromAddress(); } protected void deliverResponse(ResponseMessage msg){ response.success(msg); } protected ResponseMessage sync(boolean canInterrupt) throws Exception { return response.sync(canInterrupt); } } ArrayBlockingQueue<SMQEnvelope> receiveQueue = new ArrayBlockingQueue<SimpleMQPlugin.SMQEnvelope>(100000); ArrayBlockingQueue<SMQEnvelope> replyQueue = new ArrayBlockingQueue<SimpleMQPlugin.SMQEnvelope>(2500); static ConcurrentHashMap<String, ArrayBlockingQueue<SMQEnvelope>> agentMap = new ConcurrentHashMap<String, ArrayBlockingQueue<SMQEnvelope>>(); private Agent theAgent; private Thread listenerThread; boolean markedForTermination = false; public SimpleMQPlugin(final Agent agent) throws PEException { this.theAgent = agent; agentMap.put(getAddress(), receiveQueue); agentMap.put(getReplyAddress(), replyQueue); listenerThread = new Thread(agent.getName()) { @Override public void run() { try { while (!markedForTermination) { try { // agent.onMessage(receiveQueue.take()); Envelope e = receiveQueue.poll(5, TimeUnit.SECONDS); if (e == null) agent.onTimeout(); else agent.onMessage(e); } catch (PEException e) { if (e.hasCause(InterruptedException.class)) markForTermination(); else throw new RuntimeException("Unhandled exception received by Agent", e); } catch (InterruptedException e) { markForTermination(); } } } finally { receiveQueue.clear(); replyQueue.clear(); theAgent = null; listenerThread = null; } } }; listenerThread.start(); } public static SimpleMQPlugin newInstance(Agent theAgent) throws PEException { return new SimpleMQPlugin(theAgent); } static public void startServices(Properties props) throws Exception { } static public void stopServices() throws Exception { } static public void dispatch(String toAddress, Object message) throws PEException { if (false == toAddress.isEmpty()) { SMQEnvelope env = (SMQEnvelope) new SMQEnvelope(message).to(toAddress); try { if (loggerDebug) logger.debug(env.display(new StringBuilder(SimpleMQPlugin.class.getSimpleName()).append(" dispatched "))); BlockingQueue<SMQEnvelope> destQueue = agentMap.get(env.getToAddress()); if (destQueue != null) { if (false == destQueue.offer(env,5,TimeUnit.MINUTES)) throw new PEException("Send exceeded timeout: " + env); } else { if (logger.isDebugEnabled()) logger.debug(SimpleMQPlugin.class.getSimpleName() + ".dispatch() cannot find destination \"" + env.getToAddress() + "\""); } } catch (InterruptedException e1) { throw new PEException("send operation interrupted", e1); } } } @Override public void send(Envelope e) throws PEException { SMQEnvelope env = (SMQEnvelope) e; if (!env.getToAddress().isEmpty()) { try { if (loggerDebug) logger.debug(env.display(new StringBuilder(theAgent.getName()).append(" sent "))); env.setExceptionFrame(theAgent.getExceptionFrame()); BlockingQueue<SMQEnvelope> destQueue = agentMap.get(env.getToAddress()); if (destQueue != null) { if (false == destQueue.offer(env,5,TimeUnit.MINUTES)) throw new PEException("Send exceeded timeout: " + env); } else { // if (false == markedForTermination) { // throw new PEException(getClass().getSimpleName() + ".send() cannot find destination \"" + env.getToAddress() + "\""); // } if (logger.isDebugEnabled()) logger.debug(getClass().getSimpleName() + ".send() cannot find destination \"" + env.getToAddress() + "\""); } } catch (InterruptedException e1) { markForTermination(); throw new PEException("send operation interrupted", e1); } } } private void markForTermination() { markedForTermination = true; } @Override public ResponseMessage sendAndReceive(Envelope msg) throws PEException { SMQEnvelope env = (SMQEnvelope) msg; env.from(getReplyAddress()); send(env); try { return env.sync(true); } catch (InterruptedException ie){ markForTermination(); throw new PEException("receive operation interrupted", ie); }catch (PEException e) { throw e; } catch (Exception e){ if (e.getCause() instanceof PEException) throw (PEException)e.getCause(); else throw new PEException(e); } } @Override public String getAddress() throws PEException { return theAgent.getName(); } @Override public String getReplyAddress() throws PEException { return theAgent.getName()+"-Reply"; } @Override public void close() throws PEException { if (listenerThread != null) { agentMap.remove(getAddress()); agentMap.remove(getReplyAddress()); // Between the call for // markForTermination and the interrupt call the listenerThread can exit and set itself to null // on the way out Thread t = listenerThread; markForTermination(); if (t != null) t.interrupt(); } } @Override public void returnResponse(Envelope requestEnvelope, ResponseMessage resp) throws PEException { SMQEnvelope env = (SMQEnvelope) requestEnvelope; env.deliverResponse(resp); } @Override public Envelope newEnvelope(Object o) { return new SMQEnvelope(o); } private static final class SimpleMQProvider implements PluginProvider<SimpleMQPlugin> { @Override public SimpleMQPlugin newInstance(Agent theAgent) throws PEException { return SimpleMQPlugin.newInstance(theAgent); } @Override public void startServices(Properties props) throws Exception { SimpleMQPlugin.startServices(props); } @Override public void stopServices() throws Exception { SimpleMQPlugin.stopServices(); } @Override public void dispatch(String toAddress, Object message) throws PEException { SimpleMQPlugin.dispatch(toAddress,message); } } }