Package org.sonatype.nexus.proxy.repository

Source Code of org.sonatype.nexus.proxy.repository.AbstractRepository

/*
* 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.proxy.repository;

import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;

import javax.inject.Inject;

import org.sonatype.configuration.ConfigurationException;
import org.sonatype.nexus.configuration.Configurator;
import org.sonatype.nexus.configuration.ExternalConfiguration;
import org.sonatype.nexus.configuration.model.CRepositoryCoreConfiguration;
import org.sonatype.nexus.configuration.model.CRepositoryExternalConfigurationHolderFactory;
import org.sonatype.nexus.mime.MimeRulesSource;
import org.sonatype.nexus.mime.MimeSupport;
import org.sonatype.nexus.proxy.AccessDeniedException;
import org.sonatype.nexus.proxy.IllegalOperationException;
import org.sonatype.nexus.proxy.IllegalRequestException;
import org.sonatype.nexus.proxy.ItemNotFoundException;
import org.sonatype.nexus.proxy.LocalStorageException;
import org.sonatype.nexus.proxy.RepositoryNotAvailableException;
import org.sonatype.nexus.proxy.ResourceStoreRequest;
import org.sonatype.nexus.proxy.StorageException;
import org.sonatype.nexus.proxy.access.AccessManager;
import org.sonatype.nexus.proxy.access.Action;
import org.sonatype.nexus.proxy.attributes.AttributesHandler;
import org.sonatype.nexus.proxy.cache.CacheManager;
import org.sonatype.nexus.proxy.cache.PathCache;
import org.sonatype.nexus.proxy.events.RepositoryConfigurationUpdatedEvent;
import org.sonatype.nexus.proxy.events.RepositoryEventExpireNotFoundCaches;
import org.sonatype.nexus.proxy.events.RepositoryEventLocalStatusChanged;
import org.sonatype.nexus.proxy.events.RepositoryEventRecreateAttributes;
import org.sonatype.nexus.proxy.events.RepositoryItemEventDeleteRoot;
import org.sonatype.nexus.proxy.events.RepositoryItemEventRetrieve;
import org.sonatype.nexus.proxy.events.RepositoryItemEventStoreCreate;
import org.sonatype.nexus.proxy.events.RepositoryItemEventStoreUpdate;
import org.sonatype.nexus.proxy.item.AbstractStorageItem;
import org.sonatype.nexus.proxy.item.ContentGenerator;
import org.sonatype.nexus.proxy.item.ContentLocator;
import org.sonatype.nexus.proxy.item.DefaultStorageCollectionItem;
import org.sonatype.nexus.proxy.item.DefaultStorageFileItem;
import org.sonatype.nexus.proxy.item.DefaultStorageNotFoundItem;
import org.sonatype.nexus.proxy.item.PreparedContentLocator;
import org.sonatype.nexus.proxy.item.ReadLockingContentLocator;
import org.sonatype.nexus.proxy.item.RepositoryItemUid;
import org.sonatype.nexus.proxy.item.RepositoryItemUidFactory;
import org.sonatype.nexus.proxy.item.RepositoryItemUidLock;
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.item.uid.RepositoryItemUidAttributeManager;
import org.sonatype.nexus.proxy.storage.UnsupportedStorageOperationException;
import org.sonatype.nexus.proxy.storage.local.DefaultLocalStorageContext;
import org.sonatype.nexus.proxy.storage.local.LocalRepositoryStorage;
import org.sonatype.nexus.proxy.storage.local.LocalStorageContext;
import org.sonatype.nexus.proxy.targets.TargetRegistry;
import org.sonatype.nexus.proxy.targets.TargetSet;
import org.sonatype.nexus.proxy.utils.RepositoryStringUtils;
import org.sonatype.nexus.proxy.walker.DefaultWalkerContext;
import org.sonatype.nexus.proxy.walker.ParentOMatic;
import org.sonatype.nexus.proxy.walker.Walker;
import org.sonatype.nexus.proxy.walker.WalkerException;
import org.sonatype.nexus.proxy.walker.WalkerFilter;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Maps;
import org.codehaus.plexus.util.StringUtils;
import org.joda.time.DateTime;

import static com.google.common.base.Preconditions.checkNotNull;
import static org.sonatype.nexus.proxy.ItemNotFoundException.reasonFor;

/**
* <p>
* A common base for Proximity repository. It defines all the needed properties and main methods as in
* ProximityRepository interface.
* <p>
* This abstract class handles the following functionalities:
* <ul>
* <li>Holds base properties like repo ID, group ID, rank</li>
* <li>Manages AccessManager</li>
* <li>Manages notFoundCache to speed up responses</li>
* <li>Manages event listeners</li>
* </ul>
* <p>
* The subclasses only needs to implement the abstract method focusing on item retrieaval and other "basic" functions.
*
* @author cstamas
*/
public abstract class AbstractRepository
    extends ConfigurableRepository
    implements Repository
{

  // == injected

  private CacheManager cacheManager;

  private TargetRegistry targetRegistry;

  private RepositoryItemUidFactory repositoryItemUidFactory;

  private RepositoryItemUidAttributeManager repositoryItemUidAttributeManager;

  private Walker walker;

  private MimeSupport mimeSupport;

  private Map<String, ContentGenerator> contentGenerators;

  private AttributesHandler attributesHandler;

  private AccessManager accessManager;

  // == set by this

  /**
   * Local storage context to store storage-wide configs.
   */
  private LocalStorageContext localStorageContext;

  /**
   * The not found cache.
   */
  private PathCache notFoundCache;

  /**
   * Request strategies map. Supersedes RequestProcessors.
   *
   * @since 2.5
   */
  private final Map<String, RequestStrategy> requestStrategies = Maps.newHashMap();

  // ==

  /**
   * The local storage, set by configurator.
   */
  private LocalRepositoryStorage localStorage;

  /**
   * if local url changed, need special handling after save
   */
  private boolean localUrlChanged = false;

  /**
   * if non-indexable -> indexable change occured, need special handling after save
   */
  private boolean madeSearchable = false;

  /**
   * if local status changed, need special handling after save
   */
  private boolean localStatusChanged = false;

  // --

  @Inject
  public void populateAbstractRepository(
      CacheManager cacheManager, TargetRegistry targetRegistry, RepositoryItemUidFactory repositoryItemUidFactory,
      RepositoryItemUidAttributeManager repositoryItemUidAttributeManager, AccessManager accessManager, Walker walker,
      MimeSupport mimeSupport, Map<String, ContentGenerator> contentGenerators, AttributesHandler attributesHandler)
  {
    this.cacheManager = checkNotNull(cacheManager);
    this.targetRegistry = checkNotNull(targetRegistry);
    this.repositoryItemUidFactory = checkNotNull(repositoryItemUidFactory);
    this.repositoryItemUidAttributeManager = checkNotNull(repositoryItemUidAttributeManager);
    this.walker = checkNotNull(walker);
    this.mimeSupport = checkNotNull(mimeSupport);
    this.contentGenerators = checkNotNull(contentGenerators);
    this.attributesHandler = checkNotNull(attributesHandler);
    this.accessManager = checkNotNull(accessManager);

    // we have been not configured yet! So, we have no ID and stuff coming from config!
    // post inject stuff
    this.localStorageContext = new DefaultLocalStorageContext(
        getApplicationConfiguration().getGlobalLocalStorageContext());
  }

  protected MimeSupport getMimeSupport() {
    return mimeSupport;
  }

  @Override
  public MimeRulesSource getMimeRulesSource() {
    return MimeRulesSource.NOOP;
  }

  // ==

  @Override
  protected abstract Configurator getConfigurator();

  @Override
  protected abstract CRepositoryExternalConfigurationHolderFactory<?> getExternalConfigurationHolderFactory();

  @Override
  protected void doConfigure()
      throws ConfigurationException
  {
    super.doConfigure();
    if (notFoundCache == null) {
      this.notFoundCache = cacheManager.getPathCache(getId());
    }
  }

  @Override
  public boolean commitChanges()
      throws ConfigurationException
  {
    boolean wasDirty = super.commitChanges();

    if (wasDirty) {
      eventBus().post(getRepositoryConfigurationUpdatedEvent());
    }

    this.localUrlChanged = false;

    this.madeSearchable = false;

    this.localStatusChanged = false;

    return wasDirty;
  }

  @Override
  public boolean rollbackChanges() {
    this.localUrlChanged = false;

    this.madeSearchable = false;

    this.localStatusChanged = false;

    return super.rollbackChanges();
  }

  protected RepositoryConfigurationUpdatedEvent getRepositoryConfigurationUpdatedEvent() {
    RepositoryConfigurationUpdatedEvent event = new RepositoryConfigurationUpdatedEvent(this);

    event.setLocalUrlChanged(this.localUrlChanged);
    event.setMadeSearchable(this.madeSearchable);
    event.setLocalStatusChanged(localStatusChanged);

    return event;
  }

  protected AbstractRepositoryConfiguration getExternalConfiguration(boolean forModification) {
    final CRepositoryCoreConfiguration cc = getCurrentCoreConfiguration();
    if (cc != null) {
      ExternalConfiguration<?> ec = cc.getExternalConfiguration();
      if (ec != null) {
        return (AbstractRepositoryConfiguration) ec.getConfiguration(forModification);
      }
    }
    return null;
  }

  // ==

  @Override
  public RequestStrategy registerRequestStrategy(final String key, final RequestStrategy strategy) {
    checkNotNull(key);
    checkNotNull(strategy);
    synchronized (requestStrategies) {
      return requestStrategies.put(key, strategy);
    }
  }

  @Override
  public RequestStrategy unregisterRequestStrategy(final String key) {
    checkNotNull(key);
    synchronized (requestStrategies) {
      return requestStrategies.remove(key);
    }
  }

  @Override
  public Map<String, RequestStrategy> getRegisteredStrategies() {
    synchronized (requestStrategies) {
      return Maps.newHashMap(requestStrategies);
    }
  }

  /**
   * Returns the repository Item Uid Factory.
   */
  protected RepositoryItemUidFactory getRepositoryItemUidFactory() {
    return repositoryItemUidFactory;
  }

  /**
   * Gets the not found cache.
   *
   * @return the not found cache
   */
  @Override
  public PathCache getNotFoundCache() {
    return notFoundCache;
  }

  @Override
  public void setIndexable(boolean indexable) {
    if (!isIndexable() && indexable) {
      // we have a non-indexable -> indexable transition
      madeSearchable = true;
    }

    super.setIndexable(indexable);
  }

  @Override
  public void setLocalUrl(String localUrl)
      throws LocalStorageException
  {
    String newLocalUrl = null;

    if (localUrl != null) {
      newLocalUrl = localUrl.trim();
    }

    if (newLocalUrl != null) {
      if (newLocalUrl.endsWith(RepositoryItemUid.PATH_SEPARATOR)) {
        newLocalUrl = newLocalUrl.substring(0, newLocalUrl.length() - 1);
      }

      getLocalStorage().validateStorageUrl(newLocalUrl);
    }

    // Dont use getLocalUrl since that applies default
    if (getCurrentConfiguration(false).getLocalStorage() != null
        && !StringUtils.equals(newLocalUrl, getCurrentConfiguration(false).getLocalStorage().getUrl())) {
      this.localUrlChanged = true;
    }

    super.setLocalUrl(localUrl);
  }

  @Override
  public void setLocalStatus(LocalStatus localStatus) {
    if (!localStatus.equals(getLocalStatus())) {
      LocalStatus oldLocalStatus = getLocalStatus();

      super.setLocalStatus(localStatus);

      localStatusChanged = true;

      eventBus().post(new RepositoryEventLocalStatusChanged(this, oldLocalStatus, localStatus));
    }
  }

  @Override
  @SuppressWarnings("unchecked")
  public <F> F adaptToFacet(Class<F> t) {
    if (getRepositoryKind().isFacetAvailable(t)) {
      return (F) this;
    }
    else {
      return null;
    }
  }

  protected Walker getWalker() {
    return walker;
  }

  protected Map<String, ContentGenerator> getContentGenerators() {
    return contentGenerators;
  }

  // ===================================================================================
  // Repository iface

  @Override
  public AccessManager getAccessManager() {
    return accessManager;
  }

  /**
   * DefaultReleaseRemoverIT
   */
  @VisibleForTesting
  public void setAccessManager(AccessManager accessManager) {
    this.accessManager = accessManager;
  }

  /**
   * Returns {@code true} if action should be performed against this repository. Should guard against repeated
   * processing, like in "cascade" case when a group does action against it's members.
   */
  protected boolean shouldServiceOperation(final ResourceStoreRequest request, final String action) {
    if (!getLocalStatus().shouldServiceRequest()) {
      log.debug("Not performing {}, {} is not in service.", action, this);
      return false;
    }
    if (request.getProcessedRepositories().contains(getId())) {
      log.debug("Not performing {}, {} was already processed in this request.", action, this);
      return false;
    }
    request.addProcessedRepository(getId());
    return true;
  }

  @Override
  public final void expireCaches(final ResourceStoreRequest request) {
    expireCaches(request, null);
  }

  @Override
  public final boolean expireCaches(final ResourceStoreRequest request, final WalkerFilter filter) {
    if (!shouldServiceOperation(request, "expireCaches")) {
      return false;
    }
    log.debug("Expiring caches in repository {} from path='{}'", this, request.getRequestPath());
    return doExpireCaches(request, filter);
  }

  protected boolean doExpireCaches(final ResourceStoreRequest request, final WalkerFilter filter) {
    // at this level (we are not proxy) expireCaches() actually boils down to "expire NFC" only
    // we are NOT crawling local storage content to flip the isExpired flags to true on a hosted
    // repo, since those attributes in case of hosted (or any other non-proxy) repositories does not have any
    // meaning
    return doExpireNotFoundCaches(request, filter);
  }

  @Override
  public final void expireNotFoundCaches(final ResourceStoreRequest request) {
    expireNotFoundCaches(request, null);
  }

  @Override
  public final boolean expireNotFoundCaches(final ResourceStoreRequest request, final WalkerFilter filter) {
    if (!shouldServiceOperation(request, "expireNotFoundCaches")) {
      return false;
    }
    log.debug("Expiring NFC caches in repository {} from path='{}'", this, request.getRequestPath());
    return doExpireNotFoundCaches(request, filter);
  }

  protected boolean doExpireNotFoundCaches(final ResourceStoreRequest request, final WalkerFilter filter) {
    if (StringUtils.isBlank(request.getRequestPath())) {
      request.setRequestPath(RepositoryItemUid.PATH_ROOT);
    }
    boolean cacheAltered = false;
    // remove the items from NFC
    if (filter == null) {
      if (RepositoryItemUid.PATH_ROOT.equals(request.getRequestPath())) {
        // purge all
        if (getNotFoundCache() != null) {
          cacheAltered = getNotFoundCache().purge();
        }
      }
      else {
        // purge below and above path only
        if (getNotFoundCache() != null) {
          boolean altered1 = getNotFoundCache().removeWithParents(request.getRequestPath());
          boolean altered2 = getNotFoundCache().removeWithChildren(request.getRequestPath());
          cacheAltered = altered1 || altered2;
        }
      }
    }
    else {
      final ParentOMatic parentOMatic = new ParentOMatic(false);
      final DefaultWalkerContext context = new DefaultWalkerContext(this, request);
      final Collection<String> nfcPaths = getNotFoundCache().listKeysInCache();

      for (String nfcPath : nfcPaths) {
        final DefaultStorageNotFoundItem nfcItem =
            new DefaultStorageNotFoundItem(this, new ResourceStoreRequest(nfcPath));

        if (filter.shouldProcess(context, nfcItem)) {
          parentOMatic.addAndMarkPath(nfcPath);
        }
      }

      for (String path : parentOMatic.getMarkedPaths()) {
        boolean removed = getNotFoundCache().remove(path);
        cacheAltered = cacheAltered || removed;
      }
    }

    if (log.isDebugEnabled()) {
      if (cacheAltered) {
        log.info("NFC for repository {} from path='{}' was cleared.", this, request.getRequestPath());
      }
      else {
        log.debug("Clear NFC for repository {} from path='{}' did not alter cache.", this, request.getRequestPath());
      }
    }

    eventBus().post(
        new RepositoryEventExpireNotFoundCaches(this, request.getRequestPath(),
            request.getRequestContext().flatten(), cacheAltered));

    return cacheAltered;
  }

  @Override
  public RepositoryMetadataManager getRepositoryMetadataManager() {
    return new NoopRepositoryMetadataManager();
  }

  @Override
  public final Collection<String> evictUnusedItems(final ResourceStoreRequest request, final long timestamp) {
    if (!shouldServiceOperation(request, "evictUnusedItems")) {
      return Collections.emptyList();
    }
    log.debug("Evicting unused items in repository {}, from path='{}'", this, request.getRequestPath());
    return doEvictUnusedItems(request, timestamp);
  }

  protected Collection<String> doEvictUnusedItems(final ResourceStoreRequest request, final long timestamp) {
    // this is noop at hosted level
    return Collections.emptyList();
  }

  // TODO: this is strictly Nexus internal thing, not exposed in any way, nothing should override this
  // Also, AttributeStorage should never be "rebuilt", thats := data loss
  @Override
  public final boolean recreateAttributes(final ResourceStoreRequest request, final Map<String, String> initialData) {
    if (!shouldServiceOperation(request, "recreateAttributes")) {
      return false;
    }
    if (StringUtils.isEmpty(request.getRequestPath())) {
      request.setRequestPath(RepositoryItemUid.PATH_ROOT);
    }
    log.info("Rebuilding item attributes in repository {} from path='{}'", this, request.getRequestPath());
    final RecreateAttributesWalker walkerProcessor = new RecreateAttributesWalker(this, initialData);
    final DefaultWalkerContext ctx = new DefaultWalkerContext(this, request);
    ctx.getProcessors().add(walkerProcessor);
    // let it loose
    try {
      getWalker().walk(ctx);
    }
    catch (WalkerException e) {
      if (!(e.getWalkerContext().getStopCause() instanceof ItemNotFoundException)) {
        // everything that is not ItemNotFound should be reported,
        // otherwise just neglect it
        throw e;
      }
    }
    eventBus().post(new RepositoryEventRecreateAttributes(this));
    return true;
  }

  @Override
  public AttributesHandler getAttributesHandler() {
    return attributesHandler;
  }

  @Override
  public LocalStorageContext getLocalStorageContext() {
    return localStorageContext;
  }

  @Override
  public LocalRepositoryStorage getLocalStorage() {
    return localStorage;
  }

  @Override
  public void setLocalStorage(LocalRepositoryStorage localStorage) {
    getCurrentConfiguration(true).getLocalStorage().setProvider(localStorage.getProviderId());

    this.localStorage = localStorage;
  }

  // ===================================================================================
  // Store iface

  @Override
  public StorageItem retrieveItem(ResourceStoreRequest request)
      throws IllegalOperationException, ItemNotFoundException, StorageException, AccessDeniedException
  {
    checkConditions(request, Action.read);

    StorageItem item = retrieveItem(false, request);

    if (StorageCollectionItem.class.isAssignableFrom(item.getClass()) && !isBrowseable()) {
      log.debug(
          getId() + " retrieveItem() :: FOUND a collection on " + request.toString()
              + " but repository is not Browseable.");

      throw new ItemNotFoundException(reasonFor(request, this, "Repository %s is not browsable",
          this));
    }

    checkPostConditions(request, item);

    return item;
  }

  @Override
  public void copyItem(ResourceStoreRequest from, ResourceStoreRequest to)
      throws UnsupportedStorageOperationException, IllegalOperationException, ItemNotFoundException,
             StorageException, AccessDeniedException
  {
    checkConditions(from, Action.read);
    checkConditions(to, getResultingActionOnWrite(to));

    copyItem(false, from, to);
  }

  @Override
  public void moveItem(ResourceStoreRequest from, ResourceStoreRequest to)
      throws UnsupportedStorageOperationException, IllegalOperationException, ItemNotFoundException,
             StorageException, AccessDeniedException
  {
    checkConditions(from, Action.read);
    checkConditions(from, Action.delete);
    checkConditions(to, getResultingActionOnWrite(to));

    moveItem(false, from, to);
  }

  @Override
  public void deleteItem(ResourceStoreRequest request)
      throws UnsupportedStorageOperationException, IllegalOperationException, ItemNotFoundException,
             StorageException, AccessDeniedException
  {
    checkConditions(request, Action.delete);

    deleteItem(false, request);
  }

  @Override
  public void storeItem(ResourceStoreRequest request, InputStream is, Map<String, String> userAttributes)
      throws UnsupportedStorageOperationException, IllegalOperationException, StorageException, AccessDeniedException
  {
    try {
      checkConditions(request, getResultingActionOnWrite(request));
    }
    catch (ItemNotFoundException e) {
      throw new AccessDeniedException(request, e.getMessage());
    }

    DefaultStorageFileItem fItem =
        new DefaultStorageFileItem(this, request, true, true, new PreparedContentLocator(is,
            getMimeSupport().guessMimeTypeFromPath(getMimeRulesSource(), request.getRequestPath()),
            ContentLocator.UNKNOWN_LENGTH));

    if (userAttributes != null) {
      fItem.getRepositoryItemAttributes().putAll(userAttributes);
    }

    storeItem(false, fItem);
  }

  @Override
  public void createCollection(ResourceStoreRequest request, Map<String, String> userAttributes)
      throws UnsupportedStorageOperationException, IllegalOperationException, StorageException, AccessDeniedException
  {
    try {
      checkConditions(request, getResultingActionOnWrite(request));
    }
    catch (ItemNotFoundException e) {
      throw new AccessDeniedException(request, e.getMessage());
    }

    DefaultStorageCollectionItem coll = new DefaultStorageCollectionItem(this, request, true, true);

    if (userAttributes != null) {
      coll.getRepositoryItemAttributes().putAll(userAttributes);
    }

    storeItem(false, coll);
  }

  @Override
  public Collection<StorageItem> list(ResourceStoreRequest request)
      throws IllegalOperationException, ItemNotFoundException, StorageException, AccessDeniedException
  {
    checkConditions(request, Action.read);

    Collection<StorageItem> items = null;

    if (isBrowseable()) {
      items = list(false, request);
    }
    else {
      throw new ItemNotFoundException(reasonFor(request, this, "Repository %s is not browsable", this));
    }

    return items;
  }

  @Override
  public TargetSet getTargetsForRequest(ResourceStoreRequest request) {
    if (log.isDebugEnabled()) {
      log.debug("getTargetsForRequest() :: " + this.getId() + ":" + request.getRequestPath());
    }

    return targetRegistry.getTargetsForRepositoryPath(this, request.getRequestPath());
  }

  @Override
  public boolean hasAnyTargetsForRequest(ResourceStoreRequest request) {
    if (log.isDebugEnabled()) {
      log.debug("hasAnyTargetsForRequest() :: " + this.getId());
    }

    return targetRegistry.hasAnyApplicableTarget(this);
  }

  @Override
  public Action getResultingActionOnWrite(final ResourceStoreRequest rsr)
      throws LocalStorageException
  {
    final boolean isInLocalStorage = getLocalStorage().containsItem(this, rsr);

    if (isInLocalStorage) {
      return Action.update;
    }
    else {
      return Action.create;
    }
  }

  // ===================================================================================
  // Repositry store-like

  @Override
  public StorageItem retrieveItem(boolean fromTask, ResourceStoreRequest request)
      throws IllegalOperationException, ItemNotFoundException, StorageException
  {
    if (log.isDebugEnabled()) {
      log.debug(getId() + ".retrieveItem() :: " + request.toString());
    }

    if (!getLocalStatus().shouldServiceRequest()) {
      throw new RepositoryNotAvailableException(this);
    }

    request.addProcessedRepository(getId());

    maintainNotFoundCache(request);

    final RepositoryItemUid uid = createUid(request.getRequestPath());

    final RepositoryItemUidLock uidLock = uid.getLock();

    uidLock.lock(Action.read);

    try {
      StorageItem item = doRetrieveItem(request);

      // file with generated content?
      if (item instanceof StorageFileItem && ((StorageFileItem) item).isContentGenerated()) {
        StorageFileItem file = (StorageFileItem) item;

        String key = file.getContentGeneratorId();

        if (getContentGenerators().containsKey(key)) {
          ContentGenerator generator = getContentGenerators().get(key);

          try {
            file.setContentLocator(generator.generateContent(this, uid.getPath(), file));
          }
          catch (Exception e) {
            throw new LocalStorageException("Could not generate content:", e);
          }
        }
        else {
          log.info(
              String.format(
                  "The file in repository %s on path=\"%s\" should be generated by ContentGeneratorId=%s, but component does not exists!",
                  RepositoryStringUtils.getHumanizedNameString(this), uid.getPath(), key));

          throw new ItemNotFoundException(reasonFor(request, this,
              "The generator for generated path %s with key %s not found in %s", request.getRequestPath(),
              key, this));
        }
      }

      eventBus().post(new RepositoryItemEventRetrieve(this, item));

      if (log.isDebugEnabled()) {
        log.debug(getId() + " retrieveItem() :: FOUND " + uid.toString());
      }

      return item;
    }
    catch (ItemNotFoundException ex) {
      if (log.isDebugEnabled()) {
        log.debug(getId() + " retrieveItem() :: NOT FOUND " + uid.toString());
      }

      if (shouldAddToNotFoundCache(request)) {
        addToNotFoundCache(request);
      }

      throw ex;
    }
    finally {
      uidLock.unlock();
    }
  }

  @Override
  public void copyItem(boolean fromTask, ResourceStoreRequest from, ResourceStoreRequest to)
      throws UnsupportedStorageOperationException, IllegalOperationException, ItemNotFoundException, StorageException
  {
    if (log.isDebugEnabled()) {
      log.debug(getId() + ".copyItem() :: " + from.toString() + " --> " + to.toString());
    }

    if (!getLocalStatus().shouldServiceRequest()) {
      throw new RepositoryNotAvailableException(this);
    }

    maintainNotFoundCache(from);

    final RepositoryItemUid fromUid = createUid(from.getRequestPath());

    final RepositoryItemUid toUid = createUid(to.getRequestPath());

    final RepositoryItemUidLock fromUidLock = fromUid.getLock();

    final RepositoryItemUidLock toUidLock = toUid.getLock();

    fromUidLock.lock(Action.read);
    toUidLock.lock(Action.create);

    try {
      StorageItem item = retrieveItem(fromTask, from);

      if (StorageFileItem.class.isAssignableFrom(item.getClass())) {
        try {
          DefaultStorageFileItem target =
              new DefaultStorageFileItem(this, to, true, true, ((StorageFileItem) item).getContentLocator());

          storeItem(fromTask, target);

          // remove the "to" item from n-cache if there
          removeFromNotFoundCache(to);
        }
        catch (IOException e) {
          throw new LocalStorageException("Could not get the content of source file (is it file?)!", e);
        }
      }
    }
    finally {
      toUidLock.unlock();

      fromUidLock.unlock();
    }
  }

  @Override
  public void moveItem(boolean fromTask, ResourceStoreRequest from, ResourceStoreRequest to)
      throws UnsupportedStorageOperationException, IllegalOperationException, ItemNotFoundException, StorageException
  {
    if (log.isDebugEnabled()) {
      log.debug(getId() + ".moveItem() :: " + from.toString() + " --> " + to.toString());
    }

    if (!getLocalStatus().shouldServiceRequest()) {
      throw new RepositoryNotAvailableException(this);
    }

    copyItem(fromTask, from, to);

    deleteItem(fromTask, from);
  }

  @Override
  public void deleteItem(boolean fromTask, ResourceStoreRequest request)
      throws UnsupportedStorageOperationException, IllegalOperationException, ItemNotFoundException, StorageException
  {
    if (log.isDebugEnabled()) {
      log.debug(getId() + ".deleteItem() :: " + request.toString());
    }

    if (!getLocalStatus().shouldServiceRequest()) {
      throw new RepositoryNotAvailableException(this);
    }

    maintainNotFoundCache(request);

    final RepositoryItemUid uid = createUid(request.getRequestPath());

    final RepositoryItemUidLock uidLock = uid.getLock();

    uidLock.lock(Action.delete);

    try {
      StorageItem item = null;
      try {
        // determine is the thing to be deleted a collection or not
        item = getLocalStorage().retrieveItem(this, request);
      }
      catch (ItemNotFoundException ex) {
        if (shouldNeglectItemNotFoundExOnDelete(request, ex)) {
          item = null;
        }
        else {
          throw ex;
        }
      }

      if (item != null) {
        // fire the event for file being deleted
        eventBus().post(new RepositoryItemEventDeleteRoot(this, item));

        // if we are deleting a collection, perform recursive notification about this too
        if (item instanceof StorageCollectionItem) {
          log.debug("deleting a collection '{}'", item.getPath());

          // NEXUS-7628: If collection is being deleted, purge all of it's children from NFC
          if (isNotFoundCacheActive()) {
            getNotFoundCache().removeWithChildren(request.getRequestPath());
          }

          // it is collection, walk it and below and fire events for all files
          DeletionNotifierWalker dnw = new DeletionNotifierWalker(eventBus(), request);

          DefaultWalkerContext ctx = new DefaultWalkerContext(this, request);

          ctx.getProcessors().add(dnw);

          try {
            getWalker().walk(ctx);
          }
          catch (WalkerException e) {
            if (!(e.getWalkerContext().getStopCause() instanceof ItemNotFoundException)) {
              // everything that is not ItemNotFound should be reported,
              // otherwise just neglect it
              throw e;
            }
          }
        }

        doDeleteItem(request);
      }
    }
    finally {
      uidLock.unlock();
    }
  }

  /**
   * Decides should a {@link ItemNotFoundException} be neglected on
   * {@link #deleteItem(boolean, org.sonatype.nexus.proxy.ResourceStoreRequest)} method invocation or not. Nexus
   * was always throwing this exception when deletion of non existent item was tried, but since 2.4 in Maven support
   * the Maven checksum files are not existing anymore as standalone items (in local storage), hence default
   * implementation of this method simply returns {@code false} to retain this behaviour, but still, to make
   * Repository implementations able to override this.
   *
   * @since 2.6
   */
  protected boolean shouldNeglectItemNotFoundExOnDelete(ResourceStoreRequest request, ItemNotFoundException ex) {
    return false;
  }

  @Override
  public void storeItem(boolean fromTask, StorageItem item)
      throws UnsupportedStorageOperationException, IllegalOperationException, StorageException
  {
    if (log.isDebugEnabled()) {
      log.debug(getId() + ".storeItem() :: " + item.getRepositoryItemUid().toString());
    }

    if (!getLocalStatus().shouldServiceRequest()) {
      throw new RepositoryNotAvailableException(this);
    }

    final RepositoryItemUid uid = createUid(item.getPath());

    // replace UID to own one
    item.setRepositoryItemUid(uid);

    // NEXUS-4550: This "fake" UID/lock here is introduced only to serialize uploaders
    // This will catch immediately an uploader if an upload already happens
    // and prevent deadlocks, since uploader still does not have
    // shared lock
    final RepositoryItemUid uploaderUid = createUid(item.getPath() + ".storeItem()");

    final RepositoryItemUidLock uidUploaderLock = uploaderUid.getLock();

    uidUploaderLock.lock(Action.create);

    final Action action = getResultingActionOnWrite(item.getResourceStoreRequest());

    try {
      // NEXUS-4550: we are shared-locking the actual UID (to not prevent downloaders while
      // we save to temporary location. But this depends on actual LS backend actually...)
      // but we exclusive lock uploaders to serialize them!
      // And the LS has to take care of whatever stricter locking it has to use or not
      // Think: RDBMS LS or some trickier LS implementations for example
      final RepositoryItemUidLock uidLock = uid.getLock();

      uidLock.lock(Action.read);

      try {
        // store it
        getLocalStorage().storeItem(this, item);
      }
      finally {
        uidLock.unlock();
      }
    }
    finally {
      uidUploaderLock.unlock();
    }

    // remove the "request" item from n-cache if there
    removeFromNotFoundCache(item.getResourceStoreRequest());

    if (Action.create.equals(action)) {
      eventBus().post(new RepositoryItemEventStoreCreate(this, item));
    }
    else {
      eventBus().post(new RepositoryItemEventStoreUpdate(this, item));
    }
  }

  @Override
  public Collection<StorageItem> list(boolean fromTask, ResourceStoreRequest request)
      throws IllegalOperationException, ItemNotFoundException, StorageException
  {
    if (log.isDebugEnabled()) {
      log.debug(getId() + ".list() :: " + request.toString());
    }

    if (!getLocalStatus().shouldServiceRequest()) {
      throw new RepositoryNotAvailableException(this);
    }

    request.addProcessedRepository(getId());

    StorageItem item = retrieveItem(fromTask, request);

    if (item instanceof StorageCollectionItem) {
      return list(fromTask, (StorageCollectionItem) item);
    }
    else {
      throw new ItemNotFoundException(reasonFor(request, this, "Path %s in repository %s is not a collection",
          request.getRequestPath(), this));
    }
  }

  @Override
  public Collection<StorageItem> list(boolean fromTask, StorageCollectionItem coll)
      throws IllegalOperationException, ItemNotFoundException, StorageException
  {
    if (log.isDebugEnabled()) {
      log.debug(getId() + ".list() :: " + coll.getRepositoryItemUid().toString());
    }

    if (!getLocalStatus().shouldServiceRequest()) {
      throw new RepositoryNotAvailableException(this);
    }

    maintainNotFoundCache(coll.getResourceStoreRequest());

    Collection<StorageItem> items = doListItems(new ResourceStoreRequest(coll));

    return items;
  }

  @Override
  public RepositoryItemUid createUid(final String path) {
    return getRepositoryItemUidFactory().createUid(this, path);
  }

  @Override
  public RepositoryItemUidAttributeManager getRepositoryItemUidAttributeManager() {
    return repositoryItemUidAttributeManager;
  }

  // ===================================================================================
  // Inner stuff

  /**
   * Maintains not found cache.
   *
   * @throws ItemNotFoundException the item not found exception
   */
  @Override
  public void maintainNotFoundCache(ResourceStoreRequest request)
      throws ItemNotFoundException
  {
    // NEXUS-6177: skip NFC if request is "asExpired"
    // On outcome, if remotely found, will invalidate NFC by caching it
    if (isNotFoundCacheActive() && !request.isRequestAsExpired()) {
      if (getNotFoundCache().contains(request.getRequestPath())) {
        if (getNotFoundCache().isExpired(request.getRequestPath())) {
          if (log.isDebugEnabled()) {
            log.debug("The path " + request.getRequestPath() + " is in NFC but expired.");
          }

          removeFromNotFoundCache(request);
        }
        else {
          StringBuilder sb = new StringBuilder("The path ").append(request.getRequestPath()).append(" is cached ");
          long expirationTime = getNotFoundCache().getExpirationTime(request.getRequestPath());
          if (expirationTime > 0) {
            sb.append("until ").append(new DateTime(expirationTime)).append(" ");
          }
          final String message = sb.append("as not found in repository ").append(this).toString();

          log.debug(message);
          throw new ItemNotFoundException(reasonFor(request, this, message));
        }
      }
    }
  }

  /**
   * Adds the uid to not found cache.
   */
  @Override
  public void addToNotFoundCache(ResourceStoreRequest request) {
    if (isNotFoundCacheActive()) {
      if (log.isDebugEnabled()) {
        log.debug("Adding path " + request.getRequestPath() + " to NFC.");
      }

      getNotFoundCache().put(request.getRequestPath(), Boolean.TRUE, getNotFoundCacheTimeToLive() * 60);
    }
  }

  /**
   * Removes the uid from not found cache.
   */
  @Override
  public void removeFromNotFoundCache(ResourceStoreRequest request) {
    if (isNotFoundCacheActive()) {
      if (log.isDebugEnabled()) {
        log.debug("Removing path " + request.getRequestPath() + " from NFC.");
      }

      getNotFoundCache().removeWithParents(request.getRequestPath());
    }
  }

  /**
   * Check conditions, such as availability, permissions, etc.
   *
   * @param request the request
   * @throws RepositoryNotAvailableException
   *                               the repository not available exception
   * @throws AccessDeniedException the access denied exception
   */
  protected void checkConditions(ResourceStoreRequest request, Action action)
      throws ItemNotFoundException, IllegalOperationException, AccessDeniedException
  {
    if (!this.getLocalStatus().shouldServiceRequest()) {
      throw new RepositoryNotAvailableException(this);
    }

    // check for writing to read only repo
    // Readonly is ALWAYS read only
    if (RepositoryWritePolicy.READ_ONLY.equals(getWritePolicy()) && !isActionAllowedReadOnly(action)) {
      throw new IllegalRequestException(request, "Repository with ID='" + getId()
          + "' is Read Only, but action was '" + action.toString() + "'!");
    }
    // but Write/write once may need to allow updating metadata
    // check the write policy
    enforceWritePolicy(request, action);

    // NXCM-3600: this if was an old remnant, is not needed
    // if ( isExposed() )
    // {
    getAccessManager().decide(this, request, action);
    // }

    checkRequestStrategies(request, action);
  }

  protected void checkRequestStrategies(final ResourceStoreRequest request, final Action action)
      throws ItemNotFoundException, IllegalOperationException
  {
    final Map<String, RequestStrategy> effectiveRequestStrategies = getRegisteredStrategies();
    for (RequestStrategy strategy : effectiveRequestStrategies.values()) {
      strategy.onHandle(this, request, action);
    }
  }

  protected void checkPostConditions(final ResourceStoreRequest request, final StorageItem item)
      throws IllegalOperationException, ItemNotFoundException
  {
    final Map<String, RequestStrategy> effectiveRequestStrategies = getRegisteredStrategies();
    for (RequestStrategy strategy : effectiveRequestStrategies.values()) {
      strategy.onServing(this, request, item);
    }
  }

  protected void enforceWritePolicy(ResourceStoreRequest request, Action action)
      throws IllegalRequestException
  {
    // check for write once (no redeploy)
    if (Action.update.equals(action) && !RepositoryWritePolicy.ALLOW_WRITE.equals(this.getWritePolicy())) {
      throw new IllegalRequestException(request, "Repository with ID='" + getId()
          + "' does not allow updating artifacts.");
    }
  }

  @Override
  public boolean isCompatible(Repository repository) {
    return getRepositoryContentClass().isCompatible(repository.getRepositoryContentClass());
  }

  protected void doDeleteItem(ResourceStoreRequest request)
      throws UnsupportedStorageOperationException, ItemNotFoundException, StorageException
  {
    getLocalStorage().deleteItem(this, request);
  }

  protected Collection<StorageItem> doListItems(ResourceStoreRequest request)
      throws ItemNotFoundException, StorageException
  {
    return getLocalStorage().listItems(this, request);
  }

  protected StorageItem doRetrieveItem(ResourceStoreRequest request)
      throws IllegalOperationException, ItemNotFoundException, StorageException
  {
    return doRetrieveLocalItem(request);
  }

  protected AbstractStorageItem doRetrieveLocalItem(final ResourceStoreRequest request)
      throws ItemNotFoundException, LocalStorageException
  {
    AbstractStorageItem localItem = null;
    try {
      localItem = getLocalStorage().retrieveItem(this, request);
      if (localItem instanceof StorageFileItem) {
        StorageFileItem file = (StorageFileItem) localItem;
        // wrap the content locator if needed
        if (!(file.getContentLocator() instanceof ReadLockingContentLocator)) {
          final RepositoryItemUid uid = createUid(request.getRequestPath());
          file.setContentLocator(new ReadLockingContentLocator(uid, file.getContentLocator()));
        }
      }
      if (log.isDebugEnabled()) {
        log.debug("Item " + request.toString() + " found in local storage.");
      }
    }
    catch (ItemNotFoundException ex) {
      if (log.isDebugEnabled()) {
        log.debug("Item " + request.toString() + " not found in local storage.");
      }
      throw ex;
    }

    return localItem;
  }

  protected boolean isActionAllowedReadOnly(Action action) {
    return action.isReadAction();
  }

  /**
   * Whether or not the requested path should be added to NFC. Item will be added to NFC if is not local/remote only.
   *
   * @param request resource store request
   * @return true if requested path should be added to NFC
   * @since 2.0
   */
  protected boolean shouldAddToNotFoundCache(final ResourceStoreRequest request) {
    // if not local/remote only, add it to NFC
    return !request.isRequestLocalOnly() && !request.isRequestRemoteOnly();
  }
}
TOP

Related Classes of org.sonatype.nexus.proxy.repository.AbstractRepository

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.