org.artifactory.engine.DownloadServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.artifactory.engine.DownloadServiceImpl.java

Source

/*
 * Artifactory is a binaries repository manager.
 * Copyright (C) 2012 JFrog Ltd.
 *
 * Artifactory is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Artifactory is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Artifactory.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.artifactory.engine;

import com.google.common.collect.Iterables;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpStatus;
import org.artifactory.addon.AddonsManager;
import org.artifactory.addon.plugin.PluginsAddon;
import org.artifactory.addon.plugin.ResponseCtx;
import org.artifactory.addon.plugin.download.AfterDownloadErrorAction;
import org.artifactory.addon.plugin.download.AltResponseAction;
import org.artifactory.addon.plugin.download.BeforeDownloadAction;
import org.artifactory.addon.plugin.download.BeforeDownloadRequestAction;
import org.artifactory.addon.plugin.download.DownloadCtx;
import org.artifactory.api.config.CentralConfigService;
import org.artifactory.api.jackson.JacksonFactory;
import org.artifactory.api.repo.exception.RepoRejectException;
import org.artifactory.api.repo.exception.maven.BadPomException;
import org.artifactory.api.request.ArtifactoryResponse;
import org.artifactory.api.request.TranslatedArtifactoryRequest;
import org.artifactory.api.rest.artifact.ItemProperties;
import org.artifactory.api.security.AuthorizationService;
import org.artifactory.checksum.ChecksumType;
import org.artifactory.common.ConstantValues;
import org.artifactory.descriptor.config.CentralConfigDescriptor;
import org.artifactory.descriptor.repo.RemoteRepoDescriptor;
import org.artifactory.descriptor.repo.VirtualRepoDescriptor;
import org.artifactory.fs.RepoResource;
import org.artifactory.io.SimpleResourceStreamHandle;
import org.artifactory.io.StringResourceStreamHandle;
import org.artifactory.md.MetadataDefinitionService;
import org.artifactory.md.Properties;
import org.artifactory.mime.NamingUtils;
import org.artifactory.repo.InternalRepoPathFactory;
import org.artifactory.repo.Repo;
import org.artifactory.repo.RepoPath;
import org.artifactory.repo.service.InternalRepositoryService;
import org.artifactory.request.ArtifactoryRequest;
import org.artifactory.request.DownloadRequestContext;
import org.artifactory.request.InternalRequestContext;
import org.artifactory.request.RemoteRequestException;
import org.artifactory.request.RepoRequests;
import org.artifactory.request.Request;
import org.artifactory.request.RequestContext;
import org.artifactory.request.RequestResponseHelper;
import org.artifactory.resource.ChecksumResource;
import org.artifactory.resource.ResourceStreamHandle;
import org.artifactory.resource.UnfoundRepoResource;
import org.artifactory.resource.UnfoundRepoResourceReason;
import org.artifactory.spring.InternalContextHelper;
import org.artifactory.spring.Reloadable;
import org.artifactory.storage.StorageException;
import org.artifactory.traffic.TrafficService;
import org.artifactory.util.HttpUtils;
import org.artifactory.version.CompoundVersionDetails;
import org.codehaus.jackson.JsonGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.stereotype.Service;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.StringWriter;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;

/**
 * @author Yoav Landman
 */

@Service
@Reloadable(beanClass = InternalDownloadService.class, initAfter = { InternalRepositoryService.class })
public class DownloadServiceImpl implements InternalDownloadService {

    private static final Logger log = LoggerFactory.getLogger(DownloadServiceImpl.class);

    @Autowired
    private AuthorizationService authorizationService;

    @Autowired
    private BasicAuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    private InternalRepositoryService repositoryService;

    @Autowired
    private CentralConfigService centralConfig;

    @Autowired
    private TrafficService trafficService;

    @Autowired
    private AddonsManager addonsManager;

    private RequestResponseHelper requestResponseHelper;

    @Override
    public void init() {
        requestResponseHelper = new RequestResponseHelper(trafficService);
    }

    @Override
    public void reload(CentralConfigDescriptor oldDescriptor) {
    }

    @Override
    public void destroy() {
    }

    @Override
    public void convert(CompoundVersionDetails source, CompoundVersionDetails target) {
    }

    /**
     * Expects requests that starts with the repo url prefix and contains an optional target local repository. E.g.:
     * <pre>http://localhost:8080/artifactory/repo/ant/ant-antlr/1.6.5/ant-antlr-1.6.5.jar</pre>
     * <pre>http://localhost:8080/artifactory/repo/org/codehaus/xdoclet/xdoclet/2.0.5-SNAPSHOT/xdoclet-2.0.5-SNAPSHOT.jar</pre>
     * or with a target local repo:
     * <pre>http://localhost:8080/artifactory/local-repo/ant/ant-antlr/1.6.5/ant-antlr-1.6.5.jar</pre>
     */
    @Override
    public void process(ArtifactoryRequest request, ArtifactoryResponse response) throws IOException {
        RepoRequests.logToContext(
                "Request source = %s, Last modified = %s, If modified since = %s, Thread name = %s",
                request.getClientAddress(), centralConfig.format(request.getLastModified()),
                request.getIfModifiedSince(), Thread.currentThread().getName());
        if (response.isPropertiesQuery()) {
            RepoRequests
                    .logToContext("Requesting properties only with format " + response.getPropertiesMediaType());
        }

        //Check that this is not a recursive call
        if (request.isRecursive()) {
            RepoRequests.logToContext("Exiting download process - recursive call detected");
            String msg = "Recursive call detected for '" + request + "'. Returning nothing.";
            response.sendError(HttpStatus.SC_NOT_FOUND, msg, log);
            return;
        }

        PluginsAddon pluginAddon = addonsManager.addonByType(PluginsAddon.class);
        DownloadCtx downloadCtx = new DownloadCtx();
        RepoRequests.logToContext("Executing any BeforeDownloadRequest user plugins that may exist");
        pluginAddon.execPluginActions(BeforeDownloadRequestAction.class, downloadCtx, request,
                request.getRepoPath());

        if (downloadCtx.getModifiedRepoPath() != null) {
            RepoRequests.logToContext("BeforeDownloadRequest user plugins provided a modified repo path: "
                    + downloadCtx.getModifiedRepoPath().getId());
            request = new TranslatedArtifactoryRequest(downloadCtx.getModifiedRepoPath(), request);
        }

        if (NamingUtils.isMetadata(request.getPath())) {
            // the repo filter redirects for the ":properties" paths. This is here to protect direct calls to this
            // method (mainly from
            response.sendError(HttpStatus.SC_CONFLICT,
                    "Old metadata notation is not supported anymore: " + request.getRepoPath(), log);
        }

        String repoKey = request.getRepoKey();
        if (VirtualRepoDescriptor.GLOBAL_VIRTUAL_REPO_KEY.equals(repoKey)
                && ConstantValues.disableGlobalRepoAccess.getBoolean()) {
            // The global /repo is disabled. Cannot be used here, returning 403!
            response.sendError(HttpStatus.SC_FORBIDDEN,
                    "Accessing the global virtual repository /repo is disabled!", log);
        }

        addonsManager.interceptResponse(response);
        if (responseWasIntercepted(response)) {
            RepoRequests.logToContext("Exiting download process - intercepted by addon manager");
            return;
        }

        try {
            Repo repository = repositoryService.repositoryByKey(repoKey);

            DownloadRequestContext requestContext = new DownloadRequestContext(request, downloadCtx.isExpired());

            RepoResource resource;
            if (repository == null) {
                RepoRequests.logToContext("Exiting download process - failed to find the repository");
                resource = new UnfoundRepoResource(request.getRepoPath(),
                        "Failed to find the repository '" + repoKey + "' specified in the request.");
            } else {
                RepoRequests.logToContext("Retrieving info");
                resource = repository.getInfo(requestContext);
            }

            respond(requestContext, response, resource);

        } catch (IOException e) {
            response.setException(e);
            //We can get here when sending a response while the client hangs up the connection.
            //In this case the response will be committed so there is no point in sending an error.
            if (!response.isCommitted()) {
                response.sendInternalError(e, log);
            }
            throw e;
        } finally {
            if (response.isSuccessful()) {
                RepoRequests.logToContext("Request succeeded");
            } else {
                Exception exception = response.getException();
                if (exception != null) {
                    RepoRequests.logToContext("Request failed: %s", exception.getMessage());
                } else {
                    RepoRequests.logToContext("Request failed with no exception");
                }
            }
        }
    }

    @Override
    public void releaseDownloadWaiters(CountDownLatch latch) {
        latch.countDown();
    }

    private boolean responseWasIntercepted(ArtifactoryResponse response) {
        return response.isError();
    }

    private void respond(InternalRequestContext requestContext, ArtifactoryResponse response, RepoResource resource)
            throws IOException {
        try {
            Request request = requestContext.getRequest();

            boolean resourceFound = resource.isFound();
            boolean headRequest = request.isHeadOnly();
            boolean checksumRequest = request.isChecksum();
            boolean targetRepoIsNotRemoteOrDoesntStore = isRepoNotRemoteOrDoesntStoreLocally(resource);

            RepoRequests.logToContext("Requested resource is found = %s", resourceFound);
            RepoRequests.logToContext("Request is HEAD = %s", headRequest);
            RepoRequests.logToContext("Request is for a checksum = %s", checksumRequest);
            RepoRequests.logToContext("Target repository is not remote or doesn't store locally = %s",
                    targetRepoIsNotRemoteOrDoesntStore);
            boolean notModified = isNotModified(request, resource);
            RepoRequests.logToContext("Requested resource was not modified = %s", notModified);

            if (!resourceFound) {
                RepoRequests.logToContext("Responding with unfound resource");
                respondResourceNotFound(requestContext, response, resource);
            } else if (headRequest && !checksumRequest && targetRepoIsNotRemoteOrDoesntStore) {
                /**
                 * Send head response only if the file isn't a checksum. Also, if the repo is a remote, only respond
                 * like this if we don't store artifacts locally (so that the whole artifact won't be requested twice),
                 * otherwise download the artifact normally and return the full info for the head request
                 */
                RepoRequests.logToContext("Responding to HEAD request with status %s", response.getStatus());
                if (notModified) {
                    requestResponseHelper.sendNotModifiedResponse(response, resource);
                } else {
                    requestResponseHelper.sendHeadResponse(response, resource);
                }
            } else if (notModified) {
                requestResponseHelper.sendNotModifiedResponse(response, resource);
            } else if (checksumRequest) {
                RepoRequests.logToContext("Responding to checksum request");
                respondForChecksumRequest(request, response, resource);
            } else {
                RepoRequests.logToContext("Responding with found resource");
                respondFoundResource(requestContext, response, resource);
            }
        } catch (FileNotFoundException e) {
            RepoRequests.logToContext("File deleted while sending request response: %s", e.getMessage());
            respondResourceNotFound(requestContext, response, resource);
        } catch (IOException e) {
            RepoRequests.logToContext("Error occurred while sending request response: %s", e.getMessage());
            handleGenericIoException(response, resource, e);
        }
    }

    private boolean isNotModified(Request request, RepoResource resource) {
        boolean hasIfModifiedSince = request.hasIfModifiedSince();
        boolean notModified = request.isNewerThan(resource.getLastModified());
        boolean hasIfNoneMatch = request.hasIfNoneMatch();
        boolean nonMatch = resource.getInfo() == null || request.isNoneMatch(resource.getInfo().getSha1());

        if (notModified && !(hasIfNoneMatch && nonMatch)) {
            RepoRequests.logToContext("Local resource isn't newer - sending a not modified response");
            return true;
        } else if (!nonMatch && hasIfNoneMatch && !hasIfModifiedSince) {
            RepoRequests.logToContext("Local resource's entity exists - sending a not modified response");
            return true;
        }
        return false;
    }

    private boolean isRepoNotRemoteOrDoesntStoreLocally(RepoResource resource) {
        RemoteRepoDescriptor remoteRepoDescriptor = repositoryService
                .remoteRepoDescriptorByKey(resource.getRepoPath().getRepoKey());
        return (remoteRepoDescriptor == null) || !remoteRepoDescriptor.isStoreArtifactsLocally();
    }

    private void respondFoundResource(InternalRequestContext requestContext, ArtifactoryResponse response,
            RepoResource resource) throws IOException {
        //Get the actual repository the resource is in
        RepoPath responseRepoPath = resource.getResponseRepoPath();
        String repoKey = responseRepoPath.getRepoKey();

        //Send the resource file back (will update the cache for remote repositories)
        ResourceStreamHandle handle = getAlternateHandle(requestContext, response, responseRepoPath);
        boolean alternateRedirect = HttpUtils.isRedirectionResponseCode(response.getStatus()) && handle != null;
        // Error is every value that is not between 200-207, in case of alternateRedirect the status is 30x therefore we have to exclude this case
        if (response.isError() && !alternateRedirect) {
            RepoRequests.logToContext("Alternative response reset status as error - returning");
            return;
        }

        Repo responseRepo = repositoryService.repositoryByKey(repoKey);

        try {
            if (handle == null) {
                RepoRequests.logToContext("Retrieving a content handle from target repo");
                //Only if we didn't already set an alternate response
                handle = repositoryService.getResourceStreamHandle(requestContext, responseRepo, resource);
            }
            AddonsManager addonsManager = InternalContextHelper.get().beanForType(AddonsManager.class);
            PluginsAddon pluginAddon = addonsManager.addonByType(PluginsAddon.class);
            RepoRequests.logToContext("Executing any BeforeDownload user plugins that may exist");
            pluginAddon.execPluginActions(BeforeDownloadAction.class, null, requestContext.getRequest(),
                    responseRepoPath);

            if (requestContext.getRequest().isHeadOnly()) {
                /**
                 * If we should response to a head, make sure repo is a remote and that stores locally (to save the
                 * double artifact downloads)
                 */
                RepoRequests.logToContext("Request was of type HEAD - responding with no content");
                requestResponseHelper.sendHeadResponse(response, resource);
            } else if (response.isPropertiesQuery()) {
                RepoRequests.logToContext("Request was of type Properties - responding with properties format "
                        + response.getPropertiesMediaType());
                Properties properties = repositoryService.getProperties(resource.getRepoPath());
                if (properties != null && !properties.isEmpty()) {
                    MediaType mediaType = MediaType.valueOf(response.getPropertiesMediaType());
                    String content;
                    if (mediaType.equals(MediaType.APPLICATION_XML)) {
                        content = InternalContextHelper.get().beanForType(MetadataDefinitionService.class)
                                .getMetadataDefinition(Properties.class).getXmlProvider().toXml(properties);
                    } else if (mediaType.equals(MediaType.APPLICATION_JSON)) {
                        content = jsonProperties(responseRepoPath, properties);
                    } else {
                        response.sendError(HttpStatus.SC_BAD_REQUEST, "Media Type " + mediaType + " not supported!",
                                log);
                        return;
                    }
                    requestResponseHelper.updateResponseForProperties(response, resource, content, mediaType);
                } else {
                    RepoRequests.logToContext("No properties found. Responding with 404");
                    response.sendError(HttpStatus.SC_NOT_FOUND, "No properties could be found.", log);
                }
            } else {
                RepoRequests.logToContext("Responding with selected content handle");
                //Streaming the file is done outside a tx, so there is a chance that the content will change!
                requestResponseHelper.sendBodyResponse(response, resource, handle);
            }
        } catch (RepoRejectException rre) {
            int status = rre.getErrorCode();
            log.debug("Repo rejection while downloading: " + rre.getMessage(), rre);
            if (status == HttpStatus.SC_FORBIDDEN && authorizationService.isAnonymous()) {
                RepoRequests.logToContext(
                        "Response status is '%s' and authenticated as anonymous - sending challenge", status);

                // Transform a forbidden to unauthorized if received for an anonymous user
                response.sendAuthorizationRequired(rre.getMessage(), authenticationEntryPoint.getRealmName());
            } else {
                RepoRequests.logToContext("Error occurred while sending response - sending error instead: %s",
                        rre.getMessage());
                String msg = "Rejected artifact download request: " + rre.getMessage();
                sendError(requestContext, response, status, msg, log);
            }
        } catch (RemoteRequestException rre) {
            log.debug("Remote exception while downloading: " + rre.getMessage(), rre);
            RepoRequests.logToContext("Error occurred while sending response - sending error instead: %s",
                    rre.getMessage());
            sendError(requestContext, response, rre.getRemoteReturnCode(), rre.getMessage(), log);
        } catch (BadPomException bpe) {
            log.debug("Bad pom while downloading: " + bpe.getMessage(), bpe);
            RepoRequests.logToContext("Error occurred while sending response - sending error instead: %s",
                    bpe.getMessage());
            sendError(requestContext, response, HttpStatus.SC_CONFLICT, bpe.getMessage(), log);
        } catch (StorageException se) {
            log.debug("Exception while downloading: " + se.getMessage(), se);
            RepoRequests.logToContext("Error occurred while sending response - sending error instead: %s",
                    se.getMessage());
            sendError(requestContext, response, HttpStatus.SC_INTERNAL_SERVER_ERROR, se.getMessage(), log);
        } finally {
            IOUtils.closeQuietly(handle);
        }
    }

    private String jsonProperties(RepoPath repoPath, Properties properties) throws IOException {
        ItemProperties itemProperties = new ItemProperties();
        for (String propertyName : properties.keySet()) {
            Set<String> propertySet = properties.get(propertyName);
            if ((propertySet != null) && !propertySet.isEmpty()) {
                itemProperties.properties.put(propertyName, Iterables.toArray(propertySet, String.class));
            }
        }
        itemProperties.slf = repoPath.getRepoKey() + "/" + repoPath.getPath();
        StringWriter out = new StringWriter();
        JsonGenerator generator = JacksonFactory.createJsonGenerator(out);
        generator.writeObject(itemProperties);
        return out.getBuffer().toString();
    }

    private void sendError(InternalRequestContext requestContext, ArtifactoryResponse response, int status,
            String reason, Logger log) throws IOException {
        RepoRequests.logToContext("Sending error with status %s and message '%s'", status, reason);
        PluginsAddon pluginAddon = addonsManager.addonByType(PluginsAddon.class);
        ResponseCtx responseCtx = new ResponseCtx();
        responseCtx.setMessage(reason);
        responseCtx.setStatus(status);
        pluginAddon.execPluginActions(AfterDownloadErrorAction.class, responseCtx, requestContext.getRequest());
        RepoRequests.logToContext("Executing any AfterDownloadErrorAction user plugins that may exist");

        status = responseCtx.getStatus();
        String message = responseCtx.getMessage();

        if (HttpUtils.isSuccessfulResponseCode(status)) {//plugin changed the status, it's not error anymore
            RepoRequests.logToContext("Response code was modified to %s by the user plugins", status);
            response.setStatus(status);
            if (responseCtx.getInputStream() == null) {// no content, so only message (if set) and status
                RepoRequests.logToContext("Received no response content from the user plugins");
                //message changed in the plugin, need to write it as response
                if (reason != null && !reason.equals(message)) {
                    RepoRequests.logToContext("Response message was modified to '%s' by the user plugins", message);
                    response.getWriter().write(message);
                }
                RepoRequests.logToContext("Sending successful response");
                response.sendSuccess();
            } else {//yay, content from plugin!
                RepoRequests.logToContext("Received a response content stream from the user plugins - sending");
                if (responseCtx.hasSize()) {
                    response.setContentLength(responseCtx.getSize());
                }
                response.sendStream(responseCtx.getInputStream());
            }
        } else { //still error, proceed as usual
            RepoRequests.logToContext("Response code wasn't modified by the user plugins");
            if (!message.equals(reason)) {
                RepoRequests.logToContext("Response message was modified to '%s' by the user plugins", message);
            }
            reason = message; // in case user changed the reason in the plugin
            if (status == HttpStatus.SC_FORBIDDEN && authorizationService.isAnonymous()) {
                RepoRequests.logToContext(
                        "Response status is '%s' and authenticated as anonymous - sending challenge", status);
                // Transform a forbidden to unauthorized if received for an anonymous user
                String realmName = authenticationEntryPoint.getRealmName();
                response.sendAuthorizationRequired(reason, realmName);
            } else {
                RepoRequests.logToContext("Sending response with the status '%s' and the message '%s'", status,
                        message);
                response.sendError(status, reason, log);
            }
        }
    }

    /**
     * Executes any subscribing user plugin routines and returns an alternate resource handle if given
     *
     * @param requestContext   Context
     * @param response         Response to return
     * @param responseRepoPath Actual repo path of the requested artifact
     * @return Stream handle if return by plugins. Null if not.
     */
    private ResourceStreamHandle getAlternateHandle(RequestContext requestContext, ArtifactoryResponse response,
            RepoPath responseRepoPath) throws IOException {
        //See if we need to return an alternate response

        RepoRequests.logToContext("Executing any AltResponse user plugins that may exist");
        AddonsManager addonsManager = InternalContextHelper.get().beanForType(AddonsManager.class);
        PluginsAddon pluginAddon = addonsManager.addonByType(PluginsAddon.class);
        ResponseCtx responseCtx = new ResponseCtx();
        pluginAddon.execPluginActions(AltResponseAction.class, responseCtx, requestContext.getRequest(),
                responseRepoPath);
        int status = responseCtx.getStatus();
        String message = responseCtx.getMessage();
        RepoRequests.logToContext("Alternative response status is set to %s and message to '%s'", status, message);
        if (status != ResponseCtx.UNSET_STATUS) {
            Map<String, String> header = responseCtx.getHeaders();
            if (header != null) {
                RepoRequests.logToContext("Found non-null alternative response header");
                for (String key : header.keySet()) {
                    response.setHeader(key, header.get(key));
                }
            }
            if (HttpUtils.isSuccessfulResponseCode(status) || HttpUtils.isRedirectionResponseCode(status)) {
                RepoRequests.logToContext("Setting response status to %s", status);
                response.setStatus(status);
                if (message != null) {
                    RepoRequests.logToContext(
                            "Found non-null alternative response message - " + "returning as content handle");
                    return new StringResourceStreamHandle(message);
                }
            } else {
                RepoRequests.logToContext("Sending error response with alternative status and message");
                response.sendError(status, message, log);
                return null;
            }
        }
        InputStream is = responseCtx.getInputStream();
        if (is != null) {
            RepoRequests.logToContext(
                    "Found non-null alternative response content stream - " + "returning as content handle");
            return new SimpleResourceStreamHandle(is, responseCtx.getSize());
        }
        RepoRequests.logToContext("Found no alternative content handles");
        return null;
    }

    private void respondResourceNotFound(InternalRequestContext requestContext, ArtifactoryResponse response,
            RepoResource resource) throws IOException {
        String reason = "Resource not found";
        int status = HttpStatus.SC_NOT_FOUND;
        RepoRequests.logToContext("Setting default response status to '%s' reason to '%s'", status, reason);
        if (resource instanceof UnfoundRepoResourceReason) {
            RepoRequests.logToContext("Response is an instance of UnfoundRepoResourceReason");
            UnfoundRepoResourceReason unfound = (UnfoundRepoResourceReason) resource;
            // use the reason and status from the resource unless it's authorization response and the
            // settings prohibit revealing this information
            boolean hideUnauthorizedResources = centralConfig.getDescriptor().getSecurity()
                    .isHideUnauthorizedResources();
            boolean originalStatusNotAuthorization = notAuthorizationStatus(unfound.getStatusCode());
            RepoRequests.logToContext("Configured to hide un-authorized resources = %s",
                    Boolean.toString(hideUnauthorizedResources));
            RepoRequests.logToContext("Original response status is auth related = %s",
                    Boolean.toString(!originalStatusNotAuthorization));
            if (!hideUnauthorizedResources || originalStatusNotAuthorization) {
                reason = unfound.getReason();
                status = unfound.getStatusCode();
                RepoRequests.logToContext("Using original response status of '%s' and message '%s'", status,
                        reason);
            }
        }
        if (status == HttpStatus.SC_FORBIDDEN && authorizationService.isAnonymous()) {
            RepoRequests.logToContext("Response status is '%s' and authenticated as anonymous - sending challenge",
                    status);
            // Transform a forbidden to unauthorized if received for an anonymous user
            String realmName = authenticationEntryPoint.getRealmName();
            response.sendAuthorizationRequired(reason, realmName);
        } else {
            sendError(requestContext, response, status, reason, log);
        }
    }

    private boolean notAuthorizationStatus(int status) {
        return status != HttpStatus.SC_UNAUTHORIZED && status != HttpStatus.SC_FORBIDDEN;
    }

    /**
     * This method handles the response to checksum requests (HEAD and GET). Even though this method is called only for
     * files that exist, some checksum policies might return null values, or even fail if the checksum algorithm is not
     * found. If for any reason we don't have the checksum we return http 404 to the client and let the client decide
     * how to proceed.
     */
    private void respondForChecksumRequest(Request request, ArtifactoryResponse response, RepoResource resource)
            throws IOException {

        RepoPath requestRepoPath = request.getRepoPath();
        if (request.isZipResourceRequest()) {
            RepoRequests.logToContext("Requested resource is located within an archive");
            requestRepoPath = InternalRepoPathFactory.archiveResourceRepoPath(requestRepoPath,
                    request.getZipResourcePath());
        }
        String requestChecksumFilePath = requestRepoPath.getPath();
        ChecksumType checksumType = ChecksumType.forFilePath(requestChecksumFilePath);
        if (checksumType == null) {
            RepoRequests.logToContext(
                    "Unable to detect the type of the requested checksum - responding with status %s",
                    HttpStatus.SC_NOT_FOUND);
            response.sendError(HttpStatus.SC_NOT_FOUND, "Checksum not found: " + requestChecksumFilePath, log);
            return;
        }

        RepoPath responseRepoPath = resource.getResponseRepoPath();
        String repoKey = responseRepoPath.getRepoKey();
        String responsePath = responseRepoPath.getPath();
        Repo repository = repositoryService.repositoryByKey(repoKey);
        String checksum = repository.getChecksum(responsePath + checksumType.ext(), resource);
        if (checksum == null) {
            RepoRequests.logToContext("Unable to find the the requested checksum - responding with status %s",
                    HttpStatus.SC_NOT_FOUND);
            response.sendError(HttpStatus.SC_NOT_FOUND, "Checksum not found for " + responsePath, log);
            return;
        }

        if (request.isHeadOnly()) {
            RepoRequests.logToContext("Sending checksum HEAD response");
            // send head response using the checksum data
            ChecksumResource checksumResource = new ChecksumResource(resource, checksumType, checksum);
            requestResponseHelper.sendHeadResponse(response, checksumResource);
        } else {
            AddonsManager addonsManager = InternalContextHelper.get().beanForType(AddonsManager.class);
            PluginsAddon pluginAddon = addonsManager.addonByType(PluginsAddon.class);
            RepoRequests.logToContext("Executing any BeforeDownloadAction user plugins that may exist");
            pluginAddon.execPluginActions(BeforeDownloadAction.class, null, request, responseRepoPath);
            // send the checksum as the response body, use the original repo path (the checksum path,
            // not the file) from the request
            RepoRequests.logToContext("Sending checksum response with status %s", response.getStatus());
            requestResponseHelper.sendBodyResponse(response, requestRepoPath, checksum);
        }
    }

    private void handleGenericIoException(ArtifactoryResponse response, RepoResource res, IOException e)
            throws IOException {
        if (e instanceof InterruptedIOException) {
            String msg = this + ": Timed out when retrieving data for " + res.getRepoPath() + " (" + e.getMessage()
                    + ").";
            RepoRequests.logToContext("Setting response status to %s - %s", HttpStatus.SC_NOT_FOUND, msg);
            response.sendError(HttpStatus.SC_NOT_FOUND, msg, log);
        } else {
            throw e;
        }
    }
}