com.siemens.sw360.licenseinfo.LicenseInfoHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.siemens.sw360.licenseinfo.LicenseInfoHandler.java

Source

/*
 * Copyright Siemens AG, 2016. Part of the SW360 Portal Project.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package com.siemens.sw360.licenseinfo;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.siemens.sw360.attachments.db.AttachmentDatabaseHandler;
import com.siemens.sw360.datahandler.common.DatabaseSettings;
import com.siemens.sw360.datahandler.db.ComponentDatabaseHandler;
import com.siemens.sw360.datahandler.db.ProjectDatabaseHandler;
import com.siemens.sw360.datahandler.thrift.SW360Exception;
import com.siemens.sw360.datahandler.thrift.attachments.Attachment;
import com.siemens.sw360.datahandler.thrift.components.Release;
import com.siemens.sw360.datahandler.thrift.licenseinfo.LicenseInfo;
import com.siemens.sw360.datahandler.thrift.licenseinfo.LicenseInfoParsingResult;
import com.siemens.sw360.datahandler.thrift.licenseinfo.LicenseInfoRequestStatus;
import com.siemens.sw360.datahandler.thrift.licenseinfo.LicenseInfoService;
import com.siemens.sw360.datahandler.thrift.projects.Project;
import com.siemens.sw360.datahandler.thrift.projects.ProjectLink;
import com.siemens.sw360.datahandler.thrift.projects.ProjectRelationship;
import com.siemens.sw360.datahandler.thrift.users.User;
import com.siemens.sw360.licenseinfo.parsers.AttachmentContentProvider;
import com.siemens.sw360.licenseinfo.parsers.CLIParser;
import com.siemens.sw360.licenseinfo.parsers.LicenseInfoParser;
import com.siemens.sw360.licenseinfo.parsers.SPDXParser;
import org.apache.log4j.Logger;
import org.apache.thrift.TException;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;

import java.io.StringWriter;
import java.net.MalformedURLException;
import java.util.*;
import java.util.stream.Collectors;

import static com.siemens.sw360.datahandler.common.CommonUtils.*;
import static com.siemens.sw360.datahandler.common.SW360Assert.assertId;
import static com.siemens.sw360.datahandler.common.SW360Assert.assertNotNull;
import static com.siemens.sw360.datahandler.common.SW360Utils.flattenProjectLinkTree;

/**
 * Implementation of the Thrift service
 *
 * @author alex.borodin@evosoft.com
 */
public class LicenseInfoHandler implements LicenseInfoService.Iface {

    public static final String LICENSE_INFO_RESULTS_CONTEXT_PROPERTY = "licenseInfoResults";
    public static final String LICENSES_CONTEXT_PROPERTY = "licenses";
    public static final String LICENSE_INFO_TEMPLATE_FILE = "licenseInfoFile.vm";

    private final LicenseInfoParser[] parsers;

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

    private final ProjectDatabaseHandler projectDatabaseHandler;

    private final ComponentDatabaseHandler componentDatabaseHandler;

    public LicenseInfoHandler() throws MalformedURLException {
        this(new AttachmentDatabaseHandler(DatabaseSettings.COUCH_DB_URL, DatabaseSettings.COUCH_DB_DATABASE,
                DatabaseSettings.COUCH_DB_ATTACHMENTS),
                new ProjectDatabaseHandler(DatabaseSettings.COUCH_DB_URL, DatabaseSettings.COUCH_DB_DATABASE,
                        DatabaseSettings.COUCH_DB_ATTACHMENTS),
                new ComponentDatabaseHandler(DatabaseSettings.COUCH_DB_URL, DatabaseSettings.COUCH_DB_DATABASE,
                        DatabaseSettings.COUCH_DB_ATTACHMENTS));
    }

    @VisibleForTesting
    public LicenseInfoHandler(AttachmentDatabaseHandler attachmentDatabaseHandler,
            ProjectDatabaseHandler projectDatabaseHandler, ComponentDatabaseHandler componentDatabaseHandler)
            throws MalformedURLException {
        this.projectDatabaseHandler = projectDatabaseHandler;
        this.componentDatabaseHandler = componentDatabaseHandler;

        AttachmentContentProvider contentProvider = attachment -> attachmentDatabaseHandler
                .getAttachmentContent(attachment.getAttachmentContentId());

        parsers = new LicenseInfoParser[] {
                new SPDXParser(attachmentDatabaseHandler.getAttachmentConnector(), contentProvider),
                new CLIParser(attachmentDatabaseHandler.getAttachmentConnector(), contentProvider), };
    }

    @Override
    public LicenseInfoParsingResult getLicenseInfoForAttachment(Attachment attachment) throws TException {
        assertNotNull(attachment);
        for (LicenseInfoParser parser : parsers) {
            if (parser.isApplicableTo(attachment)) {
                return parser.getLicenseInfo(attachment);
            }
        }
        return noSourceParsingResult();
    }

    @Override
    public LicenseInfoParsingResult getLicenseInfoForRelease(Release release) throws TException {
        if (release == null) {
            return noSourceParsingResult();
        }

        try {
            Optional<LicenseInfoParser> parserOptional = Arrays.asList(parsers).stream().filter(p -> {
                try {
                    return !getApplicableAttachments(release, p).isEmpty();
                } catch (TException e) {
                    throw new UncheckedTException(e);
                }
            }).findFirst();

            if (parserOptional.isPresent()) {
                LicenseInfoParsingResult result = getApplicableAttachments(release, parserOptional.get()).stream()
                        .map(attachment -> {
                            try {
                                return parserOptional.get().getLicenseInfo(attachment);
                            } catch (TException e) {
                                throw new UncheckedTException(e);
                            }
                        }).reduce(this::mergeLicenseInfos)
                        //this could only be returned if there is a parser which found applicable sources,
                        // but there are no attachments in the release. That would be so inexplicable as to warrant
                        // throwing an exception, actually.
                        .orElse(noSourceParsingResult());
                return assignReleaseToLicenseInfoParsingResult(result, release);
            } else {
                // not a single parser has found applicable attachments
                return assignReleaseToLicenseInfoParsingResult(noSourceParsingResult(), release);
            }
        } catch (UncheckedTException e) {
            throw e.getTExceptionCause();
        }
    }

    private List<Attachment> getApplicableAttachments(Release release, LicenseInfoParser parser) throws TException {
        try {
            return nullToEmptySet(release.getAttachments()).stream().filter((attachmentContent) -> {
                try {
                    return parser.isApplicableTo(attachmentContent);
                } catch (TException e) {
                    throw new UncheckedTException(e);
                }
            }).collect(Collectors.toList());
        } catch (UncheckedTException e) {
            throw e.getTExceptionCause();
        }
    }

    @Override
    public String getLicenseInfoFileForProject(String projectId, User user) throws TException {
        assertId(projectId);
        Project project = projectDatabaseHandler.getProjectById(projectId, user);
        assertNotNull(project);

        Collection<LicenseInfoParsingResult> projectLicenseInfoResults = getAllReleaseLicenseInfos(projectId, user);

        return generateLicenseInfoFile(projectLicenseInfoResults);
    }

    public Collection<LicenseInfoParsingResult> getAllReleaseLicenseInfos(String projectId, User user)
            throws TException {
        Map<String, ProjectRelationship> fakeRelations = Maps.newHashMap();
        fakeRelations.put(projectId, ProjectRelationship.UNKNOWN);
        List<ProjectLink> linkedProjects = projectDatabaseHandler.getLinkedProjects(fakeRelations);
        Collection<ProjectLink> flatProjectLinkList = flattenProjectLinkTree(linkedProjects);
        try {
            return flatProjectLinkList.stream()
                    .flatMap(pl -> nullToEmptyCollection(pl.getLinkedReleases()).stream()).map(rl -> {
                        try {
                            return componentDatabaseHandler.getRelease(rl.getId(), user);
                        } catch (SW360Exception e) {
                            log.error("Cannot read release with id: " + rl.getId(), e);
                            return null;
                        }
                    }).filter(Objects::nonNull)
                    // public LicenseInfoParsingResult getLicenseInfoForRelease(Release release) throws TException {
                    .map((release) -> {
                        try {
                            return getLicenseInfoForRelease(release);
                        } catch (TException e) {
                            throw new UncheckedTException(e);
                        }
                    }).collect(Collectors.toList());
        } catch (UncheckedTException e) {
            throw e.getTExceptionCause();
        }
    }

    private LicenseInfoParsingResult mergeLicenseInfos(LicenseInfoParsingResult lir1,
            LicenseInfoParsingResult lir2) {
        if (lir1.getStatus() != LicenseInfoRequestStatus.SUCCESS) {
            return lir2;
        }
        if (lir2.getStatus() != LicenseInfoRequestStatus.SUCCESS) {
            return lir1;
        }
        if (!lir1.isSetLicenseInfo() || !lir2.isSetLicenseInfo()
                || !lir1.getLicenseInfo().getFiletype().equals(lir2.getLicenseInfo().getFiletype())) {
            throw new IllegalArgumentException("LicenseInfo filetypes must be equal");
        }

        if (!Objects.equals(lir1.getName(), lir2.getName()) || !Objects.equals(lir1.getVendor(), lir2.getVendor())
                || !Objects.equals(lir1.getVersion(), lir2.getVersion())) {
            throw new IllegalArgumentException(
                    "Method is not intended to merge across releases. Release data must be equal");
        }
        //use copy constructor to copy filetype
        LicenseInfo mergedLi = new LicenseInfo(lir1.getLicenseInfo());
        //merging filenames
        Set<String> filenames = new HashSet<>();
        filenames.addAll(nullToEmptyList(lir1.getLicenseInfo().getFilenames()));
        filenames.addAll(nullToEmptyList(lir2.getLicenseInfo().getFilenames()));
        mergedLi.setFilenames(filenames.stream().collect(Collectors.toList()));
        //merging copyrights
        mergedLi.setCopyrights(Sets.union(nullToEmptySet(lir1.getLicenseInfo().getCopyrights()),
                nullToEmptySet(lir2.getLicenseInfo().getCopyrights())));
        //merging licenses
        mergedLi.setLicenseTexts(Sets.union(nullToEmptySet(lir1.getLicenseInfo().getLicenseTexts()),
                nullToEmptySet(lir2.getLicenseInfo().getLicenseTexts())));

        //use copy constructor to copy vendor, name, and version
        return new LicenseInfoParsingResult(lir1).setStatus(LicenseInfoRequestStatus.SUCCESS)
                .setLicenseInfo(mergedLi)
                .setMessage((nullToEmptyString(lir1.getMessage()) + "\n" + nullToEmptyString(lir2.getMessage()))
                        .trim());
    }

    private String generateLicenseInfoFile(Collection<LicenseInfoParsingResult> projectLicenseInfoResults)
            throws SW360Exception {
        try {
            Properties p = new Properties();
            p.setProperty("resource.loader", "class");
            p.setProperty("class.resource.loader.class",
                    "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
            Velocity.init(p);
            VelocityContext vc = new VelocityContext();

            Map<String, LicenseInfoParsingResult> licenseInfos = projectLicenseInfoResults.stream()
                    .collect(Collectors.toMap(this::getComponentLongName, li -> li, (li1, li2) -> li1));
            Set<String> licenses = projectLicenseInfoResults.stream().map(LicenseInfoParsingResult::getLicenseInfo)
                    .filter(Objects::nonNull).map(LicenseInfo::getLicenseTexts).filter(Objects::nonNull)
                    .reduce(Sets::union).orElse(Collections.emptySet());

            vc.put(LICENSE_INFO_RESULTS_CONTEXT_PROPERTY, licenseInfos);
            vc.put(LICENSES_CONTEXT_PROPERTY, licenses);

            StringWriter sw = new StringWriter();
            Velocity.mergeTemplate(LICENSE_INFO_TEMPLATE_FILE, "utf-8", vc, sw);
            sw.close();
            return sw.toString();
        } catch (Exception e) {
            log.error("Could not generate licenseinfo file", e);
            return "License information could not be generated.\nAn exception occured: " + e.toString();
        }
    }

    private String getComponentLongName(LicenseInfoParsingResult li) {
        return String.format("%s %s %s", li.getVendor(), li.getName(), li.getVersion()).trim();
    }

    private LicenseInfoParsingResult noSourceParsingResult() {
        return new LicenseInfoParsingResult().setStatus(LicenseInfoRequestStatus.NO_APPLICABLE_SOURCE);
    }

    private LicenseInfoParsingResult assignReleaseToLicenseInfoParsingResult(LicenseInfoParsingResult result,
            Release release) {
        result.setVendor(release.isSetVendor() ? release.getVendor().getShortname() : "");
        result.setName(release.getName());
        result.setVersion(release.getVersion());
        return result;
    }

    private static class UncheckedTException extends RuntimeException {
        public UncheckedTException(TException te) {
            super(te);
        }

        TException getTExceptionCause() {
            return (TException) getCause();
        }
    }
}