/******************************************************************************* * Copyright © 2012-2015 eBay Software Foundation * This program is dual licensed under the MIT and Apache 2.0 licenses. * Please see LICENSE for more information. *******************************************************************************/ package com.ebay.jetstream.event.channel.file; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedResource; import com.ebay.jetstream.common.ShutDownable; import com.ebay.jetstream.config.ContextBeanChangedEvent; import com.ebay.jetstream.counter.LongCounter; import com.ebay.jetstream.counter.LongEWMACounter; import com.ebay.jetstream.event.EventException; import com.ebay.jetstream.event.JetstreamEvent; import com.ebay.jetstream.event.JetstreamReservedKeys; import com.ebay.jetstream.event.RetryEventCode; import com.ebay.jetstream.event.advice.Advice; import com.ebay.jetstream.event.channel.InboundChannel; import com.ebay.jetstream.event.support.AbstractEventSource; import com.ebay.jetstream.management.Management; import com.ebay.jetstream.messaging.MessageServiceTimer; import com.ebay.jetstream.notification.AlertListener; import com.ebay.jetstream.xmlser.XSerializable; /*** * specify this class in the subscriber's wiring file to get registered into Spring framework * @author gjin * */ /** * with this annotation will see http://127.0.0.1:1504/Event/Channel/InboundFileChannel * if the bean name is "InboundFileChannel" */ @ManagedResource(objectName = "Event/Channel", description = "Inbound File component") public class InboundFileChannel extends AbstractEventSource implements InboundChannel, BeanNameAware, InitializingBean, ApplicationListener, XSerializable, ShutDownable { Logger logger = LoggerFactory.getLogger("com.ebay.jetstream.event.channel.file.InboundFileChannel"); //private FileChannelAddress m_address; private InboundFileChannelHelper m_fcHelper; private Advice m_adviceListener; private final AtomicBoolean m_isPaused = new AtomicBoolean(false); private final LongCounter m_totalEventsReceived = new LongCounter(); private final LongCounter m_pauseCount = new LongCounter(); private final LongCounter m_resumeCount = new LongCounter(); private final LongCounter m_totalEventsSent = new LongCounter(); private final LongCounter m_totalEventsDropped = new LongCounter(); private final LongCounter m_eventSentToAdviceListener = new LongCounter(); private LongEWMACounter m_eventsReceivedPerSec; private LongEWMACounter m_eventsSentPerSec; private final AtomicBoolean m_shutdownStatus = new AtomicBoolean(false); // NOPMD private int m_numOfEventsPerSec; /* the trunkSize: number of events/per sec */ private PlayThread m_pt = new PlayThread(); private AlertListener m_AlertListener; //private AtomicBoolean m_restartable = new AtomicBoolean(false); public InboundFileChannel() { } public void afterPropertiesSet() throws Exception { } public void setAlertListener(AlertListener al) { if (al == null) { logger.error( "AlertListener is null unexpectedly"); return; } m_AlertListener = al; } /** * clean the memory * close the file */ public void close() throws EventException { logger.info( "Closing Inbound File Channel"); Management.removeBeanOrFolder(getBeanName(), this); m_eventsReceivedPerSec.destroy(); m_eventsSentPerSec.destroy(); /* close the file */ m_fcHelper.closeForRead(); } public void open() throws EventException { //LOGGER.info( "Opening Inbound File Channel"); logger.info( "Openning Inbound File Channel"); /* put my name Event/FileChannel to the page */ Management.addBean(getBeanName(), this); m_eventsReceivedPerSec = new LongEWMACounter(60, MessageServiceTimer.sInstance().getTimer()); m_eventsSentPerSec = new LongEWMACounter(60, MessageServiceTimer.sInstance().getTimer()); /* open the event file, be ready for play*/ try { m_fcHelper.openForRead(); } catch (EventException ee) { //LOGGER.error( "InboundFileChannel open() get e=" + ee); logger.error( "--InboundFileChannel open() get e=" + ee); /*** instead of forcing application to exit, we choose to post an error status at dashboard */ if (m_AlertListener != null) { m_AlertListener.sendAlert(getBeanName(), "InboundFileChannel open() get e=" + ee, AlertListener.AlertStrength.RED); } else { throw ee; } } } private void fireEvent(JetstreamEvent evt) { super.fireSendEvent(evt); } /* just for satisfying the interface */ public void flush() throws EventException { } @Override public int getPendingEvents() { if (m_fcHelper.isEOF()) { return 0; } else { return 1; /* we do not know how many left */ } } public FileChannelAddress getAddress() { return m_fcHelper.getAddress(); } public Advice getAdviceListener() { return m_adviceListener; } /** * * @return */ public long getEventSentToAdviceListener() { return m_eventSentToAdviceListener.get(); } /** * @return the messagesReceived */ public long getTotalEventsReceived() { return m_totalEventsReceived.get(); } /** * @return the pauseCount */ public long getTotalPauseCount() { return m_pauseCount.get(); } /** * @return the resumeCount */ public long getTotalResumeCount() { return m_resumeCount.get(); } public boolean getShutdownStatus() { return m_shutdownStatus.get(); } private void incrementAdviceListenerCount() { m_eventSentToAdviceListener.increment(); } private void incrementSentCount() { m_totalEventsSent.increment(); } @Override public long getEventsReceivedPerSec() { return m_eventsReceivedPerSec.get(); } @Override public long getEventsSentPerSec() { return m_eventsSentPerSec.get(); } @ManagedOperation public void startReplayWithEventFile() { Thread.State s = m_pt.getState(); /* if it is newly created or terminated, then start it */ if (s == Thread.State.NEW || s == Thread.State.TERMINATED) { m_pt.start(); } else { logger.warn( "the replay thread was started with state=" + s); } } @ManagedOperation public void restart() { Thread.State s = m_pt.getState(); /* if it is newly created or terminated, then start it */ if (s == Thread.State.TERMINATED) { /* open the event file */ m_fcHelper.openForRead(); /* restart play from the begin of the event file */ m_pt = new PlayThread(); m_pt.start(); } else { logger.warn( "the replay thread either never started or running: state=" + s); } } @Override @ManagedOperation public void pause() throws EventException { if (m_isPaused.get()) { logger.error( "InboundFileChannel " + getBeanName() + " could not be paused. It is already in paused state", "InboundFileChannel" + ".pause()"); return; } m_isPaused.set(true); logger.error( "Inbound File Channel paused."); } @Override @ManagedOperation public void resume() throws EventException { if (!m_isPaused.get()) { logger.error( "InboundFileChannel " + getBeanName() + " could not be resumed. It is already in resume state", "InboundFileChannel" + ".pause()"); return; } // if we are in paused state then first empty the pause Cache which could have received events // that were in the pipeline when we unsubscribed in response to a pause. if (m_isPaused.get() && !m_shutdownStatus.get()) { m_isPaused.set(false); if (m_resumeCount.addAndGet(1) < 0) { m_resumeCount.set(0); } /* resume by interrupt the thread from "sleep()" */ m_pt.interrupt(); } } @Override protected void processApplicationEvent(ApplicationEvent event) { if (event instanceof ContextBeanChangedEvent) { ContextBeanChangedEvent bcInfo = (ContextBeanChangedEvent) event; //Calculate changes if (bcInfo.isChangedBean(getAddress())) { logger.info( "Received new configuration for InboundMessageChannel - " + getBeanName()); try { close(); } catch (Throwable e) { //swallow the exception and log String errMsg = new String("Error closing InboundMessagingChannel while applying new config - "); errMsg += e.getMessage(); logger.error( errMsg); } setAddress((FileChannelAddress) bcInfo.getChangedBean()); try { open(); } catch (Throwable e) { // swallow the exception and log String errMsg = new String("Error opening InboundMessagingChannel while applying new config - "); errMsg += e.getMessage(); logger.error( errMsg); } } } } /** * @param messagesReceived * the messagesReceived to set */ @ManagedOperation public void resetEventsReceived() { m_totalEventsReceived.set(0); } /** * @param pauseCount * the pauseCount to set */ @ManagedOperation public void resetPauseCount() { m_pauseCount.set(0); } /** * @param resumeCount * the resumeCount to set */ @ManagedOperation public void resetResumeCount() { m_resumeCount.set(0); } /** * */ @ManagedOperation public void resetStats() { m_totalEventsReceived.reset(); m_pauseCount.reset(); m_resumeCount.reset(); m_totalEventsSent.reset(); m_totalEventsDropped.reset(); m_totalEventsReceived.reset(); m_eventsReceivedPerSec.reset(); } private void sendToAdviceListener(JetstreamEvent event, RetryEventCode code, String msg) { if (m_adviceListener != null) { m_adviceListener.retry(event, code, msg); incrementAdviceListenerCount(); } } /** * @param address */ public void setAddress(FileChannelAddress address) { //m_address = (FileChannelAddress) address; m_fcHelper = new InboundFileChannelHelper(address); } public void setAdviceListener(Advice adviceListener) { m_adviceListener = adviceListener; } @Override public void shutDown() { m_shutdownStatus.set(true); close(); } @Override public long getTotalEventsSent() { return m_totalEventsSent.get(); } @Override public long getTotalEventsDropped() { return m_totalEventsDropped.get(); } public int getNumOfEventPerSec() { return m_numOfEventsPerSec; } public void setNumOfEventPerSec(int rate) { m_numOfEventsPerSec = rate; } class PlayThread extends Thread { Map<String, Object> eventMap; public void run() { //System.out.println("play thread starts to run: eof=" + m_fcHelper.isEOF() + ", paused=" + m_isPaused.get()); //KEEPME while (!m_fcHelper.isEOF()) { while (m_isPaused.get()) { try { Thread.sleep(1000*10); } catch (InterruptedException ie) { System.out.println("##### innterrupted "); //KEEPME } } long startTimeMS = System.currentTimeMillis(); int count; //System.out.println("##### m_numOfEventPerSec=" + m_numOfEventsPerSec); //KEEPME for (count = 0; count < m_numOfEventsPerSec; count++) { //System.out.println("##### getNextEventMap()"); eventMap = m_fcHelper.getNextEventMap(); if (eventMap == null) { break; } JetstreamEvent event = extractEventFromMap(eventMap); if (event != null) { m_totalEventsReceived.increment(); m_eventsReceivedPerSec.increment(); fireSendEvent(event); } } long elapsedTimeInMS = System.currentTimeMillis() - startTimeMS; /* if less than one sec */ if (elapsedTimeInMS < 1000) { try { //System.out.println("##### sleep " + (1000 - elapsedTimeInMS) + " millisec"); //KEEPME TimeUnit.MILLISECONDS.sleep(1000 - elapsedTimeInMS); } catch (InterruptedException e) { // swallow } } } /* end of file. close it for restart */ m_fcHelper.closeForRead(); } private JetstreamEvent extractEventFromMap(Map<String, Object> eventMap) { JetstreamEvent event; if (eventMap == null) { return null; } String type = (String) eventMap.get(JetstreamReservedKeys.EventType.toString()); eventMap.remove(JetstreamReservedKeys.EventId.toString()); eventMap.remove(JetstreamReservedKeys.EventType.toString()); /* TODO: it appears we do not have any class implementing EventId * so we put null for the time being */ event = new JetstreamEvent(); if (type != null) { event.setEventType(type); } for (Map.Entry<String, Object> entry: eventMap.entrySet()) { event.put(entry.getKey(), entry.getValue()); } return event; } } }