Repository Management with Nexus
21.7. Creating a Complex Plugin

In this section, we will step through the skeletal, sample project that implements a virus scanner plugin for Nexus as created with the archetype. This plugin will consist of:
- A managed "virus scanner" component
- A RequestProcessor that sends all "incoming" artifacts for scanning
- A repository customizer to inject a RequestProcessor to all proxy repositories
We start with creating a @Managed component contract for the VirusScanner. While this class could just as easily be a non-managed component, this example uses the @Managed and @Singleton annotations to demonstrate dependency injection.
VirusScanner Interface.
package org.sonatype.book.nexus;
import javax.inject.Singleton;
import org.sonatype.nexus.proxy.item.StorageFileItem;
import org.sonatype.plugin.Managed;
@Managed
@Singleton
public interface VirusScanner
{
boolean hasVirus( StorageFileItem file );
}
Once we have the interface for VirusScanner, we need to define a named instance XYVirusScanner which implements the interface. The following example shows how the @Named annotation is used to assign a name of "XY" to this implementation.
XYVirusScanner Implementation.
package org.sonatype.book.nexus;
import javax.inject.Named;
import org.sonatype.nexus.proxy.item.StorageFileItem;
@Named( "XY" )
public class XYVirusScanner implements VirusScanner {
public boolean hasVirus( StorageFileItem file ) {
// DO THE JOB HERE
System.out.println( "Kung Fu VirusScanner --- " +
"scanning for viruses on item: " + file.getPath() );
// simulating virus hit by having the filename
// contain the "infected" string
return file.getName().contains( "infected" );
}
}
The next class is a request processor which scans an artifact for viruses before it is cached locally. If a virus is found in an artifact, this plugin will refuse to cache the artifact and trigger an event which will signal that a virus was found in a file item. Note the use of @Named which assigns the name "virusScanner" to this component. Also note the two uses of @Inject. The first use of @Inject will fetch the default implementation of ApplicationEventMulticaster, and the second use of @Inject will fetch the "XY" virus scanner.
Virus Scanning Request Processor.
package org.sonatype.book.nexus;
import javax.inject.Inject;
import javax.inject.Named;
import org.sonatype.book.nexus.events.InfectedItemFoundEvent;
import org.sonatype.nexus.proxy.ResourceStoreRequest;
import org.sonatype.nexus.proxy.access.Action;
import org.sonatype.nexus.proxy.item.AbstractStorageItem;
import org.sonatype.nexus.proxy.item.StorageFileItem;
import org.sonatype.nexus.proxy.repository.ProxyRepository;
import org.sonatype.nexus.proxy.repository.Repository;
import org.sonatype.nexus.proxy.repository.RequestProcessor;
import org.sonatype.plexus.appevents.ApplicationEventMulticaster;
@Named( "virusScanner" )
public class VirusScannerRequestProcessor
implements RequestProcessor {
@Inject
private ApplicationEventMulticaster applicationEventMulticaster;
@Inject
private @Named( "XY" )
VirusScanner virusScanner;
// @Inject
// private @Named("A") CommonDependency commonDependency;
public boolean process( Repository repository,
ResourceStoreRequest request, Action action )
{
// Check dependency
// System.out.println( "VirusScannerRequestProcessor " +
"--- CommonDependency data: " +
commonDependency.getData()
// );
// don't decide until have content
return true;
}
public boolean shouldProxy( ProxyRepository repository,
ResourceStoreRequest request )
{
// don't decide until have content
return true;
}
public boolean shouldCache( ProxyRepository repository,
AbstractStorageItem item )
{
if ( item instanceof StorageFileItem ) {
StorageFileItem file = (StorageFileItem) item;
// do a virus scan
boolean hasVirus = virusScanner.hasVirus( file );
if ( hasVirus ) {
applicationEventMulticaster
.notifyEventListeners(
new InfectedItemFoundEvent( item
.getRepositoryItemUid().getRepository(),
file ) );
}
return !hasVirus;
} else {
return true;
}
}
}
The last component is the RepositoryCustomizer. It simply injects our virus scanner RequestProcessor into proxy repositories only. For his example local uploads are considered safe, so the only way to get an artifact into Nexus from the uncontrolled Internet is a proxy repository. Note how the request processor is injected into this repository customizer with @Inject and @Named.
The Virus Scanner Repository Customizer.
package org.sonatype.book.nexus;
import javax.inject.Inject;
import javax.inject.Named;
import org.sonatype.configuration.ConfigurationException;
import org.sonatype.nexus.plugins.RepositoryCustomizer;
import org.sonatype.nexus.proxy.repository.ProxyRepository;
import org.sonatype.nexus.proxy.repository.Repository;
import org.sonatype.nexus.proxy.repository.RequestProcessor;
public class VirusScannerRepositoryCustomizer
implements RepositoryCustomizer {
@Inject
private @Named( "virusScanner" )
RequestProcessor virusScannerRequestProcessor;
public boolean isHandledRepository( Repository repository ) {
// handle proxy reposes only
return repository.getRepositoryKind()
.isFacetAvailable( ProxyRepository.class );
}
public void configureRepository( Repository repository )
throws ConfigurationException {
repository.getRequestProcessors()
.put( "virusScanner",
virusScannerRequestProcessor );
}
}
