
kieker.analysis.plugin.AbstractPlugin Maven / Gradle / Ivy
/***************************************************************************
* Copyright 2022 Kieker Project (http://kieker-monitoring.net)
*
* 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 kieker.analysis.plugin;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import kieker.analysis.AnalysisController;
import kieker.analysis.IProjectContext;
import kieker.analysis.analysisComponent.AbstractAnalysisComponent;
import kieker.analysis.exception.AnalysisConfigurationException;
import kieker.analysis.plugin.annotation.InputPort;
import kieker.analysis.plugin.annotation.OutputPort;
import kieker.analysis.plugin.annotation.Plugin;
import kieker.analysis.plugin.annotation.Property;
import kieker.analysis.plugin.annotation.RepositoryPort;
import kieker.analysis.plugin.reader.IReaderPlugin;
import kieker.analysis.repository.AbstractRepository;
import kieker.common.configuration.Configuration;
import kieker.common.record.misc.KiekerMetadataRecord;
/**
* Do not inherit directly from this class! Instead inherit from the class {@link kieker.analysis.plugin.filter.AbstractFilterPlugin} or
* {@link kieker.analysis.plugin.reader.AbstractReaderPlugin}.
*
* @author Nils Christian Ehmke, Jan Waller
*
* @since 1.5
* @deprecated since 1.15.1 old plugin api
*/
@Deprecated
@Plugin
public abstract class AbstractPlugin extends AbstractAnalysisComponent implements IPlugin {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractPlugin.class.getCanonicalName());
private final ConcurrentHashMap> registeredMethods;
private final ConcurrentHashMap registeredRepositories;
private final Map[]> outputPortTypes; // NOCS
private final Map repositoryPorts;
private final Map outputPorts;
private final Map inputPorts;
// Shutdown mechanism
private final List incomingPlugins;
private final List outgoingPlugins;
private volatile STATE state = STATE.READY;
/**
* Each Plugin requires a constructor with a Configuration object and an IProjectContext.
*
* @param configuration
* The configuration for this component.
* @param projectContext
* The project context for this component. The component will be registered.
*/
public AbstractPlugin(final Configuration configuration, final IProjectContext projectContext) {
// Registering will happen in the subclass
super(configuration, projectContext);
// Get all repository and output ports.
this.repositoryPorts = new ConcurrentHashMap<>();
this.outputPorts = new ConcurrentHashMap<>();
this.outputPortTypes = new ConcurrentHashMap<>(); // NOCS
final Plugin annotation = this.getClass().getAnnotation(Plugin.class);
for (final RepositoryPort repoPort : annotation.repositoryPorts()) {
if (this.repositoryPorts.put(repoPort.name(), repoPort) != null) {
this.logger.error("Two RepositoryPorts use the same name: {}", repoPort.name());
}
}
// ignore possible outputPorts for IWebVisualizationFilters
for (final OutputPort outputPort : annotation.outputPorts()) {
if (this.outputPorts.put(outputPort.name(), outputPort) != null) {
this.logger.error("Two OutputPorts use the same name: {}", outputPort.name());
}
Class>[] outTypes = outputPort.eventTypes();
if (outTypes.length == 0) {
outTypes = new Class>[] { Object.class };
}
this.outputPortTypes.put(outputPort, outTypes);
}
// Get all input ports.
this.inputPorts = new ConcurrentHashMap<>();
// ignore possible inputPorts for IReaderPlugins
if (!(this instanceof IReaderPlugin)) {
for (final Method method : this.getClass().getMethods()) {
final InputPort inputPort = method.getAnnotation(InputPort.class);
if ((inputPort != null) && (this.inputPorts.put(inputPort.name(), inputPort) != null)) {
this.logger.error("Two InputPorts use the same name: {}", inputPort.name());
}
if (inputPort != null) {
final Class>[] parameters = method.getParameterTypes();
if (parameters.length != 1) {
this.logger.error("The input port {} has to provide exactly one parameter of the correct type.", inputPort.name());
} else {
Class>[] eventTypes = inputPort.eventTypes();
if (eventTypes.length == 0) { // NOPMD (nested if)
eventTypes = new Class>[] { Object.class };
}
for (final Class> event : eventTypes) {
if (!parameters[0].isAssignableFrom(event)) { // NOPMD (nested if)
this.logger.error("The event type {} of the input port {} is not accepted by the parameter of type ", event.getName(),
inputPort.name(), parameters[0].getName());
}
}
}
}
}
} else {
// But inform the user about these invalid ports
for (final Method method : this.getClass().getMethods()) {
final InputPort inputPort = method.getAnnotation(InputPort.class);
if (inputPort != null) {
this.logger.warn("Invalid port for reader detected. Port is ignored: {}", inputPort.name());
}
}
}
this.registeredRepositories = new ConcurrentHashMap<>(this.repositoryPorts.size());
// Now create a linked queue for every output port of the class, to store the registered methods.
this.registeredMethods = new ConcurrentHashMap<>();
for (final OutputPort outputPort : annotation.outputPorts()) {
this.registeredMethods.put(outputPort.name(), new ArrayList<>(1));
}
// and a List for every incoming and outgoing plugin
this.incomingPlugins = new ArrayList<>(1); // usually only one incoming
this.outgoingPlugins = new ArrayList<>(1); // usually only one outgoing
}
/**
* Delivers the given data to all registered input ports of the given output port.
*
* @param outputPortName
* The output port to be used to send the given data.
* @param data
* The data to be send; must not be null.
* @return true if and only if the given output port does exist and if the data is not null and if it suits the port's event types.
*/
protected final boolean deliver(final String outputPortName, final Object data) {
if (((this.state != STATE.RUNNING) && (this.state != STATE.TERMINATING)) || (data == null)) {
return false;
}
// discard this kind of record when encountered ...
if (data instanceof KiekerMetadataRecord) {
((AnalysisController) this.projectContext).handleKiekerMetadataRecord((KiekerMetadataRecord) data);
return true;
}
// First step: Get the output port.
final OutputPort outputPort = this.outputPorts.get(outputPortName);
if (outputPort == null) {
return false;
}
// Second step: Check whether the data fits the event types.
final Class>[] outTypes = this.outputPortTypes.get(outputPort);
boolean outTypeMatch = false;
for (final Class> eventType : outTypes) {
if (eventType.isInstance(data)) {
outTypeMatch = true;
break; // for
}
}
if (!outTypeMatch) {
return false;
}
// Third step: Send everything to the registered ports.
final List registeredPortMethods = this.registeredMethods.get(outputPortName);
for (final PluginInputPortReference pluginInputPortReference : registeredPortMethods) {
// Check whether the data fits the event types.
Class>[] eventTypes = pluginInputPortReference.getEventTypes();
if (eventTypes.length == 0) {
eventTypes = new Class>[] { Object.class };
}
for (final Class> eventType : eventTypes) {
if (eventType.isAssignableFrom(data.getClass())) { // data instanceof eventType
try {
pluginInputPortReference.getInputPortMethod().invoke(pluginInputPortReference.getPlugin(), data);
} catch (final InvocationTargetException e) {
// This is an exception wrapped by invoke
final Throwable cause = e.getCause();
if (cause instanceof Error) {
// This is a severe case and there is little chance to terminate appropriately
throw (Error) cause;
} else {
this.logger.warn("Caught exception when sending data from {}: OutputPort {} to {}'s InputPort {}", this.getClass().getName(),
outputPort.name(), pluginInputPortReference.getPlugin().getClass().getName(),
pluginInputPortReference.getInputPortMethod().getName(), cause);
}
} catch (final Exception e) { // NOPMD NOCS (catch multiple)
// This is an exception wrapped by invoke
this.logger.error("Caught exception when invoking {}'s InputPort {}", pluginInputPortReference.getPlugin().getClass().getName(),
pluginInputPortReference.getInputPortMethod().getName(), e);
}
break; // for
}
}
}
return true;
}
/**
* {@inheritDoc}
*/
@Override
public final void connect(final String reponame, final AbstractRepository repository) throws AnalysisConfigurationException {
if (this.state != STATE.READY) {
throw new AnalysisConfigurationException("Plugin: " + this.getClass().getName() + " final not in " + STATE.READY + " this.state, but final in state "
+ this.state + ".");
}
final RepositoryPort port = this.repositoryPorts.get(reponame);
if (port == null) {
throw new AnalysisConfigurationException("Failed to connect plugin '" + this.getName() + "' (" + this.getPluginName() + ") to repository '"
+ repository.getName() + "' (" + repository.getRepositoryName() + "). Unknown repository port: " + reponame);
}
final Class extends AbstractRepository> repositoryType = port.repositoryType();
if (!repositoryType.isAssignableFrom(repository.getClass())) {
throw new AnalysisConfigurationException("Failed to connect plugin '" + this.getName() + "' (" + this.getPluginName() + ") to repository '"
+ repository.getName() + "' (" + repository.getRepositoryName() + "). Expected RepositoryType: " + repositoryType.getName() + " Found: "
+ repository.getClass().getName());
}
synchronized (this) {
if (this.registeredRepositories.containsKey(reponame)) {
throw new AnalysisConfigurationException("Failed to connect plugin '" + this.getName() + "' (" + this.getPluginName() + ") to repository '"
+ repository.getName() + "' (" + repository.getRepositoryName() + "). RepositoryPort already connected: " + reponame);
}
this.registeredRepositories.put(reponame, repository);
}
}
/**
* This method connects two plugins. DO NOT USE THIS METHOD! Use AnalysisController.connect
instead!
*
* @param src
* The source plugin.
* @param outputPortName
* The output port of the source plugin.
* @param dst
* The destination plugin.
* @param inputPortName
* The input port of the destination port.
* @throws AnalysisConfigurationException
* if any given plugin is invalid, any output or input port doesn't exist or if they are incompatible.
* Furthermore the destination plugin must not be a reader.
*/
public static final void connect(final AbstractPlugin src, final String outputPortName, final AbstractPlugin dst, final String inputPortName)
throws AnalysisConfigurationException {
if (!AbstractPlugin.isConnectionAllowed(src, outputPortName, dst, inputPortName)) {
throw new AnalysisConfigurationException("Failed to connect plugin '" + src.getName() + "' (" + src.getPluginName() + ") to plugin '"
+ dst.getName() + "' (" + dst.getPluginName() + ").");
}
// Connect the ports.
for (final Method m : dst.getClass().getMethods()) {
final InputPort ip = m.getAnnotation(InputPort.class);
if ((ip != null) && (m.getParameterTypes().length == 1) && ip.name().equals(inputPortName)) {
src.outputPorts.get(outputPortName).eventTypes();
java.security.AccessController.doPrivileged(new PrivilegedAction
© 2015 - 2025 Weber Informatics LLC | Privacy Policy