package rocks.inspectit.server.instrumentation.config.job;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import rocks.inspectit.server.ci.event.ClassInstrumentationChangedEvent;
import rocks.inspectit.server.instrumentation.classcache.ClassCache;
import rocks.inspectit.server.instrumentation.config.AgentCacheEntry;
import rocks.inspectit.server.instrumentation.config.ClassCacheSearchNarrower;
import rocks.inspectit.server.instrumentation.config.ConfigurationHolder;
import rocks.inspectit.server.instrumentation.config.ConfigurationResolver;
import rocks.inspectit.server.instrumentation.config.applier.IInstrumentationApplier;
import rocks.inspectit.shared.all.instrumentation.classcache.ImmutableClassType;
import rocks.inspectit.shared.all.instrumentation.classcache.ImmutableType;
import rocks.inspectit.shared.all.instrumentation.config.impl.AgentConfig;
import rocks.inspectit.shared.all.instrumentation.config.impl.InstrumentationDefinition;
import rocks.inspectit.shared.all.spring.logger.Log;
import rocks.inspectit.shared.cs.ci.Environment;
import rocks.inspectit.shared.cs.ci.assignment.AbstractClassSensorAssignment;
/**
* Abstract class for all configuration change jobs. This class knows how to add or remove
* instrumentation points on the given class cache, environment and agent configuration. Note that
* {@link #environment}, {@link #classCache} and {@link #agentConfiguration} must be set using
* setters before running the {@link #run()} method.
*
* @author Ivan Senic
* @author Marius Oehler
*
*/
public abstract class AbstractConfigurationChangeJob implements Runnable {
/**
* Log for this class.
*/
@Log
Logger log;
/**
* {@link ClassCacheSearchNarrower} for help in searching for class types.
*/
@Autowired
private ClassCacheSearchNarrower classCacheSearchNarrower;
/**
* ConfigurationResolver needed for resolving the {@link IInstrumentationApplier}s.
*/
@Autowired
private ConfigurationResolver configurationResolver;
/**
* Spring {@link ApplicationEventPublisher} for publishing the events.
*/
@Autowired
private ApplicationEventPublisher eventPublisher;
/**
* {@link AgentCacheEntry} containing all necessary information.
*/
private AgentCacheEntry agentCacheEntry;
/**
* Concrete implementation of the job.
*
* @return {@link Collection} of {@link ImmutableType} which have been modified, added or
* removed in this job.
*/
protected abstract Collection<ImmutableType> execute();
/**
* {@inheritDoc}
*/
@Override
public void run() {
Collection<ImmutableType> changedClassTypes = execute();
createInstrumentationChangedEvent(changedClassTypes);
}
/**
* Creates and publishes an {@link ClassInstrumentationChangedEvent} notifying listeners that
* the instrumentation of certain classes have been changed.
*
* @param changedTypes
* {@link ImmutableType} which instrumentation has been changed
*/
private void createInstrumentationChangedEvent(Collection<ImmutableType> changedTypes) {
if (CollectionUtils.isNotEmpty(changedTypes)) {
if (log.isInfoEnabled()) {
log.info("Updated instrumentation definition of {} class(es) for the agent [ID: {}]", changedTypes.size(), getAgentId());
}
// existing InstrumentationDefinitions
List<InstrumentationDefinition> instrumentationDefinitions = new ArrayList<>(getClassCache().getInstrumentationService().getInstrumentationResults(changedTypes));
// add empty InstrumentationDefinitions for changed classes without any
// new InstrumentationDefinition
for (ImmutableType type : changedTypes) {
if (type.isClass() && !type.castToClass().hasInstrumentationPoints()) {
if (log.isDebugEnabled()) {
log.debug("|-[X]{}", type.getFQN());
}
InstrumentationDefinition emptyDefinition = new InstrumentationDefinition(type.getFQN());
instrumentationDefinitions.add(emptyDefinition);
} else {
if (log.isDebugEnabled()) {
log.debug("|-[ ]{}", type.getFQN());
}
}
}
ClassInstrumentationChangedEvent event = new ClassInstrumentationChangedEvent(this, getAgentId(), instrumentationDefinitions);
eventPublisher.publishEvent(event);
}
}
/**
* Process the removed assignments. All instrumentation points affected by the any of these
* assignments are first completely removed. All classes that have any point removed will be
* re-analyzed against complete configuration in order to reset the possible points coming not
* from removed assignments.
*
* @param classSensorAssignments
* Collection of removed {@link AbstractClassSensorAssignment}s.
* @return Returns a {@link Collection} of {@link ImmutableClassType} which have been removed.
*/
protected Collection<ImmutableClassType> processRemovedAssignments(Collection<? extends AbstractClassSensorAssignment<?>> classSensorAssignments) {
Collection<ImmutableClassType> changedClassTypes = new ArrayList<>();
// process all class sensor assignments for removal
for (AbstractClassSensorAssignment<?> assignment : classSensorAssignments) {
// narrow the search
Collection<? extends ImmutableClassType> classTypes = classCacheSearchNarrower.narrowByClassSensorAssignment(getClassCache(), assignment);
// get the applier
IInstrumentationApplier instrumentationApplier = configurationResolver.getInstrumentationApplier(assignment, getEnvironment());
changedClassTypes.addAll(getClassCache().getInstrumentationService().removeInstrumentationPoints(classTypes, Collections.singleton(instrumentationApplier)));
}
// if no class was affected just return
if (CollectionUtils.isNotEmpty(changedClassTypes)) {
// if any class was affected re-check those classes against complete configuration
// because we removed all instrumentation points
Collection<IInstrumentationApplier> instrumentationAppliers = getConfigurationHolder().getInstrumentationAppliers();
getClassCache().getInstrumentationService().addInstrumentationPoints(changedClassTypes, getAgentConfiguration(), instrumentationAppliers);
}
return changedClassTypes;
}
/**
* Process the added assignments. New instrumentation points will be added to all the classes in
* the class cache that fit to the given assignments.
*
* @param classSensorAssignments
* Collection of added {@link AbstractClassSensorAssignment}s.
* @return Returns a {@link Collection} of {@link ImmutableClassType} which have been added.
*/
protected Collection<ImmutableClassType> processAddedAssignments(Collection<? extends AbstractClassSensorAssignment<?>> classSensorAssignments) {
Collection<ImmutableClassType> changedClassTypes = new ArrayList<>();
// process all class sensor assignments for adding
for (AbstractClassSensorAssignment<?> assignment : classSensorAssignments) {
// narrow the search
Collection<? extends ImmutableClassType> classTypes = classCacheSearchNarrower.narrowByClassSensorAssignment(getClassCache(), assignment);
// get the applier
IInstrumentationApplier instrumentationApplier = configurationResolver.getInstrumentationApplier(assignment, getEnvironment());
// execute
Collection<? extends ImmutableClassType> instrumentedClassTypes = getClassCache().getInstrumentationService().addInstrumentationPoints(classTypes, getAgentConfiguration(),
Collections.singleton(instrumentationApplier));
changedClassTypes.addAll(instrumentedClassTypes);
}
return changedClassTypes;
}
/**
* @return Returns agent id based on the {@link AgentCacheEntry}.
*/
protected long getAgentId() {
return agentCacheEntry.getId();
}
/**
* @return Returns class cache based on the {@link AgentCacheEntry}.
*/
protected ClassCache getClassCache() {
return agentCacheEntry.getClassCache();
}
/**
* @return Returns configuration holder based on the {@link AgentCacheEntry}.
*/
protected ConfigurationHolder getConfigurationHolder() {
return agentCacheEntry.getConfigurationHolder();
}
/**
* @return Returns environment based on the {@link ConfigurationHolder}.
*/
protected Environment getEnvironment() {
return getConfigurationHolder().getEnvironment();
}
/**
* @return Returns agent configuration based on the {@link ConfigurationHolder}.
*/
protected AgentConfig getAgentConfiguration() {
return getConfigurationHolder().getAgentConfiguration();
}
/**
* Sets {@link #agentCacheEntry}.
*
* @param agentCacheEntry
* New value for {@link #agentCacheEntry}
*/
public void setAgentCacheEntry(AgentCacheEntry agentCacheEntry) {
this.agentCacheEntry = agentCacheEntry;
}
}