/**
 * Copyright (C) 2011-2018 Red Hat, Inc. (https://github.com/Commonjava/indy)
 *
 * 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 org.commonjava.indy.core.content;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.commonjava.indy.IndyWorkflowException;
import org.commonjava.indy.conf.IndyConfiguration;
import org.commonjava.indy.content.ContentDigester;
import org.commonjava.indy.content.ContentGenerator;
import org.commonjava.indy.content.ContentManager;
import org.commonjava.indy.content.DownloadManager;
import org.commonjava.indy.content.StoreResource;
import org.commonjava.indy.data.IndyDataException;
import org.commonjava.indy.data.StoreDataManager;
import org.commonjava.indy.measure.annotation.Measure;
import org.commonjava.indy.measure.annotation.MetricNamed;
import org.commonjava.indy.model.core.ArtifactStore;
import org.commonjava.indy.model.core.Group;
import org.commonjava.indy.model.core.StoreKey;
import org.commonjava.indy.model.core.io.IndyObjectMapper;
import org.commonjava.indy.model.galley.KeyedLocation;
import org.commonjava.indy.util.ApplicationStatus;
import org.commonjava.indy.util.LocationUtils;
import org.commonjava.maven.galley.TransferManager;
import org.commonjava.maven.galley.event.EventMetadata;
import org.commonjava.maven.galley.model.ConcreteResource;
import org.commonjava.maven.galley.model.SpecialPathInfo;
import org.commonjava.maven.galley.model.Transfer;
import org.commonjava.maven.galley.model.TransferOperation;
import org.commonjava.maven.galley.spi.io.SpecialPathManager;
import org.commonjava.maven.galley.spi.nfc.NotFoundCache;
import org.commonjava.maven.galley.transport.htcli.model.HttpExchangeMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static org.commonjava.indy.IndyContentConstants.CHECK_CACHE_ONLY;
import static org.commonjava.indy.measure.annotation.MetricNamed.DEFAULT;
import static org.commonjava.indy.model.core.StoreType.group;
import static org.commonjava.indy.model.core.StoreType.hosted;
import static org.commonjava.indy.util.ContentUtils.dedupeListing;
import static org.commonjava.maven.galley.io.SpecialPathConstants.HTTP_METADATA_EXT;

public class DefaultContentManager
        implements ContentManager
{

    private final Logger logger = LoggerFactory.getLogger( getClass() );

    @Inject
    private Instance<ContentGenerator> contentProducerInstances;

    private Set<ContentGenerator> contentGenerators;

    @Inject
    private StoreDataManager storeManager;

    @Inject
    private DownloadManager downloadManager;

    @Inject
    private SpecialPathManager specialPathManager;

    @Inject
    private NotFoundCache nfc;

    @Inject
    private IndyObjectMapper mapper;

    @Inject
    private ContentDigester contentDigester;

    @Inject
    private IndyConfiguration indyConfig;

    protected DefaultContentManager()
    {
    }

    public DefaultContentManager( final StoreDataManager storeManager, final DownloadManager downloadManager,
                                  final IndyObjectMapper mapper, final SpecialPathManager specialPathManager,
                                  final NotFoundCache nfc, final ContentDigester contentDigester, final Set<ContentGenerator> contentProducers )
    {
        this.storeManager = storeManager;
        this.downloadManager = downloadManager;
        this.mapper = mapper;
        this.specialPathManager = specialPathManager;
        this.nfc = nfc;
        this.contentDigester = contentDigester;
        this.contentGenerators = contentProducers == null ? new HashSet<>() : contentProducers;
    }

    @PostConstruct
    public void initialize()
    {
        contentGenerators = new HashSet<>();
        if ( contentProducerInstances != null )
        {
            for ( final ContentGenerator producer : contentProducerInstances )
            {
                contentGenerators.add( producer );
            }
        }
    }

    @Override
    public Transfer retrieveFirst( final List<? extends ArtifactStore> stores, final String path )
            throws IndyWorkflowException
    {
        return retrieveFirst( stores, path, new EventMetadata() );
    }

    @Override
    public Transfer retrieveFirst( final List<? extends ArtifactStore> stores, final String path,
                                   final EventMetadata eventMetadata )
            throws IndyWorkflowException
    {
        Transfer txfr = null;
        for ( final ArtifactStore store : stores )
        {
            txfr = doRetrieve( store, path, eventMetadata );
            if ( txfr != null )
            {
                break;
            }
        }

        return txfr;
    }

    @Override
    public List<Transfer> retrieveAll( final List<? extends ArtifactStore> stores, final String path )
            throws IndyWorkflowException
    {
        return retrieveAll( stores, path, new EventMetadata() );
    }

    @Override
    public List<Transfer> retrieveAll( final List<? extends ArtifactStore> stores, final String path,
                                       final EventMetadata eventMetadata )
            throws IndyWorkflowException
    {
        final List<Transfer> txfrs = new ArrayList<>();
        for ( final ArtifactStore store : stores )
        {
            if ( group == store.getKey().getType() )
            {
                List<ArtifactStore> members;
                try
                {
                    members = storeManager.query()
                                          .packageType( store.getPackageType() )
                                          .enabledState( true )
                                          .getOrderedConcreteStoresInGroup( store.getName() );
                }
                catch ( final IndyDataException e )
                {
                    throw new IndyWorkflowException( "Failed to lookup concrete members of: %s. Reason: %s", e, store,
                                                      e.getMessage() );
                }

                final List<Transfer> storeTransfers = new ArrayList<>();
                for ( final ContentGenerator generator : contentGenerators )
                {
                    final Transfer txfr =
                            generator.generateGroupFileContent( (Group) store, members, path, eventMetadata );
                    if ( txfr != null )
                    {
                        storeTransfers.add( txfr );
                    }
                }

                // If the content was generated, don't try to retrieve it from a member store...this is the lone exception to retrieveAll
                // ...if it's generated, it's merged in this case.
                if ( storeTransfers.isEmpty() )
                {
                    for ( final ArtifactStore member : members )
                    {
                        // NOTE: This is only safe to call because we're concrete ordered stores, so anything passing through here is concrete.
                        final Transfer txfr = doRetrieve( member, path, eventMetadata );
                        if ( txfr != null )
                        {
                            storeTransfers.add( txfr );
                        }
                    }
                }

                txfrs.addAll( storeTransfers );
            }
            else
            {
                // NOTE: This is only safe to call because we're doing the group check up front, so anything passing through here is concrete.
                final Transfer txfr = doRetrieve( store, path, eventMetadata );
                if ( txfr != null )
                {
                    txfrs.add( txfr );
                }
            }
        }

        return txfrs;
    }

    @Override
    public Transfer retrieve( final ArtifactStore store, final String path )
            throws IndyWorkflowException
    {
        return retrieve( store, path, new EventMetadata() );
    }

    @Override
    @Measure( timers = @MetricNamed( DEFAULT ), exceptions = @MetricNamed( DEFAULT ) )
    public Transfer retrieve( final ArtifactStore store, final String path, final EventMetadata eventMetadata )
            throws IndyWorkflowException
    {
        Transfer item;
        if ( group == store.getKey().getType() )
        {
            List<ArtifactStore> members;
            try
            {
                members = storeManager.query()
                                      .packageType( store.getPackageType() )
                                      .enabledState( true )
                                      .getOrderedConcreteStoresInGroup( store.getName() );
            }
            catch ( final IndyDataException e )
            {
                throw new IndyWorkflowException( "Failed to lookup concrete members of: %s. Reason: %s", e, store,
                                                  e.getMessage() );
            }

            if ( logger.isTraceEnabled() )
            {
                logger.trace( "{} is a group. Attempting downloads from (in order):\n  {}", store.getKey(), StringUtils.join(members, "\n  ") );
            }

            item = null;
            boolean generated = false;
            for ( final ContentGenerator generator : contentGenerators )
            {
                if ( generator.canProcess( path ) )
                {
                    item = generator.generateGroupFileContent( (Group) store, members, path, eventMetadata );
                    logger.trace( "From content {}.generateGroupFileContent: {} (exists? {})",
                                  generator.getClass().getSimpleName(), item, item != null && item.exists() );
                    generated = true;
                    break;
                }
            }

            if ( !generated )
            {
                if ( PathMaskChecker.checkMask( store, path ) )
                {
                    for ( final ArtifactStore member : members )
                    {
                        try
                        {
                            item = doRetrieve( member, path, eventMetadata );
                        }
                        catch ( IndyWorkflowException e )
                        {
                            logger.error( "Failed to retrieve artifact from for path {} from {} in group {}, error is: {}",
                                          path, member, store, e.getMessage() );
                        }
                        if ( item != null )
                        {
                            // get the item from the first member store
                            break;
                        }
                    }
                }
            }
        }
        else
        {
            item = doRetrieve( store, path, eventMetadata );
        }

        logger.info( "Returning transfer {} from {}", item, store.getKey() );

        return item;
    }

    private Transfer doRetrieve( final ArtifactStore store, final String path, final EventMetadata eventMetadata )
            throws IndyWorkflowException
    {
        logger.trace( "Attempting to retrieve: {} from: {}", path, store.getKey() );

        if ( store.isDisabled() )
        {
            logger.debug( "Content not available in repository due to store disabled for {}, path is {}", store,
                         path );
            return null;
        }

        Transfer item = null;
        try
        {
            item = downloadManager.retrieve( store, path, eventMetadata );

            if ( item == null )
            {
                for ( final ContentGenerator generator : contentGenerators )
                {
                    logger.trace( "Attempting to generate content for path: {} in: {} via: {}", path, store,
                                  generator );
                    item = generator.generateFileContent( store, path, eventMetadata );
                    if ( item != null )
                    {
                        logger.debug( "Resource generated for {}, clean NFC and delete obsolete http-metadata.json", item.getResource() );
                        nfc.clearMissing( item.getResource() );
                        Transfer httpMeta = item.getSiblingMeta( HTTP_METADATA_EXT );
                        try
                        {
                            httpMeta.delete();
                        }
                        catch ( IOException e )
                        {
                            logger.warn( "Failed to delete {}", httpMeta.getResource() );
                        }
                        break;
                    }
                }
            }
        }
        catch ( IndyWorkflowException e )
        {
            e.filterLocationErrors();
        }

        return item;
    }

    @Override
    public Transfer store( final ArtifactStore store, final String path, final InputStream stream,
                           final TransferOperation op )
            throws IndyWorkflowException
    {
        return store( store, path, stream, op, new EventMetadata() );
    }

    @Override
    @Measure( timers = @MetricNamed( DEFAULT ), exceptions = @MetricNamed( DEFAULT ) )
    public Transfer store( final ArtifactStore store, final String path, final InputStream stream,
                           final TransferOperation op, final EventMetadata eventMetadata )
            throws IndyWorkflowException
    {
        if ( group == store.getKey().getType() )
        {
            try
            {
                final List<ArtifactStore> allMembers = storeManager.query()
                                      .packageType( store.getPackageType() )
                                      .enabledState( true )
                                      .getOrderedConcreteStoresInGroup( store.getName() );

                final Transfer txfr = store( allMembers, store.getKey(), path, stream, op, eventMetadata );
                logger.debug( "Stored: {} for group: {} in: {}", path, store.getKey(), txfr );
                return txfr;
            }
            catch ( final IndyDataException e )
            {
                throw new IndyWorkflowException( "Failed to lookup concrete members of: %s. Reason: %s", e, store,
                                                  e.getMessage() );
            }
        }

        logger.debug( "Storing: {} for: {} with event metadata: {}", path, store.getKey(), eventMetadata );
        final Transfer txfr = downloadManager.store( store, path, stream, op, eventMetadata );
        if ( txfr != null )
        {
            final KeyedLocation kl = (KeyedLocation) txfr.getLocation();
            ArtifactStore transferStore;
            try
            {
                transferStore = storeManager.getArtifactStore( kl.getKey() );
            }
            catch ( final IndyDataException e )
            {
                throw new IndyWorkflowException( "Failed to lookup store: %s. Reason: %s", e, kl.getKey(),
                                                  e.getMessage() );
            }

            for ( final ContentGenerator generator : contentGenerators )
            {
                generator.handleContentStorage( transferStore, path, txfr, eventMetadata );
            }

            if ( !store.equals( transferStore ) )
            {
                for ( final ContentGenerator generator : contentGenerators )
                {
                    generator.handleContentStorage( transferStore, path, txfr, eventMetadata );
                }
            }

            clearNFCEntries(kl, path);
        }

        return txfr;
    }

    private void clearNFCEntries( final KeyedLocation kl, final String path )
    {
        try
        {
            storeManager.query()
                        .getGroupsAffectedBy( kl.getKey() )
                        .stream()
                        .map( ( g ) -> new ConcreteResource( LocationUtils.toLocation( g ), path ) )
                        .forEach( ( cr ) -> nfc.clearMissing( cr ) );

            nfc.clearMissing( new ConcreteResource( kl, path ) );
        }
        catch ( IndyDataException e )
        {
            logger.error( String.format("Failed to clear NFC entries affected by upload of: %s to: %s. Reason: %s", path,
                                        kl.getKey(), e.getMessage()), e );
        }
    }

    //    @Override
//    public Transfer store( final List<? extends ArtifactStore> stores, final String path, final InputStream stream,
//                           final TransferOperation op )
//            throws IndyWorkflowException
//    {
//        return store( stores, path, stream, op, new EventMetadata() );
//    }

    @Override
    public Transfer store( final List<? extends ArtifactStore> stores, final StoreKey topKey, final String path, final InputStream stream,
                           final TransferOperation op, final EventMetadata eventMetadata )
            throws IndyWorkflowException
    {
        logger.debug( "Storing: {} in: {} with event metadata: {}", path, stores, eventMetadata );
        final Transfer txfr = downloadManager.store( stores, path, stream, op, eventMetadata );
        if ( txfr != null )
        {
            final KeyedLocation kl = (KeyedLocation) txfr.getLocation();
            ArtifactStore transferStore;
            try
            {
                transferStore = storeManager.getArtifactStore( kl.getKey() );
            }
            catch ( final IndyDataException e )
            {
                throw new IndyWorkflowException( "Failed to lookup store: %s. Reason: %s", e, kl.getKey(),
                                                  e.getMessage() );
            }

            for ( final ContentGenerator generator : contentGenerators )
            {
                logger.debug( "{} Handling content storage of: {} in: {}", generator, path, transferStore.getKey() );
                generator.handleContentStorage( transferStore, path, txfr, eventMetadata );
            }

            clearNFCEntries(kl, path);
        }

        return txfr;
    }

    @Override
    public boolean delete( final ArtifactStore store, final String path )
            throws IndyWorkflowException
    {
        return delete( store, path, new EventMetadata() );
    }

    @Override
    @Measure( timers = @MetricNamed( DEFAULT ), exceptions = @MetricNamed( DEFAULT ) )
    public boolean delete( final ArtifactStore store, final String path, final EventMetadata eventMetadata )
            throws IndyWorkflowException
    {
        if ( Boolean.TRUE.equals( eventMetadata.get( CHECK_CACHE_ONLY ) ) )
        {
            return deleteCache( store, path, eventMetadata );
        }

        boolean result = false;
        if ( group == store.getKey().getType() )
        {
            List<ArtifactStore> members;
            try
            {
                members = storeManager.query()
                                      .packageType( store.getPackageType() )
                                      .enabledState( true )
                                      .getOrderedConcreteStoresInGroup( store.getName() );
            }
            catch ( final IndyDataException e )
            {
                throw new IndyWorkflowException( "Failed to lookup concrete members of: %s. Reason: %s", e, store,
                                                  e.getMessage() );
            }

            for ( final ArtifactStore member : members )
            {
                if ( downloadManager.delete( member, path, eventMetadata ) )
                {
                    result = true;
                    for ( final ContentGenerator generator : contentGenerators )
                    {
                        generator.handleContentDeletion( member, path, eventMetadata );
                    }
                }
            }

            if ( result )
            {
                for ( final ContentGenerator generator : contentGenerators )
                {
                    generator.handleContentDeletion( store, path, eventMetadata );
                }
            }
        }
        else
        {
            if ( downloadManager.delete( store, path, eventMetadata ) )
            {
                result = true;
                for ( final ContentGenerator generator : contentGenerators )
                {
                    generator.handleContentDeletion( store, path, eventMetadata );
                }
            }
        }

        return result;
    }

    /**
     * clean just the cache (storage of groups and remote repos)
     */
    private boolean deleteCache( ArtifactStore store, String path, EventMetadata eventMetadata )
                    throws IndyWorkflowException
    {
        logger.trace( "Delete cache, store:{}, path:{}", store.getName(), path );
        if ( hosted == store.getKey().getType() )
        {
            return false;
        }
        else
        {
            return downloadManager.delete( store, path, eventMetadata );
        }
    }

    @Override
    public boolean deleteAll( final List<? extends ArtifactStore> stores, final String path )
            throws IndyWorkflowException
    {
        return deleteAll( stores, path, new EventMetadata() );
    }

    @Override
    public boolean deleteAll( final List<? extends ArtifactStore> stores, final String path,
                              final EventMetadata eventMetadata )
            throws IndyWorkflowException
    {
        boolean result = false;
        for ( final ArtifactStore store : stores )
        {
            result = delete( store, path, eventMetadata ) || result;
        }

        return result;
    }

    @Override
    public void rescan( final ArtifactStore store )
            throws IndyWorkflowException
    {
        rescan( store, new EventMetadata() );
    }

    @Override
    public void rescan( final ArtifactStore store, final EventMetadata eventMetadata )
            throws IndyWorkflowException
    {
        downloadManager.rescan( store, eventMetadata );
    }

    @Override
    public void rescanAll( final List<? extends ArtifactStore> stores )
            throws IndyWorkflowException
    {
        rescanAll( stores, new EventMetadata() );
    }

    @Override
    public void rescanAll( final List<? extends ArtifactStore> stores, final EventMetadata eventMetadata )
            throws IndyWorkflowException
    {
        downloadManager.rescanAll( stores, eventMetadata );
    }

    @Override
    public List<StoreResource> list( final ArtifactStore store, final String path )
            throws IndyWorkflowException
    {
        return list( store, path, new EventMetadata() );
    }

    @Override
    @Measure( timers = @MetricNamed( DEFAULT ), exceptions = @MetricNamed( DEFAULT ) )
    public List<StoreResource> list( final ArtifactStore store, final String path, final EventMetadata eventMetadata )
            throws IndyWorkflowException
    {
        final EventMetadata metadata = setAllowRemoteDownloading(eventMetadata);

        List<StoreResource> listed;
        if ( group == store.getKey().getType() )
        {
            List<ArtifactStore> members;
            try
            {
                members = storeManager.query()
                                      .packageType( store.getPackageType() )
                                      .enabledState( true )
                                      .getOrderedConcreteStoresInGroup( store.getName() );
            }
            catch ( final IndyDataException e )
            {
                throw new IndyWorkflowException( "Failed to lookup concrete members of: %s. Reason: %s", e, store,
                                                  e.getMessage() );
            }

            listed = new ArrayList<>();
            for ( final ContentGenerator generator : contentGenerators )
            {
                final List<StoreResource> generated =
                        generator.generateGroupDirectoryContent( (Group) store, members, path, eventMetadata );
                if ( generated != null )
                {
                    listed.addAll( generated );
                }
            }

            for ( final ArtifactStore member : members )
            {
                List<StoreResource> storeListing = null;
                try
                {
                    storeListing = list( member, path, metadata );
                }
                catch ( IndyWorkflowException e )
                {
                    e.filterLocationErrors();
                }

                if ( storeListing != null )
                {
                    listed.addAll( storeListing );
                }
            }
        }
        else
        {
            listed = downloadManager.list( store, path, metadata );

            for ( final ContentGenerator producer : contentGenerators )
            {
                final List<StoreResource> produced =
                        producer.generateDirectoryContent( store, path, listed, metadata );
                if ( produced != null )
                {
                    listed.addAll( produced );
                }
            }
        }

        return dedupeListing( listed );
    }

    private EventMetadata setAllowRemoteDownloading( final EventMetadata eventMetadata )
    {
        if ( eventMetadata == null )
        {
            return new EventMetadata().set( TransferManager.ALLOW_REMOTE_LISTING_DOWNLOAD,
                                            indyConfig.isAllowRemoteListDownload() );
        }
        else if ( eventMetadata.get( TransferManager.ALLOW_REMOTE_LISTING_DOWNLOAD ) == null )
        {
            return eventMetadata.set( TransferManager.ALLOW_REMOTE_LISTING_DOWNLOAD,
                                      indyConfig.isAllowRemoteListDownload() );
        }
        else
        {
            return eventMetadata;
        }
    }

    @Override
    public List<StoreResource> list( final List<? extends ArtifactStore> stores, final String path )
            throws IndyWorkflowException
    {
        final List<StoreResource> listed = new ArrayList<>();
        for ( final ArtifactStore store : stores )
        {
            List<StoreResource> storeListing = null;
            try
            {
                storeListing = list( store, path, new EventMetadata() );
            }
            catch ( IndyWorkflowException e )
            {
                e.filterLocationErrors();
            }

            if ( storeListing != null )
            {
                listed.addAll( storeListing );
            }
        }

        return dedupeListing( listed );
    }

    @Override
    public Transfer getTransfer( final StoreKey store, final String path, final TransferOperation op )
            throws IndyWorkflowException
    {
        try
        {
            return getTransfer( storeManager.getArtifactStore( store ), path, op );
        }
        catch ( final IndyDataException e )
        {
            throw new IndyWorkflowException( ApplicationStatus.BAD_REQUEST.code(),
                                              "Failed to retrieve ArtifactStore for key: %s. Reason: %s", e, store,
                                              e.getMessage() );

        }
    }

    @Override
    public Transfer getTransfer( final ArtifactStore store, final String path, final TransferOperation op )
            throws IndyWorkflowException
    {
        logger.trace( "Getting transfer for: {}/{} (op: {})", store.getKey(), path, op );
        if ( group == store.getKey().getType() )
        {
            KeyedLocation location = LocationUtils.toLocation( store );
            SpecialPathInfo spInfo = specialPathManager.getSpecialPathInfo( location, path, store.getPackageType() );
            if ( spInfo == null || !spInfo.isMergable() )
            {
                try
                {
                    final List<ArtifactStore> allMembers = storeManager.query()
                                          .packageType( store.getPackageType() )
                                          .enabledState( true )
                                          .getOrderedConcreteStoresInGroup( store.getName() );

                    logger.trace( "Trying to retrieve suitable transfer for: {} in group: {}", path, store.getName() );
                    logger.trace( "Members in group {}: {}", store.getName(), allMembers );

                    return getTransfer( allMembers, path, op );
                }
                catch ( final IndyDataException e )
                {
                    throw new IndyWorkflowException( "Failed to lookup concrete members of: %s. Reason: %s", e, store,
                                                      e.getMessage() );
                }
            }
            else
            {
                logger.trace( "Detected mergable special path: {}/{}.", store.getKey(), path );
            }
        }

        logger.trace( "Retrieving storage reference (Transfer) directly for: {}/{}", store.getKey(), path );
        return downloadManager.getStorageReference( store, path, op );
    }

    @Override
    public Transfer getTransfer( final List<ArtifactStore> stores, final String path, final TransferOperation op )
            throws IndyWorkflowException
    {
        Logger logger = LoggerFactory.getLogger( getClass() );
        logger.trace( "Looking for path: '{}' in stores: {}", path,
                      stores.stream().map( ArtifactStore::getKey ).collect( Collectors.toList() ) );

        return downloadManager.getStorageReference( stores, path, op );
    }

    @Override
    public HttpExchangeMetadata getHttpMetadata( final Transfer txfr )
            throws IndyWorkflowException
    {
        final Transfer meta = txfr.getSiblingMeta( HttpExchangeMetadata.FILE_EXTENSION );
        return readExchangeMetadata( meta );
    }

    @Override
    public HttpExchangeMetadata getHttpMetadata( final StoreKey key, final String path )
            throws IndyWorkflowException
    {
        Transfer transfer = getTransfer( key, path, TransferOperation.DOWNLOAD );
        if ( transfer != null && transfer.exists() )
        {
            Transfer meta = transfer.getSiblingMeta( HttpExchangeMetadata.FILE_EXTENSION );
            if ( meta != null && meta.exists() )
            {
                return readExchangeMetadata( meta );
            }
        }

        return null;
    }

    @Override
    // TODO: to add content generation handling here, for things like merged metadata, checksum files, etc.
    public boolean exists(ArtifactStore store, String path)
        throws IndyWorkflowException
    {
        Logger logger = LoggerFactory.getLogger( getClass() );
        logger.trace( "Checking existence of: {} in: {}", path, store.getKey() );
        if ( store instanceof Group )
        {
            try
            {
                final List<ArtifactStore> allMembers = storeManager.query()
                                      .packageType( store.getPackageType() )
                                      .enabledState( true )
                                      .getOrderedConcreteStoresInGroup( store.getName() );

                logger.trace( "Trying to retrieve suitable transfer for: {} in group: {}", path, store.getName() );
                logger.trace( "Members in group {}: {}", store.getName(), allMembers );

                for ( ArtifactStore member : allMembers )
                {
                    if ( exists( member, path ) )
                    {
                        return true;
                    }
                }

                return false;
            }
            catch ( final IndyDataException e )
            {
                throw new IndyWorkflowException( "Failed to lookup concrete members of: %s. Reason: %s", e, store,
                                                 e.getMessage() );
            }
        }
        else
        {
            return downloadManager.exists(store, path);
        }
    }

    private HttpExchangeMetadata readExchangeMetadata( final Transfer meta )
            throws IndyWorkflowException
    {
        logger.trace( "Reading HTTP exchange metadata from: {}", meta );
        if ( meta != null && meta.exists() )
        {
            try(InputStream stream = meta.openInputStream( false ))
            {
                String raw = IOUtils.toString( stream );
                logger.trace( "HTTP Metadata string is:\n\n{}\n\n", raw );

                return mapper.readValue( raw, HttpExchangeMetadata.class );
            }
            catch ( final IOException e )
            {
                throw new IndyWorkflowException( "HTTP exchange metadata appears to be damaged: %s. Reason: %s", e,
                                                  meta, e.getMessage() );
            }
        }
        else
        {
            logger.trace( "Cannot read HTTP exchange: {}. Transfer is missing!", meta );
        }

        return null;
    }

}


/**
 * Copyright (C) 2011-2018 Red Hat, Inc. (https://github.com/Commonjava/indy)
 *
 * 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 org.commonjava.indy.core.bind.jaxrs.admin;

import static javax.ws.rs.core.Response.Status.CONFLICT;
import static javax.ws.rs.core.Response.noContent;
import static javax.ws.rs.core.Response.notModified;
import static javax.ws.rs.core.Response.ok;
import static javax.ws.rs.core.Response.status;
import static org.apache.commons.lang.StringUtils.isEmpty;
import static org.commonjava.indy.bind.jaxrs.util.ResponseUtils.formatCreatedResponseWithJsonEntity;
import static org.commonjava.indy.bind.jaxrs.util.ResponseUtils.formatOkResponseWithJsonEntity;
import static org.commonjava.indy.bind.jaxrs.util.ResponseUtils.formatResponse;
import static org.commonjava.indy.model.core.ArtifactStore.METADATA_CHANGELOG;
import static org.commonjava.indy.util.ApplicationContent.application_json;

import java.io.IOException;
import java.net.URI;
import java.util.List;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HEAD;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriInfo;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.apache.commons.io.IOUtils;
import org.commonjava.indy.IndyWorkflowException;
import org.commonjava.indy.bind.jaxrs.IndyResources;
import org.commonjava.indy.bind.jaxrs.SecurityManager;
import org.commonjava.indy.bind.jaxrs.util.ResponseUtils;
import org.commonjava.indy.core.ctl.AdminController;
import org.commonjava.indy.model.core.ArtifactStore;
import org.commonjava.indy.model.core.RemoteRepository;
import org.commonjava.indy.model.core.StoreKey;
import org.commonjava.indy.model.core.StoreType;
import org.commonjava.indy.model.core.dto.StoreListingDTO;
import org.commonjava.indy.model.core.io.IndyObjectMapper;
import org.commonjava.indy.util.ApplicationContent;
import org.commonjava.atlas.maven.ident.util.JoinString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Api( description = "Resource for accessing and managing artifact store definitions", value = "Store Administration" )
@Path( "/api/admin/stores/{packageType}/{type: (hosted|group|remote)}" )
@ApplicationScoped
public class StoreAdminHandler
    implements IndyResources
{

    private final Logger logger = LoggerFactory.getLogger( getClass() );

    @Inject
    private AdminController adminController;

    @Inject
    private IndyObjectMapper objectMapper;

    @Inject
    private SecurityManager securityManager;

    public StoreAdminHandler()
    {
        logger.info( "\n\n\n\nStarted StoreAdminHandler\n\n\n\n" );
    }

    //    @Context
    //    private UriInfo uriInfo;
    //
    //    @Context
    //    private HttpServletRequest request;

    @ApiOperation( "Check if a given store exists" )
    @ApiResponses( { @ApiResponse( code = 200, message = "The store exists" ),
                           @ApiResponse( code = 404, message = "The store doesn't exist" ) } )
    @Path( "/{name}" )
    @HEAD
    public Response exists( final @PathParam( "packageType" ) String packageType,
                            final @ApiParam( allowableValues = "hosted,group,remote", required = true )
                            @PathParam( "type" ) String type,
                            @ApiParam( required = true ) @PathParam( "name" ) final String name )
    {
        Response response;
        final StoreType st = StoreType.get( type );

        logger.info( "Checking for existence of: {}:{}:{}", packageType, st, name );

        if ( adminController.exists( new StoreKey( packageType, st, name ) ) )
        {

            logger.info( "returning OK" );
            response = Response.ok().build();
        }
        else
        {
            logger.info( "Returning NOT FOUND" );
            response = Response.status( Status.NOT_FOUND ).build();
        }
        return response;
    }

    @ApiOperation( "Create a new store" )
    @ApiResponses( { @ApiResponse( code = 201, response = ArtifactStore.class, message = "The store was created" ),
        @ApiResponse( code = 409, message = "A store with the specified type and name already exists" ) } )
    @ApiImplicitParams( { @ApiImplicitParam( allowMultiple = false, paramType = "body", name = "body", required = true, dataType = "org.commonjava.indy.model.core.ArtifactStore", value = "The artifact store definition JSON" ) } )
    @POST
    @Consumes( ApplicationContent.application_json )
    @Produces( ApplicationContent.application_json )
    public Response create( final @PathParam( "packageType" ) String packageType,
                            final @ApiParam( allowableValues = "hosted,group,remote", required = true ) @PathParam( "type" ) String type,
                            final @Context UriInfo uriInfo,
                            final @Context HttpServletRequest request,
                            final @Context SecurityContext securityContext )
    {
        final StoreType st = StoreType.get( type );

        Response response = null;
        String json = null;
        try
        {
            json = IOUtils.toString( request.getInputStream() );
            json = objectMapper.patchLegacyStoreJson( json );
        }
        catch ( final IOException e )
        {
            final String message = "Failed to read " + st.getStoreClass()
                                                         .getSimpleName() + " from request body.";

            logger.error( message, e );
            response = formatResponse( e, message );
        }

        if ( response != null )
        {
            return response;
        }

        ArtifactStore store = null;
        try
        {
            store = objectMapper.readValue( json, st.getStoreClass() );
        }
        catch ( final IOException e )
        {
            final String message = "Failed to parse " + st.getStoreClass()
                                                         .getSimpleName() + " from request body.";

            logger.error( message, e );
            response = formatResponse( e, message );
        }

        if ( response != null )
        {
            return response;
        }

        logger.info( "\n\nGot artifact store: {}\n\n", store );

        try
        {
            String user = securityManager.getUser( securityContext, request );

            if ( adminController.store( store, user, false ) )
            {
                final URI uri = uriInfo.getBaseUriBuilder()
                                       .path( "/api/admin/stores" )
                                       .path( store.getPackageType() )
                                       .path( store.getType().singularEndpointName() )
                                       .build( store.getName() );

                response = formatCreatedResponseWithJsonEntity( uri, store, objectMapper );
            }
            else
            {
                response = status( CONFLICT )
                                   .entity( "{\"error\": \"Store already exists.\"}" )
                                   .type( application_json )
                                   .build();
            }
        }
        catch ( final IndyWorkflowException e )
        {
            logger.error( e.getMessage(), e );
            response = formatResponse( e );
        }
        return response;
    }

    /*
     * (non-Javadoc)
     * @see org.commonjava.indy.core.rest.admin.DeployPointAdminResource#store(java.lang.String)
     */
    @ApiOperation( "Update an existing store" )
    @ApiResponses( { @ApiResponse( code = 200, message = "The store was updated" ),
        @ApiResponse( code = 400, message = "The store specified in the body JSON didn't match the URL parameters" ), } )
    @ApiImplicitParams( { @ApiImplicitParam( allowMultiple = false, paramType = "body", name = "body", required = true, dataType = "org.commonjava.indy.model.core.ArtifactStore", value = "The artifact store definition JSON" ) } )
    @Path( "/{name}" )
    @PUT
    @Consumes( ApplicationContent.application_json )
    public Response store( final @PathParam( "packageType" ) String packageType,
                           final @ApiParam( allowableValues = "hosted,group,remote", required = true ) @PathParam( "type" ) String type,
                           final @ApiParam( required = true ) @PathParam( "name" ) String name,
                           final @Context HttpServletRequest request,
                           final @Context SecurityContext securityContext )
    {
        final StoreType st = StoreType.get( type );

        Response response = null;
        String json = null;
        try
        {
            json = IOUtils.toString( request.getInputStream() );
            json = objectMapper.patchLegacyStoreJson( json );
        }
        catch ( final IOException e )
        {
            final String message = "Failed to read " + st.getStoreClass()
                                                         .getSimpleName() + " from request body.";

            logger.error( message, e );
            response = formatResponse( e, message );
        }

        if ( response != null )
        {
            return response;
        }

        ArtifactStore store = null;
        try
        {
            store = objectMapper.readValue( json, st.getStoreClass() );
        }
        catch ( final IOException e )
        {
            final String message = "Failed to parse " + st.getStoreClass()
                                                          .getSimpleName() + " from request body.";

            logger.error( message, e );
            response = formatResponse( e, message );
        }

        if ( response != null )
        {
            return response;
        }

        if ( !packageType.equals(store.getPackageType()) || st != store.getType() || !name.equals( store.getName() ) )
        {
            response = Response.status( Status.BAD_REQUEST )
                               .entity( String.format( "Store in URL path is: '%s' but in JSON it is: '%s'",
                                                       new StoreKey( packageType, st, name ), store.getKey() ) )
                               .build();
        }

        try
        {
            String user = securityManager.getUser( securityContext, request );

            logger.info( "Storing: {}", store );
            if ( adminController.store( store, user, false ) )
            {
                response = ok().build();
            }
            else
            {
                logger.warn( "{} NOT modified!", store );
                response = notModified().build();
            }
        }
        catch ( final IndyWorkflowException e )
        {
            logger.error( e.getMessage(), e );
            response = formatResponse( e );
        }

        return response;
    }

    @ApiOperation( "Retrieve the definitions of all artifact stores of a given type on the system" )
    @ApiResponses(
            { @ApiResponse( code = 200, response = StoreListingDTO.class, message = "The store definitions" ), } )
    @GET
    @Produces( ApplicationContent.application_json )
    public Response getAll( final @ApiParam(
            "Filter only stores that support the package type (eg. maven, npm). NOTE: '_all' returns all." )
                            @PathParam( "packageType" ) String packageType,
                            final @ApiParam( allowableValues = "hosted,group,remote", required = true )
                            @PathParam( "type" ) String type )
    {

        final StoreType st = StoreType.get( type );

        Response response;
        try
        {
            final List<ArtifactStore> stores = adminController.getAllOfType( packageType, st );

            logger.info( "Returning listing containing stores:\n\t{}", new JoinString( "\n\t", stores ) );

            final StoreListingDTO<ArtifactStore> dto = new StoreListingDTO<>( stores );

            response = formatOkResponseWithJsonEntity( dto, objectMapper );
        }
        catch ( final IndyWorkflowException e )
        {
            logger.error( e.getMessage(), e );
            response = formatResponse( e );
        }

        return response;
    }

    @ApiOperation( "Retrieve the definition of a specific artifact store" )
    @ApiResponses( { @ApiResponse( code = 200, response = ArtifactStore.class, message = "The store definition" ),
        @ApiResponse( code = 404, message = "The store doesn't exist" ), } )
    @Path( "/{name}" )
    @GET
    @Produces( ApplicationContent.application_json )
    public Response get( final @PathParam( "packageType" ) String packageType,
                         final @ApiParam( allowableValues = "hosted,group,remote", required = true ) @PathParam( "type" ) String type,
                         final @ApiParam( required = true ) @PathParam( "name" ) String name )
    {
        final StoreType st = StoreType.get( type );
        final StoreKey key = new StoreKey( packageType, st, name );

        Response response;
        try
        {
            final ArtifactStore store = adminController.get( key );
            logger.info( "Returning repository: {}", store );

            if ( store == null )
            {
                response = Response.status( Status.NOT_FOUND )
                                   .build();
            }
            else
            {
                response = formatOkResponseWithJsonEntity( store, objectMapper );
            }
        }
        catch ( final IndyWorkflowException e )
        {
            logger.error( e.getMessage(), e );
            response = formatResponse( e );
        }
        return response;
    }

    @ApiOperation( "Delete an artifact store" )
    @ApiResponses( { @ApiResponse( code = 204, response = ArtifactStore.class, message = "The store was deleted (or didn't exist in the first place)" ), } )
    @Path( "/{name}" )
    @DELETE
    public Response delete( final @PathParam( "packageType" ) String packageType,
                            final @ApiParam( allowableValues = "hosted,group,remote", required = true ) @PathParam( "type" ) String type,
                            final @ApiParam( required = true ) @PathParam( "name" ) String name,
                            @Context final HttpServletRequest request,
                            final @Context SecurityContext securityContext )
    {
        final StoreType st = StoreType.get( type );
        final StoreKey key = new StoreKey( packageType, st, name );

        logger.info( "Deleting: {}", key );
        Response response;
        try
        {
            String summary = null;
            try
            {
                summary = IOUtils.toString( request.getInputStream() );
            }
            catch ( final IOException e )
            {
                // no problem, try to get the summary from a header instead.
                logger.info( "store-deletion change summary not in request body, checking headers." );
            }

            if ( isEmpty( summary ) )
            {
                summary = request.getHeader( METADATA_CHANGELOG );
            }

            if ( isEmpty( summary ) )
            {
                summary = "Changelog not provided";
            }

            String user = securityManager.getUser( securityContext, request );

            adminController.delete( key, user, summary );

            response = noContent().build();
        }
        catch ( final IndyWorkflowException e )
        {
            logger.error( e.getMessage(), e );
            response = formatResponse( e );
        }
        return response;
    }

    @ApiOperation( "Retrieve the definition of a remote by specific url" )
    @ApiResponses( { @ApiResponse( code = 200, response = ArtifactStore.class, message = "The store definition" ),
                           @ApiResponse( code = 404, message = "The remote repository doesn't exist" ), } )
    @Path( "/query/byUrl" )
    @GET
    public Response getRemoteByUrl( final @PathParam( "packageType" ) String packageType,
                                    final @ApiParam( allowableValues = "remote", required = true )
                                    @PathParam( "type" ) String type, final @QueryParam( "url" ) String url,
                                    @Context final HttpServletRequest request,
                                    final @Context SecurityContext securityContext )
    {
        if ( !"remote".equals( type ) )
        {
            return ResponseUtils.formatBadRequestResponse(
                    String.format( "Not supporte repository type of %s", type ) );
        }

        logger.info( "Get remote repository by url: {}", url );
        Response response;
        try
        {
            final List<RemoteRepository> remotes = adminController.getRemoteByUrl( url, packageType );
            logger.info( "According to url {}, Returning remote listing remote repositories: {}", url, remotes );

            if ( remotes == null || remotes.isEmpty() )
            {
                response = Response.status( Status.NOT_FOUND ).build();
            }
            else
            {
                final StoreListingDTO<RemoteRepository> dto = new StoreListingDTO<>( remotes );
                response = formatOkResponseWithJsonEntity( dto, objectMapper );
            }
        }
        catch ( final IndyWorkflowException e )
        {
            logger.error( e.getMessage(), e );
            response = formatResponse( e );
        }
        return response;
    }

}


package org.commonjava.indy.flat.data;

import org.commonjava.indy.audit.ChangeSummary;
import org.commonjava.indy.data.IndyDataException;
import org.commonjava.indy.model.core.ArtifactStore;
import org.commonjava.indy.model.core.StoreKey;
import org.commonjava.indy.model.core.StoreType;
import org.commonjava.indy.model.core.io.IndyObjectMapper;
import org.commonjava.indy.subsys.datafile.DataFile;
import org.commonjava.indy.subsys.datafile.DataFileManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.function.Consumer;

public class DataFileStoreUtils
{
    private static final Logger logger = LoggerFactory.getLogger( DataFileStoreUtils.class );

    public static final String INDY_STORE = "indy";

    public static final String LOAD_FROM_DISK = "load-from-disk";

    /**
     * Load all store definitions from disk and apply consumer function.
     */
    public static void loadFromDiskAnd( DataFileManager manager, IndyObjectMapper serializer,
                                        final ChangeSummary summary, Consumer<ArtifactStore> consumer )
    {
        loadFromDiskAnd( manager, serializer, null, summary, consumer );
    }

    /**
     * Load store definitions from disk and apply consumer function.
     * @param manager
     * @param serializer
     * @param key if null, load all.
     * @param summary
     * @param consumer
     */
    public static void loadFromDiskAnd( DataFileManager manager, IndyObjectMapper serializer, StoreKey key,
                                        final ChangeSummary summary, Consumer<ArtifactStore> consumer )
    {
        if ( key != null ) // Load a single file
        {
            DataFile f = manager.getDataFile( INDY_STORE, key.getPackageType(), key.getType().singularEndpointName(),
                                              key.getName() + ".json" );
            if ( f.exists() )
            {
                ArtifactStore store;
                try
                {
                    String json = f.readString();
                    store = serializer.readValue( json, key.getType().getStoreClass() );
                }
                catch ( IOException e )
                {
                    logger.error( "Failed to read file", e );
                    return;
                }
                consumer.accept( store );
            }
            return;
        }

        // Load all
        DataFile[] packageDirs = manager.getDataFile( INDY_STORE ).listFiles( ( f ) -> true );
        for ( DataFile pkgDir : packageDirs )
        {
            for ( StoreType type : StoreType.values() )
            {
                DataFile[] files = pkgDir.getChild( type.singularEndpointName() ).listFiles( f -> true );
                if ( files != null )
                {
                    for ( final DataFile f : files )
                    {
                        try
                        {
                            final String json = f.readString();
                            final ArtifactStore store = serializer.readValue( json, type.getStoreClass() );
                            if ( store == null )
                            {
                                f.delete( summary );
                            }
                            else
                            {
                                consumer.accept( store );
                            }
                        }
                        catch ( final IOException e )
                        {
                            logger.error( String.format( "Failed to load %s store: %s. Reason: %s", type, f,
                                                         e.getMessage() ), e );
                            try
                            {
                                f.delete( summary );
                            }
                            catch ( IOException e1 )
                            {
                                logger.error( "Failed to delete invalid store definition file: " + f, e );
                            }
                        }
                    }
                }
            }
        }
    }

    public static void storeToDisk( final DataFileManager manager, final IndyObjectMapper serializer,
                                    final boolean skipIfExists, final ChangeSummary summary,
                                    final ArtifactStore... stores ) throws IndyDataException
    {
        for ( final ArtifactStore store : stores )
        {
            final DataFile f = manager.getDataFile( INDY_STORE, store.getPackageType(),
                                                    store.getType().singularEndpointName(), store.getName() + ".json" );

            if ( skipIfExists && f.exists() )
            {
                continue;
            }

            final DataFile d = f.getParent();
            if ( !d.mkdirs() )
            {
                throw new IndyDataException( "Cannot create storage directory: {} for definition: {}", d, store );
            }

            try
            {
                final String json = serializer.writeValueAsString( store );
                f.writeString( json, "UTF-8", summary );
                logger.debug( "Persisted {} to disk at: {}\n{}", store, f, json );
            }
            catch ( final IOException e )
            {
                throw new IndyDataException( "Cannot write definition: {} to: {}. Reason: {}", e, store, f,
                                             e.getMessage() );
            }
        }
    }

    public static void deleteFromDisk( final DataFileManager manager, final ArtifactStore store,
                                       final ChangeSummary summary ) throws IndyDataException
    {
        logger.trace( "Attempting to delete data file for store: {}", store.getKey() );

        final DataFile f =
                        manager.getDataFile( INDY_STORE, store.getPackageType(), store.getType().singularEndpointName(),
                                             store.getName() + ".json" );

        try
        {
            logger.trace( "Deleting file: {}", f );
            f.delete( summary );
        }
        catch ( final IOException e )
        {
            throw new IndyDataException( "Cannot delete store definition: {} in file: {}. Reason: {}", e,
                                         store.getKey(), f, e.getMessage() );
        }
    }

}


/**
 * Copyright (C) 2011-2018 Red Hat, Inc. (https://github.com/Commonjava/indy)
 *
 * 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 org.commonjava.indy.hostedbyarc;

import org.apache.commons.lang.StringUtils;
import org.commonjava.indy.IndyWorkflowException;
import org.commonjava.indy.audit.ChangeSummary;
import org.commonjava.indy.content.ContentManager;
import org.commonjava.indy.data.IndyDataException;
import org.commonjava.indy.data.StoreDataManager;
import org.commonjava.indy.hostedbyarc.config.HostedByArchiveConfig;
import org.commonjava.indy.model.core.HostedRepository;
import org.commonjava.indy.pkg.maven.model.MavenPackageTypeDescriptor;
import org.commonjava.indy.util.ApplicationStatus;
import org.commonjava.maven.galley.event.EventMetadata;
import org.commonjava.maven.galley.model.TransferOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

@ApplicationScoped
public class HostedByArchiveManager
{
    private final Logger logger = LoggerFactory.getLogger( this.getClass() );

//    public static final String TMP_DIR = StringUtils.defaultIfBlank( System.getProperty( "java.io.tmpdir" ), "/tmp" );

    @Inject
    private StoreDataManager storeDataManager;

    @Inject
    private ContentManager contentManager;

    @Inject
    private HostedByArchiveConfig config;

//    @Inject
//    @WeftManaged
//    @ExecutorConfig( named = "Hosted-by-arc-executor", priority = 1, threads = 8 )
//    @Deprecated
//    private ExecutorService executors;

    public HostedRepository createStoreByArc( final InputStream fileInput, final String repoName, final String user,
                                              final String ignoredPrefix )
            throws IndyWorkflowException
    {
        final HostedRepository repo = createHostedByName( repoName, user, "Create hosted by zip." );

        storeZipContentInHosted( fileInput, ignoredPrefix, repo );

        return repo;
    }

    private HostedRepository createHostedByName( final String repoName, final String user, final String changeLog )
            throws IndyWorkflowException
    {
        final HostedRepository hosted = new HostedRepository( MavenPackageTypeDescriptor.MAVEN_PKG_KEY, repoName );
        // I think this new hosted should allow snapshots and releases
        hosted.setAllowSnapshots( true );
        hosted.setAllowReleases( true );
        String storeChangeLog = changeLog;
        if ( StringUtils.isBlank( changeLog ) )
        {
            storeChangeLog = "Changelog not provided";
        }

        final ChangeSummary summary = new ChangeSummary( user, storeChangeLog );

        logger.trace( "Persisting hosted store: {} using: {}", hosted, storeDataManager );
        try
        {

            if ( storeDataManager.storeArtifactStore( hosted, summary, true, true, new EventMetadata() ) )
            {
                return hosted;
            }
            else
            {
                throw new IndyWorkflowException( ApplicationStatus.SERVER_ERROR.code(), "Failed to store: {}. ",
                                                 hosted.getKey() );
            }
        }
        catch ( final IndyDataException e )
        {
            throw new IndyWorkflowException( ApplicationStatus.SERVER_ERROR.code(), "Failed to store: {}. Reason: {}",
                                             e, hosted.getKey(), e.getMessage() );
        }
    }

    private void storeZipContentInHosted( final InputStream zipStream, final String ignoredPrefix,
                                          final HostedRepository repo )
            throws IndyWorkflowException
    {

        try (ZipInputStream zis = new ZipInputStream( zipStream ))
        {
            ZipEntry zipEntry = zis.getNextEntry();
            while ( zipEntry != null )
            {
                String fileName = zipEntry.getName();
                if ( !zipEntry.isDirectory() )
                {
                    storeStreamWithPath( fileName, ignoredPrefix, zis, repo );
                }
                zipEntry = zis.getNextEntry();
            }
            zis.closeEntry();
        }
        catch ( IOException e )
        {
            throw new IndyWorkflowException( "", e );
        }

    }

    private void storeStreamWithPath( final String rawPath, final String ignoredPrefix, final InputStream input,
                                      final HostedRepository repo )
            throws IndyWorkflowException
    {
        logger.trace( "Raw path is {}, ignored prefix is {}", rawPath, ignoredPrefix );
        String path = rawPath.startsWith( "/" ) ? rawPath : "/" + rawPath;
        logger.trace( "Processed path is {}", path );
        if ( StringUtils.isNotBlank( ignoredPrefix ) && path.startsWith( ignoredPrefix ) )
        {
            path = path.replaceFirst( ignoredPrefix, "" );
        }
        contentManager.store( repo, path, input, TransferOperation.UPLOAD );
    }

}


/**
 * Copyright (C) 2011-2018 Red Hat, Inc. (https://github.com/Commonjava/indy)
 *
 * 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 org.commonjava.indy.implrepo.data;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.enterprise.inject.Alternative;
import javax.inject.Named;

import org.commonjava.indy.implrepo.ImpliedReposException;
import org.commonjava.indy.model.core.ArtifactStore;
import org.commonjava.indy.model.core.StoreKey;
import org.commonjava.indy.model.core.io.IndyObjectMapper;

import com.fasterxml.jackson.core.JsonProcessingException;

@Alternative
@Named
public class ImpliedRepoMetadataManager
{

    public static final String IMPLIED_STORES = "implied_stores";

    public static final String IMPLIED_BY_STORES = "implied_by_stores";

    private final IndyObjectMapper mapper;

    public ImpliedRepoMetadataManager( final IndyObjectMapper mapper )
    {
        this.mapper = mapper;
    }

    // TODO: Need to deal with pre-existing implications.
    public void addImpliedMetadata( final ArtifactStore origin, final List<ArtifactStore> implied )
        throws ImpliedReposException
    {
        try
        {
            final List<StoreKey> impliedKeys = new ArrayList<>( implied.size() );
            for ( final ArtifactStore store : implied )
            {
                impliedKeys.add( store.getKey() );
                updateImpliedBy( store, origin );
            }

            origin.setMetadata( IMPLIED_STORES, mapper.writeValueAsString( new ImpliedRemotesWrapper( impliedKeys ) ) );
        }
        catch ( final JsonProcessingException e )
        {
            throw new ImpliedReposException( "Failed to serialize implied stores: %s to JSON: %s. Error: %s", e,
                                             implied, origin.getKey(), e.getMessage() );
        }
    }

    public void updateImpliedBy( final ArtifactStore store, final ArtifactStore origin )
        throws ImpliedReposException
    {
        final String metadata = store.getMetadata( IMPLIED_BY_STORES );

        ImpliedRemotesWrapper wrapper;
        if ( metadata == null )
        {
            wrapper = new ImpliedRemotesWrapper( Collections.singletonList( origin.getKey() ) );
        }
        else
        {
            try
            {
                wrapper = mapper.readValue( metadata, ImpliedRemotesWrapper.class );
                if ( !wrapper.addItem( origin.getKey() ) )
                {
                    // nothing to change; set to null to signal that nothing should change.
                    wrapper = null;
                }
            }
            catch ( final IOException e )
            {
                throw new ImpliedReposException(
                                                 "Failed to de-serialize implied-by stores from: %s\nJSON: %s\nError: %s",
                                                 e, store.getKey(), metadata, e.getMessage() );
            }
        }

        if ( wrapper != null )
        {
            try
            {
                store.setMetadata( IMPLIED_BY_STORES, mapper.writeValueAsString( wrapper ) );
            }
            catch ( final JsonProcessingException e )
            {
                throw new ImpliedReposException( "Failed to serialize implied-by stores to: %s\nJSON: %s\nError: %s",
                                                 e, store.getKey(), metadata, e.getMessage() );
            }
        }

    }

    public List<StoreKey> getStoresImpliedBy( final ArtifactStore origin )
        throws ImpliedReposException
    {
        final String metadata = origin.getMetadata( IMPLIED_STORES );
        if ( metadata == null )
        {
            return null;
        }

        try
        {
            final ImpliedRemotesWrapper wrapper = mapper.readValue( metadata, ImpliedRemotesWrapper.class );
            return wrapper.getItems();
        }
        catch ( final IOException e )
        {
            throw new ImpliedReposException( "Failed to de-serialize implied stores from: %s\nJSON: %s\nError: %s", e,
                                             origin.getKey(), metadata, e.getMessage() );
        }
    }

    public List<StoreKey> getStoresImplying( final ArtifactStore store )
        throws ImpliedReposException
    {
        final String metadata = store.getMetadata( IMPLIED_BY_STORES );
        if ( metadata == null )
        {
            return null;
        }

        try
        {
            final ImpliedRemotesWrapper wrapper = mapper.readValue( metadata, ImpliedRemotesWrapper.class );
            return wrapper.getItems();
        }
        catch ( final IOException e )
        {
            throw new ImpliedReposException( "Failed to de-serialize implied stores from: %s\nJSON: %s\nError: %s", e,
                                             store.getKey(), metadata, e.getMessage() );
        }
    }

    public static final class ImpliedRemotesWrapper
    {
        private List<StoreKey> items;

        public ImpliedRemotesWrapper( final List<StoreKey> items )
        {
            this.items = items;
        }

        boolean addItem( final StoreKey key )
        {
            if ( key == null )
            {
                return false;
            }

            if ( items == null )
            {
                items = new ArrayList<>();
            }

            if ( !items.contains( key ) )
            {
                return items.add( key );
            }

            return false;
        }

        @SuppressWarnings( "unused" )
        ImpliedRemotesWrapper()
        {
        }

        public List<StoreKey> getItems()
        {
            return items;
        }

        @SuppressWarnings( "unused" )
        public void setItems( final List<StoreKey> items )
        {
            this.items = items;
        }
    }

}


/**
 * Copyright (C) 2011-2018 Red Hat, Inc. (https://github.com/Commonjava/indy)
 *
 * 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 org.commonjava.indy.subsys.http;

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.client.CloseableHttpClient;
import org.commonjava.indy.subsys.http.util.IndySiteConfigLookup;
import org.commonjava.maven.galley.spi.auth.PasswordManager;
import org.commonjava.maven.galley.transport.htcli.Http;
import org.commonjava.maven.galley.transport.htcli.HttpImpl;
import org.commonjava.util.jhttpc.HttpFactory;
import org.commonjava.util.jhttpc.INTERNAL.util.HttpUtils;
import org.commonjava.util.jhttpc.JHttpCException;
import org.commonjava.util.jhttpc.auth.AttributePasswordManager;

import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Default;
import javax.enterprise.inject.Produces;
import javax.inject.Inject;
import javax.inject.Singleton;

@ApplicationScoped
public class IndyHttpProvider
{

    private HttpFactory httpFactory;

    private Http http;

    @Inject
    private IndySiteConfigLookup siteConfigLookup;

    private PasswordManager passwordManager;

    protected IndyHttpProvider()
    {
    }

    public IndyHttpProvider( IndySiteConfigLookup siteConfigLookup )
    {
        this.siteConfigLookup = siteConfigLookup;
        setup();
    }

    @PostConstruct
    public void setup()
    {
        passwordManager = new org.commonjava.maven.galley.auth.AttributePasswordManager();
        http = new HttpImpl( passwordManager );
        httpFactory = new HttpFactory( new AttributePasswordManager( siteConfigLookup ) );
    }

    @Produces
    @Default
    @Singleton
    public PasswordManager getPasswordManager()
    {
        return passwordManager;
    }

    @Produces
    @Default
    @Singleton
    public Http getHttp()
    {
        return http;
    }

    /**
     * Create http request context and apply site config.
     * @param siteId ID to represent the site. It is generally the hostname of target site.
     * @return
     * @throws IndyHttpException
     */
    public HttpClientContext createContext( final String siteId ) throws IndyHttpException
    {
        try
        {
            return httpFactory.createContext( siteConfigLookup.lookup( siteId ) );
        }
        catch ( JHttpCException e )
        {
            throw new IndyHttpException( "Failed to create http client context: %s", e, e.getMessage() );
        }
    }

    /**
     * Create http client and apply site config.
     * @param siteId ID to represent the site. It is generally the hostname of target site.
     * @return
     * @throws IndyHttpException
     */
    public CloseableHttpClient createClient( final String siteId ) throws IndyHttpException
    {
        try
        {
            return httpFactory.createClient( siteConfigLookup.lookup( siteId ) );
        }
        catch ( JHttpCException e )
        {
            throw new IndyHttpException( "Failed to create http client: %s", e, e.getMessage() );
        }
    }

    public void cleanup( final CloseableHttpClient client, final HttpUriRequest request,
                         final CloseableHttpResponse response )
    {
        HttpUtils.cleanupResources( client, request, response );
    }

}


/**
 * Copyright (C) 2011-2018 Red Hat, Inc. (https://github.com/Commonjava/indy)
 *
 * 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 org.commonjava.indy.promote.validate;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.ClassUtils;
import org.commonjava.cdi.util.weft.DrainingExecutorCompletionService;
import org.commonjava.cdi.util.weft.ExecutorConfig;
import org.commonjava.cdi.util.weft.WeftExecutorService;
import org.commonjava.cdi.util.weft.WeftManaged;
import org.commonjava.indy.IndyWorkflowException;
import org.commonjava.indy.audit.ChangeSummary;
import org.commonjava.indy.content.ContentManager;
import org.commonjava.indy.content.DownloadManager;
import org.commonjava.indy.data.IndyDataException;
import org.commonjava.indy.data.StoreDataManager;
import org.commonjava.indy.measure.annotation.Measure;
import org.commonjava.indy.metrics.IndyMetricsManager;
import org.commonjava.indy.model.core.ArtifactStore;
import org.commonjava.indy.model.core.RemoteRepository;
import org.commonjava.indy.promote.conf.PromoteConfig;
import org.commonjava.indy.promote.model.GroupPromoteRequest;
import org.commonjava.indy.promote.model.PathsPromoteRequest;
import org.commonjava.indy.promote.model.PromoteRequest;
import org.commonjava.indy.promote.model.ValidationResult;
import org.commonjava.indy.promote.model.ValidationRuleSet;
import org.commonjava.indy.promote.validate.model.ValidationRequest;
import org.commonjava.indy.promote.validate.model.ValidationRuleMapping;
import org.commonjava.maven.galley.event.EventMetadata;
import org.commonjava.maven.galley.model.Transfer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;

import static com.codahale.metrics.MetricRegistry.name;
import static java.lang.String.format;
import static org.apache.commons.lang.StringUtils.join;
import static org.commonjava.indy.core.ctl.PoolUtils.detectOverloadVoid;

/**
 * Created by jdcasey on 9/11/15.
 */
public class PromotionValidator
{
    private static final String PROMOTE_REPO_PREFIX = "Promote_";

    private static final String PROMOTION_VALIDATION_RULE = "promotion-validation-rule";

    private static final String PROMOTION_VALIDATION_RULE_SET = "promotion-validation-rule-set";

    @Inject
    private PromoteValidationsManager validationsManager;

    @Inject
    private PromotionValidationTools validationTools;

    @Inject
    private StoreDataManager storeDataMgr;

    @Inject
    private DownloadManager downloadManager;

    @Inject
    private PromoteConfig config;

    @Inject
    private IndyMetricsManager metricsManager;

    @Inject
    @WeftManaged
    @ExecutorConfig( named = "promote-validation-rules-runner", threads = 20, priority = 5, loadSensitive = ExecutorConfig.BooleanLiteral.TRUE, maxLoadFactor = 400 )
    private WeftExecutorService validateService;

    private final Logger logger = LoggerFactory.getLogger( getClass() );

    protected PromotionValidator()
    {
    }

    public PromotionValidator( PromoteValidationsManager validationsManager, PromotionValidationTools validationTools,
                               StoreDataManager storeDataMgr, DownloadManager downloadManager,
                               WeftExecutorService validateService, IndyMetricsManager metricsManager )
    {
        this.validationsManager = validationsManager;
        this.validationTools = validationTools;
        this.storeDataMgr = storeDataMgr;
        this.downloadManager = downloadManager;
        this.validateService = validateService;
        this.metricsManager = metricsManager;
    }

    /**
     * NOTE: As of Indy 1.2.6, ValidationRequest passed back to enable further post-processing, especially of promotion
     * paths, after promotion takes place. This enables us to avoid re-executing recursive path discovery, for instance.
     *
     * @param request
     * @param result
     * @param baseUrl
     * @return
     * @throws PromotionValidationException
     * @throws IndyWorkflowException
     */
    @Measure
    public ValidationRequest validate( PromoteRequest request, ValidationResult result, String baseUrl )
            throws PromotionValidationException, IndyWorkflowException
    {
        ValidationRuleSet set = validationsManager.getRuleSetMatching( request.getTargetKey() );

        ArtifactStore source;
        try
        {
            source = storeDataMgr.getArtifactStore( request.getSource() );
        }
        catch ( IndyDataException e )
        {
            throw new PromotionValidationException(
                    String.format( "Failed to retrieve source ArtifactStore: %s for validation", request.getSource() ),
                    e );
        }

        if ( set != null )
        {

            logger.debug( "Running validation rule-set for promotion: {}", set.getName() );

            result.setRuleSet( set.getName() );
            MDC.put( PROMOTION_VALIDATION_RULE_SET, set.getName() );

            List<String> ruleNames = set.getRuleNames();
            if ( ruleNames != null && !ruleNames.isEmpty() )
            {
                final ArtifactStore store = getRequestStore( request, baseUrl );
                final ValidationRequest req = new ValidationRequest( request, set, validationTools, store );
                try
                {
                    DrainingExecutorCompletionService<Exception> svc =
                            new DrainingExecutorCompletionService<>( validateService );

                    detectOverloadVoid(()->{
                        for ( String ruleRef : ruleNames )
                        {
                            svc.submit( () -> {
                                MDC.put( PROMOTION_VALIDATION_RULE, ruleRef );
                                Exception err = null;
                                try
                                {
                                    executeValidationRule( ruleRef, req, result, request );
                                }
                                catch ( Exception e )
                                {
                                    err = e;
                                }
                                finally
                                {
                                    MDC.remove( PROMOTION_VALIDATION_RULE );
                                }

                                return err;
                            } );
                        }
                    });

                    List<String> errors = new ArrayList<>();
                    svc.drain( err->{
                        if ( err != null )
                        {
                            logger.error( "Promotion validation failure", err );
                            errors.add( err.getMessage() );
                        }
                    } );

                    if ( !errors.isEmpty() )
                    {
                        throw new PromotionValidationException( format( "Failed to do promotion validation: \n\n%s", join( errors, "\n" ) ) );
                    }
                }
                catch ( InterruptedException e )
                {
                    throw new PromotionValidationException( "Failed to do promotion validation: validation execution has been interrupted ", e );
                }
                catch ( ExecutionException e )
                {
                    throw new PromotionValidationException( "Failed to execute promotion validations", e );
                }
                finally
                {
                    if ( needTempRepo( request ) )
                    {
                        try
                        {
                            final String changeSum = format( "Removes the temp remote repo [%s] after promote operation.", store );
                            storeDataMgr.deleteArtifactStore( store.getKey(),
                                                              new ChangeSummary( ChangeSummary.SYSTEM_USER,
                                                                                changeSum ),
                                                              new EventMetadata().set( ContentManager.SUPPRESS_EVENTS,
                                                                                true ) );

                            Transfer root = downloadManager.getStoreRootDirectory( store );
                            if ( root.exists() )
                            {
                                root.delete( false );
                            }

                            logger.debug( "Promotion temporary repo {} has been deleted for {}", store.getKey(),
                                         request.getSource() );
                        }
                        catch ( IndyDataException | IOException e )
                        {
                            logger.warn( "Temporary promotion validation repository was NOT removed correctly.", e );
                        }
                    }
                }
                return req;
            }
            else
            {
                logger.info( "No validation rules are defined for: {}", request.getTargetKey() );
                return new ValidationRequest( request, set, validationTools, source );
            }
        }
        else
        {
            logger.info( "No validation rule-sets are defined for: {}", request.getTargetKey() );
            return new ValidationRequest( request, set, validationTools, source );
        }
    }

    //@Measure <-- this does not work. Internal calls can never be intercepted.
    private void executeValidationRule( final String ruleRef, final ValidationRequest req,
                                        final ValidationResult result, final PromoteRequest request )
            throws PromotionValidationException
    {
        String ruleName =
                new File( ruleRef ).getName(); // flatten in case some path fragment leaks in...

        ValidationRuleMapping rule = validationsManager.getRuleMappingNamed( ruleName );
        if ( rule != null )
        {
            logger.debug( "Running promotion validation rule: {}", rule.getName() );
            String error = null;
            if ( metricsManager != null )
            {
                AtomicReference<Exception> ex = new AtomicReference<>();
                error = metricsManager.wrapWithStandardMetrics( () -> {
                    try
                    {
                        return rule.getRule().validate( req );
                    }
                    catch ( Exception e )
                    {
                        ex.set( e );
                        return null;
                    }
                }, () -> getMetricName( rule.getName() ) );

                if ( ex.get() != null )
                {
                    throwException( ex.get(), rule, request );
                }
            }
            else
            {
                try
                {
                    error = rule.getRule().validate( req );
                }
                catch ( Exception e )
                {
                    throwException( e, rule, request );
                }
            }

            if ( StringUtils.isNotEmpty( error ) )
            {
                logger.debug( "{} failed", rule.getName() );
                result.addValidatorError( rule.getName(), error );
            }
            else
            {
                logger.debug( "{} succeeded", rule.getName() );
            }
        }
    }

    private void throwException( Exception e, ValidationRuleMapping rule, PromoteRequest request )
                    throws PromotionValidationException
    {
        if ( e instanceof PromotionValidationException )
        {
            throw (PromotionValidationException) e;
        }

        throw new PromotionValidationException( "Failed to run validation rule: {} for request: {}. Reason: {}", e,
                                                rule.getName(), request, e );
    }

    private String getMetricName( String ruleName )
    {
        String cls = ClassUtils.getAbbreviatedName( getClass().getName(), 1 );
        return name( cls, "rule", ruleName );
    }

    private boolean needTempRepo( PromoteRequest promoteRequest )
            throws PromotionValidationException
    {
        if ( promoteRequest instanceof GroupPromoteRequest )
        {
            return false;
        }
        else if ( promoteRequest instanceof PathsPromoteRequest )
        {
            final Set<String> reqPaths = ( (PathsPromoteRequest) promoteRequest ).getPaths();
            return reqPaths != null && !reqPaths.isEmpty();
        }
        else
        {
            throw new PromotionValidationException( "The promote request is not a valid request, should not happen" );
        }
    }

    private ArtifactStore getRequestStore( PromoteRequest promoteRequest, String baseUrl )
            throws PromotionValidationException
    {
        final ArtifactStore store;
        final Logger logger = LoggerFactory.getLogger( getClass() );
        logger.info( "Promotion baseUrl: {} ", baseUrl );

        if ( needTempRepo( promoteRequest ) )
        {
            logger.info( "Promotion temporary repo is needed for {}, points to {} ", promoteRequest.getSource(),
                         baseUrl );
            final PathsPromoteRequest pathsReq = (PathsPromoteRequest) promoteRequest;

            String tempName =
                    PROMOTE_REPO_PREFIX + "tmp_" + pathsReq.getSource().getName() + "_" + new SimpleDateFormat(
                            "yyyyMMdd.hhmmss.SSSZ" ).format( new Date() );

            String packageType = pathsReq.getSource().getPackageType();

            final RemoteRepository tempRemote = new RemoteRepository( packageType, tempName, baseUrl );

            tempRemote.setPathMaskPatterns( pathsReq.getPaths() );

            store = tempRemote;
            try
            {
                final ChangeSummary changeSummary =
                        new ChangeSummary( ChangeSummary.SYSTEM_USER, "create temp remote repository" );

                storeDataMgr.storeArtifactStore( tempRemote, changeSummary, false, true, new EventMetadata() );
            }
            catch ( IndyDataException e )
            {
                throw new PromotionValidationException( "Can not store the temp remote repository correctly", e );
            }
        }
        else
        {
            logger.info( "Promotion temporary repo is not needed for {} ", promoteRequest.getSource() );
            try
            {
                store = storeDataMgr.getArtifactStore( promoteRequest.getSource() );
            }
            catch ( IndyDataException e )
            {
                throw new PromotionValidationException( "Failed to retrieve source ArtifactStore: {}. Reason: {}", e,
                                                        promoteRequest.getSource(), e.getMessage() );
            }
        }
        return store;
    }
}