Package org.sonatype.nexus.maven.tasks

Source Code of org.sonatype.nexus.maven.tasks.DefaultReleaseRemover$ReleaseRemovalWalkerProcessor

/*
* Sonatype Nexus (TM) Open Source Version
* Copyright (c) 2007-2014 Sonatype, Inc.
* All rights reserved. Includes the third-party code listed at http://links.sonatype.com/products/nexus/oss/attributions.
*
* This program and the accompanying materials are made available under the terms of the Eclipse Public License Version 1.0,
* which accompanies this distribution and is available at http://www.eclipse.org/legal/epl-v10.html.
*
* Sonatype Nexus (TM) Professional Version is available from Sonatype, Inc. "Sonatype" and "Sonatype Nexus" are trademarks
* of Sonatype, Inc. Apache Maven is a trademark of the Apache Software Foundation. M2eclipse is a trademark of the
* Eclipse Foundation. All other trademarks are the property of their respective owners.
*/
package org.sonatype.nexus.maven.tasks;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import org.sonatype.nexus.proxy.IllegalOperationException;
import org.sonatype.nexus.proxy.ItemNotFoundException;
import org.sonatype.nexus.proxy.NoSuchRepositoryException;
import org.sonatype.nexus.proxy.ResourceStoreRequest;
import org.sonatype.nexus.proxy.StorageException;
import org.sonatype.nexus.proxy.item.StorageCollectionItem;
import org.sonatype.nexus.proxy.item.StorageFileItem;
import org.sonatype.nexus.proxy.item.StorageItem;
import org.sonatype.nexus.proxy.maven.MavenRepository;
import org.sonatype.nexus.proxy.maven.RepositoryPolicy;
import org.sonatype.nexus.proxy.maven.gav.Gav;
import org.sonatype.nexus.proxy.registry.ContentClass;
import org.sonatype.nexus.proxy.registry.RepositoryRegistry;
import org.sonatype.nexus.proxy.repository.GroupRepository;
import org.sonatype.nexus.proxy.repository.ProxyRepository;
import org.sonatype.nexus.proxy.repository.Repository;
import org.sonatype.nexus.proxy.targets.Target;
import org.sonatype.nexus.proxy.targets.TargetRegistry;
import org.sonatype.nexus.proxy.targets.TargetStoreWalkerFilter;
import org.sonatype.nexus.proxy.walker.ConjunctionWalkerFilter;
import org.sonatype.nexus.proxy.walker.DefaultWalkerContext;
import org.sonatype.nexus.proxy.walker.DottedStoreWalkerFilter;
import org.sonatype.nexus.proxy.walker.Walker;
import org.sonatype.nexus.proxy.walker.WalkerContext;
import org.sonatype.nexus.proxy.walker.WalkerFilter;
import org.sonatype.nexus.proxy.wastebasket.DeleteOperation;
import org.sonatype.scheduling.TaskUtil;
import org.sonatype.sisu.goodies.common.ComponentSupport;

import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.eclipse.aether.util.version.GenericVersionScheme;
import org.eclipse.aether.version.InvalidVersionSpecificationException;
import org.eclipse.aether.version.Version;
import org.eclipse.aether.version.VersionScheme;

import static com.google.common.base.Preconditions.checkNotNull;
import static org.sonatype.nexus.maven.tasks.ReleaseRemovalTaskDescriptor.ID;

/**
* @since 2.5
*/
@Named
@Singleton
public class DefaultReleaseRemover
    extends ComponentSupport
    implements ReleaseRemover
{

  private final RepositoryRegistry repositoryRegistry;

  private final TargetRegistry targetRegistry;

  private final Walker walker;

  private final ContentClass maven2ContentClass;

  private final VersionScheme versionScheme = new GenericVersionScheme();

  @Inject
  public DefaultReleaseRemover(final RepositoryRegistry repositoryRegistry, final TargetRegistry targetRegistry,
                               final Walker walker,
                               final @Named("maven2") ContentClass maven2ContentClass)
  {
    this.repositoryRegistry = checkNotNull(repositoryRegistry);
    this.targetRegistry = checkNotNull(targetRegistry);
    this.walker = checkNotNull(walker);
    this.maven2ContentClass = checkNotNull(maven2ContentClass);
  }

  @Override
  public ReleaseRemovalResult removeReleases(final ReleaseRemovalRequest request)
      throws NoSuchRepositoryException
  {
    logDetails(request);
    ReleaseRemovalResult result = new ReleaseRemovalResult(request.getRepositoryId());

    Repository repository = repositoryRegistry.getRepository(request.getRepositoryId());
    Target repositoryTarget = targetRegistry.getRepositoryTarget(request.getTargetId());

    if (!Strings.isNullOrEmpty(request.getTargetId()) && repositoryTarget == null) {
      throw new IllegalStateException(
          "The specified repository target does not exist. Perhaps it has been deleted since this repository target was configured? Target id = "
              + request.getTargetId());
    }

    if (!process(request, result, repository, repositoryTarget)) {
      throw new IllegalArgumentException("The repository with ID=" + repository.getId() + " is not valid for "
          + ID);
    }
    log.debug("Results of {} are: {}", ReleaseRemovalTaskDescriptor.ID, result);
    return result;
  }

  private boolean process(final ReleaseRemovalRequest request, final ReleaseRemovalResult result,
                          final Repository repository, final Target repositoryTarget)
  {
    if (!repository.getRepositoryContentClass().isCompatible(maven2ContentClass)) {
      log.debug("Skipping '{}' is not a maven 2 repository", repository.getId());
      return false;
    }

    if (!repository.getLocalStatus().shouldServiceRequest()) {
      log.debug("Skipping '{}' because the repository is out of service", repository.getId());
      return false;
    }

    if (repository.getRepositoryKind().isFacetAvailable(ProxyRepository.class)) {
      log.debug("Skipping '{}' because it is a proxy repository", repository.getId());
      return false;
    }

    if (repository.getRepositoryKind().isFacetAvailable(GroupRepository.class)) {
      log.debug("Skipping '{}' because it is a group repository", repository.getId());
      return false;
    }

    MavenRepository mavenRepository = repository.adaptToFacet(MavenRepository.class);

    if (mavenRepository == null) {
      log.debug("Skipping '{}' because it could not be adapted to MavenRepository", repository.getId());
      return false;
    }

    if (!RepositoryPolicy.RELEASE.equals(mavenRepository.getRepositoryPolicy())) {
      log.debug("Skipping '{}' because it is a snapshot or mixed repository", repository.getId());
      return false;
    }

    removeReleasesFromMavenRepository(mavenRepository, request, result, repositoryTarget);
    return true;
  }

  private void removeReleasesFromMavenRepository(final MavenRepository repository,
                                                 final ReleaseRemovalRequest request,
                                                 final ReleaseRemovalResult result,
                                                 final Target repositoryTarget)
  {
    TaskUtil.checkInterruption();

    if (!repository.getLocalStatus().shouldServiceRequest()) {
      return;
    }

    log.debug(
        "Collecting deletable releases on repository '" + repository.getId() + "' from storage directory "
            + repository.getLocalUrl());

    DefaultWalkerContext ctxMain =
        new DefaultWalkerContext(repository, new ResourceStoreRequest("/"),
            determineFilter(repositoryTarget));

    ctxMain.getContext().put(DeleteOperation.DELETE_OPERATION_CTX_KEY, DeleteOperation.MOVE_TO_TRASH);

    ctxMain.getProcessors().add(new ReleaseRemovalWalkerProcessor(repository, request, result, repositoryTarget));

    walker.walk(ctxMain);

    if (ctxMain.getStopCause() != null) {
      result.setSuccessful(false);
    }
  }

  /**
   * Create an appropriate filter based on the repositoryTarget
   */
  private WalkerFilter determineFilter(final Target repositoryTarget) {
    if (repositoryTarget == null) {
      return new DottedStoreWalkerFilter();
    }
    return ConjunctionWalkerFilter.satisfiesAllOf(new DottedStoreWalkerFilter(),
        new TargetStoreWalkerFilter(repositoryTarget));
  }

  private void logDetails(final ReleaseRemovalRequest request) {
    log.info("Removing older releases from repository: {}", request.getRepositoryId());
    if (log.isDebugEnabled()) {
      log.debug("With parameters: ");
      log.debug("    NumberOfVersionsToKeep: {}", request.getNumberOfVersionsToKeep());
      log.debug("    RepositoryTarget applied: {}", request.getTargetId());
    }
  }

  private class ReleaseRemovalWalkerProcessor
      extends AbstractFileDeletingWalkerProcessor
  {

    private static final String POSSIBLY_EMPTY_COLLECTIONS = "possiblyEmptyCollections";

    private final MavenRepository repository;

    private final ReleaseRemovalRequest request;

    private final Map<Version, List<StorageFileItem>> deletableVersionsAndFiles =
        new HashMap<Version, List<StorageFileItem>>();

    private final Map<Gav, Map<Version, List<StorageFileItem>>> groupArtifactToVersions =
        new HashMap<Gav, Map<Version, List<StorageFileItem>>>();

    private final ReleaseRemovalResult result;

    private final Target repositoryTarget;

    private int deletedFiles = 0;

    private ReleaseRemovalWalkerProcessor(final MavenRepository repository,
                                          final ReleaseRemovalRequest request, final ReleaseRemovalResult result,
                                          final Target repositoryTarget)
    {
      this.repository = repository;
      this.request = request;
      this.result = result;
      this.repositoryTarget = repositoryTarget;
    }

    @Override
    public void processItem(final WalkerContext context, final StorageItem item)
        throws Exception
    {
    }

    @Override
    public void onCollectionExit(final WalkerContext context, final StorageCollectionItem coll)
        throws Exception
    {
      try {
        doOnCollectionExit(context, coll);
      }
      catch (Exception e) {
        // we always simply log the exception and continue
        log.warn("{} failed to process path: '{}'.", ID, coll.getPath(), e);
      }
    }

    private void doOnCollectionExit(final WalkerContext context, final StorageCollectionItem coll)
        throws ItemNotFoundException, StorageException, IllegalOperationException,
               InvalidVersionSpecificationException
    {
      deletableVersionsAndFiles.clear();

      Collection<StorageItem> items = repository.list(false, coll);
      Gav gav = null;
      for (StorageItem item : items) {
        if (!item.isVirtual() && !StorageCollectionItem.class.isAssignableFrom(item.getClass())) {
          gav =
              ((MavenRepository) coll.getRepositoryItemUid().getRepository()).getGavCalculator().pathToGav(
                  item.getPath());
          if (gav != null) {
            addCollectionToContext(context, coll);

            maybeAddStorageFileItemToMap(gav, (StorageFileItem) item);
          }
        }
      }
      // if a gav can be calculated, it should be shared by all files in the collection
      if (null != gav) {
        groupVersions(groupArtifactToVersions, deletableVersionsAndFiles, gav);
      }
    }

    /**
     * Compare the item path to the RepositoryTarget(if it is declared) and only include files that match that
     * pattern.
     * While the walker itself handles GAV paths, this ensures that we also respect patterns with file-level
     * granularity.
     */
    private void maybeAddStorageFileItemToMap(final Gav gav, final StorageFileItem item) {
      if (repositoryTarget != null && !repositoryTarget.isPathContained(
          item.getRepositoryItemUid().getRepository().getRepositoryContentClass(), item
          .getPath())) {
        log.debug("Excluding file: {} from deletion due to repositoryTarget: {}.", item.getName(),
            repositoryTarget.getName());
        return;
      }
      addStorageFileItemToMap(deletableVersionsAndFiles, gav, (StorageFileItem) item);
    }

    /**
     * Store visited collections so we can later determine if we need to delete them.
     */
    private void addCollectionToContext(final WalkerContext context, final StorageCollectionItem coll) {
      if (!context.getContext().containsKey(POSSIBLY_EMPTY_COLLECTIONS)) {
        context.getContext().put(POSSIBLY_EMPTY_COLLECTIONS, Lists.<StorageCollectionItem>newArrayList());
      }
      ((List<StorageCollectionItem>) context.getContext().get(POSSIBLY_EMPTY_COLLECTIONS)).add(coll);
    }

    /**
     * Map Group + Artifact to each version with those GA coordinates
     */
    private void groupVersions(final Map<Gav, Map<Version, List<StorageFileItem>>> groupArtifactToVersions,
                               final Map<Version, List<StorageFileItem>> versionsAndFiles,
                               final Gav gav)
    {
      //ga only coordinates
      Gav ga = new Gav(gav.getGroupId(), gav.getArtifactId(), "");
      if (!groupArtifactToVersions.containsKey(ga)) {
        groupArtifactToVersions.put(ga, Maps.newHashMap(versionsAndFiles));
      }
      groupArtifactToVersions.get(ga).putAll(versionsAndFiles);
    }

    protected void addStorageFileItemToMap(Map<Version, List<StorageFileItem>> map, Gav gav, StorageFileItem item) {
      Version key = null;
      try {
        key = versionScheme.parseVersion(gav.getVersion());
      }
      catch (InvalidVersionSpecificationException e) {
        throw new IllegalStateException("Unable to determine version for " + gav.getVersion() +
            ", cannot proceed with deletion of releases unless"
            + "all version information can be parsed into major.minor.incremental version.");
      }

      if (!map.containsKey(key)) {
        map.put(key, new ArrayList<StorageFileItem>());
      }

      map.get(key).add(item);
    }

    @Override
    public void afterWalk(final WalkerContext context)
        throws Exception
    {
      for (Map.Entry<Gav, Map<Version, List<StorageFileItem>>> gavListEntry : groupArtifactToVersions.entrySet()) {
        Map<Version, List<StorageFileItem>> versions = gavListEntry.getValue();
        if (versions.size() > request.getNumberOfVersionsToKeep()) {
          log.debug("{} will delete {} versions of artifact with g={} a={}",
              ReleaseRemovalTaskDescriptor.ID,
              versions.size() - request.getNumberOfVersionsToKeep(),
              gavListEntry.getKey().getGroupId(), gavListEntry.getKey().getArtifactId());

          List<Version> sortedVersions = Lists.newArrayList(versions.keySet());
          Collections.sort(sortedVersions);
          List<Version> toDelete =
              sortedVersions.subList(0, versions.size() - request.getNumberOfVersionsToKeep());
          log.debug("Will delete these specific versions: {}", toDelete);
          for (Version version : toDelete) {
            for (StorageFileItem storageFileItem : versions.get(version)) {
              repository.deleteItem(createResourceStoreRequest(storageFileItem, context));
              deletedFiles++;
            }
          }
          if (context.getContext().containsKey(POSSIBLY_EMPTY_COLLECTIONS)) {
            for (StorageCollectionItem coll : (List<StorageCollectionItem>) context.getContext().get(
                POSSIBLY_EMPTY_COLLECTIONS)) {
              removeDirectoryIfEmpty(repository, coll);
            }
          }
        }
      }
      result.setDeletedFileCount(deletedFiles);
      result.setSuccessful(true);
    }
  }
}
TOP

Related Classes of org.sonatype.nexus.maven.tasks.DefaultReleaseRemover$ReleaseRemovalWalkerProcessor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.