/* * Copyright (c) 2016 Saugo360. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.tsdr.restconf.collector; import java.lang.invoke.MethodHandles; import java.util.LinkedList; import java.util.List; import java.util.Timer; import java.util.TimerTask; import org.opendaylight.yang.gen.v1.opendaylight.tsdr.rev150219.DataCategory; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.tsdr.collector.spi.rev150915.InsertTSDRLogRecordInputBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.tsdr.collector.spi.rev150915.TsdrCollectorSpiService; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.tsdr.collector.spi.rev150915.inserttsdrlogrecord.input.TSDRLogRecord; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.tsdr.collector.spi.rev150915.inserttsdrlogrecord.input.TSDRLogRecordBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class is responsible for interfacing with the TSDR collector SPI in order to persist the collected logs. * It maintains a cached queue of the logs, that is persisted every pre-set amount of time, and then emptied * * @author <a href="mailto:a.alhamali93@gmail.com">AbdulRahman AlHamali</a> * * Created: Dec 16th, 2016 * */ public class TSDRRestconfCollectorLogger extends TimerTask { /** * a reference to the collector SPI service. */ private TsdrCollectorSpiService tsdrCollectorSpiService; /** * The instance of this class (for singleton pattern). */ private static TSDRRestconfCollectorLogger INSTANCE = null; /** * The interval after which the data is persisted, specified in milli-seconds. */ private static final long PERSIST_CHECK_INTERVAL_IN_MILLISECONDS = 5000; /** * The queue in which the data is cached before persisting. */ private LinkedList<TSDRLogRecord> queue = new LinkedList<TSDRLogRecord>(); /** * A mutex used for locking the queue to prevent race conditions between multiple threads. */ private Object queueMutex = new Object(); /** * The index of the current record, useful to distinguish records are received in the same milli-second * it is incremented for each record received, and then zeroed again after the data is persisted. */ private int currentIndex = 0; /** * used to inform the run method that the module has shutdown, which will cancel the scheduled timer task. * This variable is used because it is best to cancel the timer from within the run method: * https://docs.oracle.com/javase/7/docs/api/java/util/TimerTask.html#cancel() */ private boolean hasShutDown = false; /** * the timer instance. */ private Timer timer; /** * the logger of the class. */ private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); /** * the constructor is private because we are following the singleton pattern. * it is called once an instance is created. It initialized the queue, and schedules the timer task * @param timer the timer instance */ private TSDRRestconfCollectorLogger(Timer timer) { queue = new LinkedList<TSDRLogRecord>(); this.timer = timer; this.timer.schedule(this, PERSIST_CHECK_INTERVAL_IN_MILLISECONDS, PERSIST_CHECK_INTERVAL_IN_MILLISECONDS); } /** * retrieves the instance of the class, and creates a new one if no instance exists. * @return the instance of the singleton */ public static TSDRRestconfCollectorLogger getInstance() { return getInstance(new Timer()); } /** * retrieves the instance of the class, and creates a new one if no instance exists. * Only call this version of the function when testing. It is useful if you want to mock the timer * @param timer the timer instance * @return the instance of the singleton */ public static TSDRRestconfCollectorLogger getInstance(Timer timer) { if (INSTANCE == null) { INSTANCE = new TSDRRestconfCollectorLogger(timer); } return INSTANCE; } /** * only call this method in testing to do mocking. * @param instance the instance of the class */ public static void setInstance(TSDRRestconfCollectorLogger instance) { INSTANCE = instance; } /** * sets the collector SPI service. * @param tsdrCollectorSpiService the collector SPI service instance */ public void setTsdrCollectorSpiService(TsdrCollectorSpiService tsdrCollectorSpiService) { this.tsdrCollectorSpiService = tsdrCollectorSpiService; } /** * returns the collector SPI service. * @return the collector SPI service instance */ public TsdrCollectorSpiService getTsdrCollectorSpiService() { return this.tsdrCollectorSpiService; } /** * builds a log from the provided parameters, and inserts it into the cache queue. * @param method the http method * @param pathInfo the relative url of the request * @param remoteAddress the address from which the request generated * @param body the content of the request */ public void insertLog(String method, String pathInfo, String remoteAddress, String body) { TSDRLogRecordBuilder recordBuilder = new TSDRLogRecordBuilder(); recordBuilder.setNodeID(pathInfo); recordBuilder.setTimeStamp(System.currentTimeMillis()); recordBuilder.setRecordFullText("METHOD=" + method + ",REMOTE_ADDRESS=" + remoteAddress + ",BODY=" + body); recordBuilder.setTSDRDataCategory(DataCategory.RESTCONF); synchronized (queueMutex) { recordBuilder.setIndex(currentIndex); currentIndex++; queue.add(recordBuilder.build()); } } /** * persists the cache queue. * @param queue the queue to persist */ private void store(List<TSDRLogRecord> queue) { InsertTSDRLogRecordInputBuilder input = new InsertTSDRLogRecordInputBuilder(); input.setTSDRLogRecord(queue); input.setCollectorCodeName("TSDRRestconfCollector"); this.tsdrCollectorSpiService.insertTSDRLogRecord(input.build()); } /** * called when the module is about to shut down, it sets hasShutDown to true, which will inform the run method, * which will consequently cancel the timer task. */ public void shutDown() { this.hasShutDown = true; } /** * called automatically by the timer each time a period of PERSIST_CHECK_INTERVAL_IN_MILLISECONDS has passed. * It copies the cached queue into another queue, then clears the queue and the currentIndex. * Then, it checks if that new queue has data, if it has data, it gets persisted. * Finally, it checks if the module has shut down. If it has, it cancels the timer task. * The reason behind using a temporary queue is to free the original queue as quickly as possible, so that if * insertLog is waiting, it could resume its execution as soon as possible */ @Override public void run() { LinkedList<TSDRLogRecord> tempQueue = new LinkedList<TSDRLogRecord>(); synchronized (queueMutex) { tempQueue.addAll(queue); queue.clear(); currentIndex = 0; } if (tempQueue.size() != 0) { store(tempQueue); } // Calling the cancel function from inside run guarantees that the timer // will definitely stop if (this.hasShutDown) { this.cancel(); this.timer.cancel(); this.timer.purge(); } } }