/* This file is part of VoltDB. * Copyright (C) 2008-2017 VoltDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * 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 VoltDB. If not, see <http://www.gnu.org/licenses/>. */ package org.voltdb.importer; import java.net.URI; import java.util.function.Function; import org.voltcore.logging.Level; import org.voltcore.logging.VoltLogger; import org.voltdb.InternalConnectionContext; import org.voltdb.client.ProcedureCallback; /** * Abstract class that must be extended to create custom importers in VoltDB server. * The sequence of calls when the importer is started up is: * <ul> * <li> Find the importer factory in the OSGi bundle as a service </li> * <li> Validate and setup configuration using factory.createImporterConfigurations </li> * <li> Create an importer instance using factory.createImporter for every resource that must be * run on this server </li> * <li> Start the importers by calling accept. Each of this will be called in its own thread. * Importers should do their work in the implementation of accept. * Importer implementations should do their work in a <code>while (shouldRun())</code> loop, * which will make sure that that the importer will stop its work when the framework calls stop. </li> * </ul> * * <p>The framework stops the importer by calling <code>stopImporter</code>, which will stop the executor service and * call <code>stop</code> on the importer instance to close resources used by the specific importer. * <code>stop(resourceID)</code> will also be called on the importer instances when the resources are redistributed * because of addition/deletion of nodes to the cluster. */ public abstract class AbstractImporter implements InternalConnectionContext { private static final int LOG_SUPPRESSION_INTERVAL_SECONDS = 60; private final VoltLogger m_logger; private ImporterServerAdapter m_importServerAdapter; private volatile boolean m_stopping; private final Function<Integer, Boolean> m_backPressurePredicate = (x) -> shouldRun(); protected AbstractImporter() { m_logger = new VoltLogger(getName()); } /** * Passes in the server adapter that may be used by this importer to access the server, * like calling a procedure. * * @param adapter the server adapter that may be used by this to access the server. */ public final void setImportServerAdapter(ImporterServerAdapter adapter) { m_importServerAdapter = adapter; } /** * This method indicates if the importer has been stopped or if it should continue running. * This should be checked by importer implementations regularly to determine if the importer * should continue its execution. * * @return returns true if the importer execution should continue; false otherwise */ protected final boolean shouldRun() { return !m_stopping; } /** * This should be used importer implementations to execute a stored procedure. * * @param invocation Invocation object with procedure name and parameter information * @return returns true if the procedure execution went through successfully; false otherwise */ protected final boolean callProcedure(Invocation invocation) { return callProcedure(invocation, null); } /** * This should be used importer implementations to execute a stored procedure. * * @param invocation Invocation object with procedure name and parameter information * @param callback the callback that will receive procedure invocation status * @return returns true if the procedure execution went through successfully; false otherwise */ protected final boolean callProcedure(Invocation invocation, ProcedureCallback callback) { try { boolean result = m_importServerAdapter.callProcedure(this, m_backPressurePredicate, callback, invocation.getProcedure(), invocation.getParams()); reportStat(result, invocation.getProcedure()); return result; } catch (Exception ex) { rateLimitedLog(Level.ERROR, ex, "%s: Error trying to import", getName()); reportFailureStat(invocation.getProcedure()); return false; } } /** * Called to stop the importer from processing more data. */ public void stopImporter() { m_stopping = true; stop(); } private void reportStat(boolean result, String procName) { if (result) { m_importServerAdapter.reportQueued(getName(), procName); } else { m_importServerAdapter.reportFailure(getName(), procName, false); } } private void reportFailureStat(String procName) { m_importServerAdapter.reportFailure(getName(), procName, false); } /** * This rate limited log must be used by the importers to log messages that may * happen frequently and must be rate limited. * * @param level the log level * @param cause cause exception, if there is one * @param format error message format * @param args arguments to format the error message */ protected void rateLimitedLog(Level level, Throwable cause, String format, Object... args) { m_logger.rateLimitedLog(LOG_SUPPRESSION_INTERVAL_SECONDS, level, cause, format, args); } protected boolean isDebugEnabled() { return m_logger.isDebugEnabled(); } protected boolean isInfoEnabled() { return m_logger.isInfoEnabled(); } protected boolean isTraceEnabled() { return m_logger.isTraceEnabled(); } /** * Log a DEBUG level log message. * * @param message * @param t */ protected void debug(Throwable t, String msgFormat, Object... args) { m_logger.debug(String.format(msgFormat, args), t); } /** * Log a ERROR level log message. * * @param message * @param t */ protected void error(Throwable t, String msgFormat, Object... args) { m_logger.error(String.format(msgFormat, args), t); } /** * Log a INFO level log message. * * @param message * @param t */ protected void info(Throwable t, String msgFormat, Object... args) { m_logger.info(String.format(msgFormat, args), t); } /** * Log a TRACE level log message. * * @param message * @param t */ protected void trace(Throwable t, String msgFormat, Object... args) { m_logger.trace(String.format(msgFormat, args), t); } /** * Log a WARN level log message. * * @param message * @param t */ protected void warn(Throwable t, String msgFormat, Object... args) { m_logger.warn(String.format(msgFormat, args), t); } /** * Returns the resource id for which this importer was started. There will be unique * resource id per importer for each importer type. * * @return the unique resource id that is used by this importer */ public abstract URI getResourceID(); public String getTaskThreadName() { return null; } /** * Implementation of this should contain the main importer work. This is typically * called in its own thread so that each importer can do its work in parallel in its * own thread. This implementation should check <code>shouldRun()</code> to check if * importer work should be stopped or continued. */ protected abstract void accept(); /** * This is called by the importer framework to stop the importer. Any importer * specific resources should be closed and released here. */ protected abstract void stop(); }