org.commonjava.indy.httprox.util.ProxyResponseHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.commonjava.indy.httprox.util.ProxyResponseHelper.java

Source

/**
 * 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.httprox.util;

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpRequest;
import org.commonjava.indy.IndyWorkflowException;
import org.commonjava.indy.audit.ChangeSummary;
import org.commonjava.indy.core.ctl.ContentController;
import org.commonjava.indy.data.ArtifactStoreQuery;
import org.commonjava.indy.data.IndyDataException;
import org.commonjava.indy.data.StoreDataManager;
import org.commonjava.indy.folo.ctl.FoloConstants;
import org.commonjava.indy.folo.model.TrackingKey;
import org.commonjava.indy.httprox.conf.HttproxConfig;
import org.commonjava.indy.httprox.handler.AbstractProxyRepositoryCreator;
import org.commonjava.indy.httprox.handler.ProxyCreationResult;
import org.commonjava.indy.httprox.handler.ProxyRepositoryCreator;
import org.commonjava.indy.metrics.conf.IndyMetricsConfig;
import org.commonjava.indy.model.core.AccessChannel;
import org.commonjava.indy.model.core.ArtifactStore;
import org.commonjava.indy.model.core.Group;
import org.commonjava.indy.model.core.HostedRepository;
import org.commonjava.indy.model.core.RemoteRepository;
import org.commonjava.indy.model.core.StoreType;
import org.commonjava.indy.subsys.http.util.UserPass;
import org.commonjava.indy.util.ApplicationHeader;
import org.commonjava.indy.util.ApplicationStatus;
import org.commonjava.indy.util.UrlInfo;
import org.commonjava.maven.galley.TransferException;
import org.commonjava.maven.galley.event.EventMetadata;
import org.commonjava.maven.galley.model.Transfer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static com.codahale.metrics.MetricRegistry.name;
import static org.commonjava.indy.model.core.ArtifactStore.TRACKING_ID;
import static org.commonjava.indy.model.core.GenericPackageTypeDescriptor.GENERIC_PKG_KEY;
import static org.commonjava.maven.galley.io.SpecialPathConstants.PKG_TYPE_GENERIC_HTTP;

/**
 * Created by ruhan on 9/20/18.
 */
public class ProxyResponseHelper {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    private static final String TRACKED_USER_SUFFIX = "+tracking";

    private final HttpRequest httpRequest;

    private final ProxyRepositoryCreator repoCreator;

    private final StoreDataManager storeManager;

    private final IndyMetricsConfig metricsConfig;

    private final MetricRegistry metricRegistry;

    private final String cls;

    private final HttproxConfig config;

    private final ContentController contentController;

    private boolean transferred;

    public ProxyResponseHelper(HttpRequest httpRequest, HttproxConfig config, ContentController contentController,
            ProxyRepositoryCreator repoCreator, StoreDataManager storeManager, IndyMetricsConfig metricsConfig,
            MetricRegistry metricRegistry, String cls) {
        this.httpRequest = httpRequest;
        this.config = config;
        this.contentController = contentController;
        this.repoCreator = repoCreator;
        this.storeManager = storeManager;
        this.metricsConfig = metricsConfig;
        this.metricRegistry = metricRegistry;
        this.cls = cls;
    }

    public ArtifactStore getArtifactStore(String trackingId, final URL url) throws IndyDataException {
        if (metricsConfig == null || metricRegistry == null) {
            return doGetArtifactStore(trackingId, url);
        }

        Timer timer = metricRegistry.timer(name(metricsConfig.getNodePrefix(), cls, "getArtifactStore"));
        Timer.Context timerContext = timer.time();
        try {
            return doGetArtifactStore(trackingId, url);
        } finally {
            timerContext.stop();
        }
    }

    private ArtifactStore doGetArtifactStore(String trackingId, final URL url) throws IndyDataException {
        int port = getPort(url);

        if (trackingId != null) {
            String groupName = repoCreator.formatId(url.getHost(), port, 0, trackingId, StoreType.group);

            ArtifactStoreQuery<Group> query = storeManager.query().packageType(GENERIC_PKG_KEY)
                    .storeType(Group.class);

            Group group = query.getGroup(groupName);
            logger.debug("Get httproxy group, group: {}", group);

            if (group == null) {
                logger.debug(
                        "Creating repositories (group, hosted, remote) for HTTProx request: {}, trackingId: {}",
                        url, trackingId);
                ProxyCreationResult result = createRepo(trackingId, url, null);
                group = result.getGroup();
            }
            return group;
        } else {
            RemoteRepository remote;
            final String baseUrl = getBaseUrl(url, false);

            ArtifactStoreQuery<RemoteRepository> query = storeManager.query().packageType(GENERIC_PKG_KEY)
                    .storeType(RemoteRepository.class);

            remote = query.stream()
                    .filter(store -> store.getUrl().equals(baseUrl) && store.getMetadata(TRACKING_ID) == null)
                    .findFirst().orElse(null);

            logger.debug("Get httproxy remote, remote: {}", remote);
            if (remote == null) {
                logger.debug("Creating remote repository for HTTProx request: {}", url);
                String name = getRemoteRepositoryName(url);
                ProxyCreationResult result = createRepo(null, url, name);
                remote = result.getRemote();
            }
            return remote;
        }
    }

    /**
     * Create repositories (group, remote, hosted) when trackingId is present. Otherwise create normal remote
     * repository with specified name.
     *
     * @param trackingId
     * @param url
     * @param name distinct remote repository name. null if trackingId is given
     */
    private ProxyCreationResult createRepo(String trackingId, URL url, String name) throws IndyDataException {
        UrlInfo info = new UrlInfo(url.toExternalForm());

        UserPass up = UserPass.parse(ApplicationHeader.authorization, httpRequest, url.getAuthority());
        String baseUrl = getBaseUrl(url, false);

        logger.debug(">>>> Create repo: trackingId=" + trackingId + ", name=" + name);
        ProxyCreationResult result = repoCreator.create(trackingId, name, baseUrl, info, up,
                LoggerFactory.getLogger(repoCreator.getClass()));
        ChangeSummary changeSummary = new ChangeSummary(ChangeSummary.SYSTEM_USER,
                "Creating HTTProx proxy for: " + info.getUrl());

        RemoteRepository remote = result.getRemote();
        if (remote != null) {
            storeManager.storeArtifactStore(remote, changeSummary, false, true, new EventMetadata());
        }

        HostedRepository hosted = result.getHosted();
        if (hosted != null) {
            storeManager.storeArtifactStore(hosted, changeSummary, false, true, new EventMetadata());
        }

        Group group = result.getGroup();
        if (group != null) {
            storeManager.storeArtifactStore(group, changeSummary, false, true, new EventMetadata());
        }

        return result;
    }

    /**
     * if repo with this name already exists, we need to use a different name
     */
    private String getRemoteRepositoryName(URL url) throws IndyDataException {
        final String name = repoCreator.formatId(url.getHost(), getPort(url), 0, null, StoreType.remote);

        logger.debug("Looking for remote repo starts with name: {}", name);

        AbstractProxyRepositoryCreator abstractProxyRepositoryCreator = null;
        if (repoCreator instanceof AbstractProxyRepositoryCreator) {
            abstractProxyRepositoryCreator = (AbstractProxyRepositoryCreator) repoCreator;
        }

        if (abstractProxyRepositoryCreator == null) {
            return name;
        }

        Predicate<ArtifactStore> filter = abstractProxyRepositoryCreator.getNameFilter(name);
        List<String> l = storeManager.query().packageType(GENERIC_PKG_KEY).storeType(RemoteRepository.class)
                .stream(filter).map(repository -> repository.getName()).collect(Collectors.toList());

        if (l.isEmpty()) {
            return name;
        }
        return abstractProxyRepositoryCreator.getNextName(l);
    }

    private int getPort(URL url) {
        int port = url.getPort();
        if (port < 1) {
            port = url.getDefaultPort();
        }
        return port;
    }

    private String getBaseUrl(URL url, boolean includeDefaultPort) {
        int port = getPort(url);
        String portStr;
        if (includeDefaultPort || port != url.getDefaultPort()) {
            portStr = ":" + port;
        } else {
            portStr = "";
        }
        return String.format("%s://%s%s/", url.getProtocol(), url.getHost(), portStr);
    }

    public void transfer(final HttpConduitWrapper http, final ArtifactStore store, final String path,
            final boolean writeBody, final UserPass proxyUserPass) throws IOException, IndyWorkflowException {
        if (metricsConfig == null || metricRegistry == null) {
            doTransfer(http, store, path, writeBody, proxyUserPass);
            return;
        }

        Timer timer = metricRegistry.timer(name(metricsConfig.getNodePrefix(), cls, "transfer"));
        Timer.Context timerContext = timer.time();
        try {
            doTransfer(http, store, path, writeBody, proxyUserPass);
        } finally {
            timerContext.stop();
        }
    }

    private void doTransfer(final HttpConduitWrapper http, final ArtifactStore store, final String path,
            final boolean writeBody, final UserPass proxyUserPass) throws IOException, IndyWorkflowException {
        if (transferred) {
            return;
        }

        transferred = true;
        if (!http.isOpen()) {
            throw new IOException("Sink channel already closed (or null)!");
        }

        final EventMetadata eventMetadata = createEventMetadata(writeBody, proxyUserPass, path, store);

        Transfer txfr = null;
        try {
            txfr = contentController.get(store.getKey(), path, eventMetadata);
        } catch (final IndyWorkflowException e) {
            if (!(e.getCause() instanceof TransferException)) {
                throw e;
            }
            logger.debug("Suppressed exception for further handling inside proxy logic:", e);
        }

        if (txfr != null && txfr.exists()) {
            http.writeExistingTransfer(txfr, writeBody, path, eventMetadata);
        } else {
            http.writeNotFoundTransfer(store, path);
        }
    }

    private EventMetadata createEventMetadata(final boolean writeBody, final UserPass proxyUserPass,
            final String path, final ArtifactStore store) throws IndyWorkflowException {
        final EventMetadata eventMetadata = new EventMetadata();
        if (writeBody) {
            TrackingKey tk = getTrackingKey(proxyUserPass);

            if (tk != null) {
                logger.debug("TRACKING {} in {} (KEY: {})", path, store, tk);
                eventMetadata.set(FoloConstants.TRACKING_KEY, tk);

                eventMetadata.set(FoloConstants.ACCESS_CHANNEL, AccessChannel.GENERIC_PROXY);
            } else {
                logger.debug("NOT TRACKING: {} in {}", path, store);
            }
        } else {
            logger.debug("NOT TRACKING non-body request: {} in {}", path, store);
        }

        eventMetadata.setPackageType(PKG_TYPE_GENERIC_HTTP);

        return eventMetadata;
    }

    public TrackingKey getTrackingKey(UserPass proxyUserPass) throws IndyWorkflowException {
        TrackingKey tk = null;
        switch (config.getTrackingType()) {
        case ALWAYS: {
            if (proxyUserPass == null) {
                throw new IndyWorkflowException(ApplicationStatus.BAD_REQUEST.code(),
                        "Tracking is always-on, but no username was provided! Cannot initialize tracking key.");
            }

            tk = new TrackingKey(proxyUserPass.getUser());

            break;
        }
        case SUFFIX: {
            if (proxyUserPass != null) {
                final String user = proxyUserPass.getUser();

                if (user != null && user.endsWith(TRACKED_USER_SUFFIX)
                        && user.length() > TRACKED_USER_SUFFIX.length()) {
                    tk = new TrackingKey(StringUtils.substring(user, 0, -TRACKED_USER_SUFFIX.length()));
                }
            }

            break;
        }
        default: {
        }
        }
        return tk;
    }

}