org.commonjava.aprox.depgraph.rest.RepositoryController.java Source code

Java tutorial

Introduction

Here is the source code for org.commonjava.aprox.depgraph.rest.RepositoryController.java

Source

/**
 * Copyright (C) 2011 Red Hat, Inc. (jdcasey@commonjava.org)
 *
 * 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.aprox.depgraph.rest;

import static org.apache.commons.io.IOUtils.closeQuietly;
import static org.apache.commons.io.IOUtils.copy;
import static org.apache.commons.lang.StringUtils.join;
import static org.commonjava.maven.galley.util.UrlUtils.buildUrl;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;

import org.commonjava.aprox.AproxWorkflowException;
import org.commonjava.aprox.depgraph.conf.AproxDepgraphConfig;
import org.commonjava.aprox.depgraph.dto.PathsDTO;
import org.commonjava.aprox.depgraph.dto.WebOperationConfigDTO;
import org.commonjava.aprox.depgraph.util.ConfigDTOHelper;
import org.commonjava.aprox.model.core.StoreKey;
import org.commonjava.aprox.model.galley.CacheOnlyLocation;
import org.commonjava.aprox.model.galley.KeyedLocation;
import org.commonjava.aprox.util.ApplicationStatus;
import org.commonjava.aprox.util.UriFormatter;
import org.commonjava.maven.atlas.graph.rel.ProjectRelationship;
import org.commonjava.maven.atlas.ident.ref.ArtifactRef;
import org.commonjava.maven.atlas.ident.ref.ProjectVersionRef;
import org.commonjava.maven.cartographer.data.CartoDataException;
import org.commonjava.maven.cartographer.ops.ResolveOps;
import org.commonjava.maven.cartographer.preset.PresetSelector;
import org.commonjava.maven.cartographer.util.ProjectVersionRefComparator;
import org.commonjava.maven.galley.TransferException;
import org.commonjava.maven.galley.TransferManager;
import org.commonjava.maven.galley.model.ConcreteResource;
import org.commonjava.maven.galley.model.Transfer;
import org.commonjava.maven.galley.model.TransferBatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

@ApplicationScoped
public class RepositoryController {

    private static final String URLMAP_DATA_REPO_URL = "repoUrl";

    private static final String URLMAP_DATA_FILES = "files";

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

    @Inject
    private ResolveOps ops;

    @Inject
    private ObjectMapper serializer;

    @Inject
    private TransferManager transferManager;

    @Inject
    private PresetSelector presets;

    @Inject
    private AproxDepgraphConfig config;

    @Inject
    private ConfigDTOHelper configHelper;

    public String getUrlMap(final InputStream configStream, final String baseUri, final UriFormatter uriFormatter)
            throws AproxWorkflowException {
        final WebOperationConfigDTO dto = configHelper.readWebOperationDTO(configStream);
        return getUrlMap(dto, baseUri, uriFormatter);
    }

    public String getUrlMap(final String json, final String baseUri, final UriFormatter uriFormatter)
            throws AproxWorkflowException {
        final WebOperationConfigDTO dto = configHelper.readWebOperationDTO(json);
        return getUrlMap(dto, baseUri, uriFormatter);
    }

    private String getUrlMap(final WebOperationConfigDTO dto, final String baseUri, final UriFormatter uriFormatter)
            throws AproxWorkflowException {
        final Map<ProjectVersionRef, Map<String, Object>> result = new LinkedHashMap<ProjectVersionRef, Map<String, Object>>();

        try {

            final Map<ProjectVersionRef, Map<ArtifactRef, ConcreteResource>> contents = resolveContents(dto);

            final List<ProjectVersionRef> topKeys = new ArrayList<ProjectVersionRef>(contents.keySet());
            Collections.sort(topKeys, new ProjectVersionRefComparator());

            for (final ProjectVersionRef gav : topKeys) {
                final Map<ArtifactRef, ConcreteResource> items = contents.get(gav);

                final Map<String, Object> data = new HashMap<String, Object>();
                result.put(gav, data);

                final Set<String> files = new HashSet<String>();
                KeyedLocation kl = null;

                for (final ConcreteResource item : items.values()) {
                    final KeyedLocation loc = (KeyedLocation) item.getLocation();

                    // FIXME: we're squashing some potential variation in the locations here!
                    // if we're not looking for local urls, allow any cache-only location to be overridden...
                    if (kl == null || (!dto.getLocalUrls() && (kl instanceof CacheOnlyLocation))) {
                        kl = loc;
                    }

                    logger.info("Adding {} (keyLocation: {})", item, kl);
                    files.add(new File(item.getPath()).getName());
                }

                final List<String> sortedFiles = new ArrayList<String>(files);
                Collections.sort(sortedFiles);
                data.put(URLMAP_DATA_REPO_URL,
                        formatUrlMapRepositoryUrl(kl, dto.getLocalUrls(), baseUri, uriFormatter));
                data.put(URLMAP_DATA_FILES, sortedFiles);
            }
        } catch (final MalformedURLException e) {
            throw new AproxWorkflowException("Failed to generate runtime repository. Reason: {}", e,
                    e.getMessage());
        }

        try {
            return serializer.writeValueAsString(result);
        } catch (final JsonProcessingException e) {
            throw new AproxWorkflowException("Failed to serialize to JSON: %s", e, e.getMessage());
        }
    }

    public String getPaths(final InputStream configStream, final UriFormatter uriFormatter)
            throws AproxWorkflowException {
        final PathsDTO dto = configHelper.readPathsDTO(configStream);
        return getPaths(dto, uriFormatter);
    }

    public String getPaths(final String json, final UriFormatter uriFormatter) throws AproxWorkflowException {
        final PathsDTO dto = configHelper.readPathsDTO(json);
        return getPaths(dto, uriFormatter);
    }

    private String getPaths(final PathsDTO dto, final UriFormatter uriFormatter) throws AproxWorkflowException {
        final List<List<ProjectRelationship<ProjectVersionRef>>> paths;

        paths = resolvePaths(dto);

        Collections.sort(paths, new Comparator<List<ProjectRelationship<ProjectVersionRef>>>() {

            @Override
            public int compare(final List<ProjectRelationship<ProjectVersionRef>> o1,
                    final List<ProjectRelationship<ProjectVersionRef>> o2) {
                int result = 0;

                int i = 0;
                ProjectVersionRefComparator cmp = new ProjectVersionRefComparator();
                while (result == 0 && i < o1.size() && i < o2.size()) {
                    ProjectRelationship<ProjectVersionRef> rel1 = o1.get(i);
                    ProjectRelationship<ProjectVersionRef> rel2 = o2.get(i);
                    i++;

                    result = cmp.compare(rel1.getDeclaring(), rel2.getDeclaring());
                    if (result == 0) {
                        result = cmp.compare(rel1.getTarget(), rel2.getTarget());
                    }
                }

                if (result == 0) {
                    if (o1.size() < o2.size()) {
                        result = -1;
                    } else if (o1.size() > o2.size()) {
                        result = 1;
                    }
                }

                return result;
            }

        });

        Map<ProjectVersionRef, List<List<ProjectRelationship<ProjectVersionRef>>>> result = new HashMap<>();
        for (List<ProjectRelationship<ProjectVersionRef>> path : paths) {
            final ProjectVersionRef leaf = path.get(path.size() - 1).getTarget().asProjectVersionRef();
            if (!result.containsKey(leaf)) {
                result.put(leaf, new ArrayList<List<ProjectRelationship<ProjectVersionRef>>>());
            }
            List<List<ProjectRelationship<ProjectVersionRef>>> pathList = result.get(leaf);
            pathList.add(path);
        }

        try {
            return serializer.writeValueAsString(result);
        } catch (final JsonProcessingException e) {
            throw new AproxWorkflowException("Failed to serialize to JSON: %s", e, e.getMessage());
        }
    }

    public String getDownloadLog(final InputStream configStream, final String baseUri,
            final UriFormatter uriFormatter) throws AproxWorkflowException {
        final WebOperationConfigDTO dto = configHelper.readWebOperationDTO(configStream);
        return getDownloadLog(dto, baseUri, uriFormatter);
    }

    public String getDownloadLog(final String json, final String baseUri, final UriFormatter uriFormatter)
            throws AproxWorkflowException {
        final WebOperationConfigDTO dto = configHelper.readWebOperationDTO(json);
        return getDownloadLog(dto, baseUri, uriFormatter);
    }

    public String getDownloadLog(final WebOperationConfigDTO dto, final String baseUri,
            final UriFormatter uriFormatter) throws AproxWorkflowException {
        final Set<String> downLog = new HashSet<String>();
        try {
            final Map<ProjectVersionRef, Map<ArtifactRef, ConcreteResource>> contents = resolveContents(dto);

            final List<ProjectVersionRef> refs = new ArrayList<ProjectVersionRef>(contents.keySet());
            Collections.sort(refs);

            for (final ProjectVersionRef ref : refs) {
                final Map<ArtifactRef, ConcreteResource> items = contents.get(ref);
                for (final ConcreteResource item : items.values()) {
                    logger.info("Adding: '{}'", item);
                    downLog.add(formatDownlogEntry(item, dto.getLocalUrls(), baseUri, uriFormatter));
                }
            }
        } catch (final MalformedURLException e) {
            throw new AproxWorkflowException("Failed to generate runtime repository. Reason: {}", e,
                    e.getMessage());
        }

        final List<String> sorted = new ArrayList<String>(downLog);
        Collections.sort(sorted);

        return join(sorted, "\n");
    }

    public void getZipRepository(final InputStream configStream, final OutputStream zipStream)
            throws AproxWorkflowException {
        final WebOperationConfigDTO dto = configHelper.readWebOperationDTO(configStream);
        getZipRepository(dto, zipStream);
    }

    public void getZipRepository(final String json, final OutputStream zipStream) throws AproxWorkflowException {
        final WebOperationConfigDTO dto = configHelper.readWebOperationDTO(json);
        getZipRepository(dto, zipStream);
    }

    public void getZipRepository(final WebOperationConfigDTO dto, final OutputStream zipStream)
            throws AproxWorkflowException {
        ZipOutputStream stream = null;
        try {
            final Map<ProjectVersionRef, Map<ArtifactRef, ConcreteResource>> contents = resolveContents(dto);

            final Set<ConcreteResource> entries = new HashSet<ConcreteResource>();
            final Set<String> seenPaths = new HashSet<String>();

            logger.info("Iterating contents with {} GAVs.", contents.size());
            for (final Map<ArtifactRef, ConcreteResource> artifactResources : contents.values()) {
                for (final Entry<ArtifactRef, ConcreteResource> entry : artifactResources.entrySet()) {
                    final ArtifactRef ref = entry.getKey();
                    final ConcreteResource resource = entry.getValue();

                    //                        logger.info( "Checking {} ({}) for inclusion...", ref, resource );

                    final String path = resource.getPath();
                    if (seenPaths.contains(path)) {
                        logger.warn("Conflicting path: {}. Skipping {}.", path, ref);
                        continue;
                    }

                    seenPaths.add(path);

                    //                        logger.info( "Adding to batch: {} via resource: {}", ref, resource );
                    entries.add(resource);
                }
            }

            logger.info("Starting batch retrieval of {} artifacts.", entries.size());
            TransferBatch batch = new TransferBatch(entries);
            batch = transferManager.batchRetrieve(batch);

            logger.info("Retrieved {} artifacts. Creating zip.", batch.getTransfers().size());

            // FIXME: Stream to a temp file, then pass that to the Response.ok() handler...
            stream = new ZipOutputStream(zipStream);

            final List<Transfer> items = new ArrayList<Transfer>(batch.getTransfers().values());
            Collections.sort(items, new Comparator<Transfer>() {
                @Override
                public int compare(final Transfer f, final Transfer s) {
                    return f.getPath().compareTo(s.getPath());
                }
            });

            for (final Transfer item : items) {
                //                    logger.info( "Adding: {}", item );
                final String path = item.getPath();
                if (item != null) {
                    final ZipEntry ze = new ZipEntry(path);
                    stream.putNextEntry(ze);

                    InputStream itemStream = null;
                    try {
                        itemStream = item.openInputStream();
                        copy(itemStream, stream);
                    } finally {
                        closeQuietly(itemStream);
                    }
                }
            }
        } catch (final IOException e) {
            throw new AproxWorkflowException("Failed to generate runtime repository. Reason: {}", e,
                    e.getMessage());
        } catch (final TransferException e) {
            throw new AproxWorkflowException("Failed to generate runtime repository. Reason: {}", e,
                    e.getMessage());
        } finally {
            closeQuietly(stream);
        }
    }

    private String formatDownlogEntry(final ConcreteResource item, final boolean localUrls, final String baseUri,
            final UriFormatter uriFormatter) throws MalformedURLException {
        final KeyedLocation kl = (KeyedLocation) item.getLocation();
        final StoreKey key = kl.getKey();

        if (localUrls || kl instanceof CacheOnlyLocation) {
            final String uri = uriFormatter.formatAbsolutePathTo(baseUri, key.getType().singularEndpointName(),
                    key.getName());
            return String.format("Downloading: %s", uri);
        } else {
            return "Downloading: " + buildUrl(item.getLocation().getUri(), item.getPath());
        }
    }

    private String formatUrlMapRepositoryUrl(final KeyedLocation kl, final boolean localUrls, final String baseUri,
            final UriFormatter uriFormatter) throws MalformedURLException {
        if (localUrls || kl instanceof CacheOnlyLocation) {
            final StoreKey key = kl.getKey();
            return uriFormatter.formatAbsolutePathTo(baseUri, key.getType().singularEndpointName(), key.getName());
        } else {
            return kl.getUri();
        }
    }

    private Map<ProjectVersionRef, Map<ArtifactRef, ConcreteResource>> resolveContents(
            final WebOperationConfigDTO dto) throws AproxWorkflowException {
        if (dto == null) {
            logger.warn("Repository archive configuration is missing.");
            throw new AproxWorkflowException(ApplicationStatus.BAD_REQUEST.code(),
                    "JSON configuration not supplied");
        }

        dto.resolveFilters(presets, config.getDefaultWebFilterPreset());

        if (!dto.isValid()) {
            logger.warn("Repository archive configuration is invalid: {}", dto);
            throw new AproxWorkflowException(ApplicationStatus.BAD_REQUEST.code(), "Invalid configuration: {}",
                    dto);
        }

        Map<ProjectVersionRef, Map<ArtifactRef, ConcreteResource>> contents;
        try {
            contents = ops.resolveRepositoryContents(dto);
        } catch (final CartoDataException e) {
            logger.error(
                    String.format("Failed to resolve repository contents for: %s. Reason: %s", dto, e.getMessage()),
                    e);
            throw new AproxWorkflowException("Failed to resolve repository contents for: {}. Reason: {}", e, dto,
                    e.getMessage());
        }

        return contents;
    }

    private List<List<ProjectRelationship<ProjectVersionRef>>> resolvePaths(PathsDTO dto)
            throws AproxWorkflowException {
        if (dto == null) {
            logger.warn("Repository archive configuration is missing.");
            throw new AproxWorkflowException(ApplicationStatus.BAD_REQUEST.code(),
                    "JSON configuration not supplied");
        }

        // TODO implement for more than one graph
        dto.resolveFilters(presets, config.getDefaultWebFilterPreset());

        if (!dto.isValid()) {
            logger.warn("Repository archive configuration is invalid: {}", dto);
            throw new AproxWorkflowException(ApplicationStatus.BAD_REQUEST.code(), "Invalid configuration: {}",
                    dto);
        }

        final List<List<ProjectRelationship<ProjectVersionRef>>> discoveredPaths;
        try {
            discoveredPaths = ops.resolvePaths(dto, dto.getTargets());
        } catch (final CartoDataException ex) {
            logger.error(String.format("Failed to resolve paths for: %s. Reason: %s", dto, ex.getMessage()), ex);
            throw new AproxWorkflowException("Failed to resolve paths for: {}. Reason: {}", ex, dto,
                    ex.getMessage());
        }

        return discoveredPaths;
    }

}