Skip to content

Commit

Permalink
feature(attachments): enable viewing/editing of attachment usages, lo…
Browse files Browse the repository at this point in the history
…ck used attachments from deletion, automatically save attachment usages on source

code bundle generation

closes #153
  • Loading branch information
alexbrdn committed Aug 3, 2018
1 parent 7ffab39 commit dd7025a
Show file tree
Hide file tree
Showing 40 changed files with 1,328 additions and 320 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,78 @@

package org.eclipse.sw360.datahandler.db;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import org.eclipse.sw360.datahandler.thrift.SW360Exception;
import org.eclipse.sw360.datahandler.thrift.Source;
import org.eclipse.sw360.datahandler.thrift.attachments.Attachment;
import org.eclipse.sw360.datahandler.thrift.attachments.CheckStatus;
import org.eclipse.sw360.datahandler.thrift.components.Component;
import org.eclipse.sw360.datahandler.thrift.components.Release;
import org.eclipse.sw360.datahandler.thrift.projects.Project;
import org.ektorp.http.HttpClient;

import java.net.MalformedURLException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static org.eclipse.sw360.datahandler.common.CommonUtils.nullToEmptyCollection;
import static org.eclipse.sw360.datahandler.common.CommonUtils.nullToEmptySet;

public abstract class AttachmentAwareDatabaseHandler {

public Set<Attachment> getAllAttachmentsToKeep(Set<Attachment> originalAttachments, Set<Attachment> changedAttachments) {
Set <Attachment> attachmentsToKeep = new HashSet<>();
attachmentsToKeep.addAll(nullToEmptySet(changedAttachments));
Set<String> alreadyPresentIdsInAttachmentsToKeep = nullToEmptyCollection(attachmentsToKeep).stream().map(Attachment::getAttachmentContentId).collect(Collectors.toSet());
protected AttachmentDatabaseHandler attachmentDatabaseHandler;

// prevent deletion of already accepted attachments
Set<Attachment> acceptedAttachmentsNotYetToKeep = nullToEmptySet(originalAttachments).stream().filter(a -> (a.getCheckStatus() == CheckStatus.ACCEPTED && !alreadyPresentIdsInAttachmentsToKeep.contains(a.getAttachmentContentId()))).collect(Collectors.toSet());
attachmentsToKeep.addAll(acceptedAttachmentsNotYetToKeep);
protected AttachmentAwareDatabaseHandler(AttachmentDatabaseHandler attachmentDatabaseHandler) {
this.attachmentDatabaseHandler = attachmentDatabaseHandler;
}

protected AttachmentAwareDatabaseHandler(Supplier<HttpClient> httpClient, String dbName, String attachmentDbName) throws MalformedURLException {
this(new AttachmentDatabaseHandler(httpClient, dbName, attachmentDbName));
}

protected Source toSource(Release release){
return Source.releaseId(release.getId());
}

protected Source toSource(Component component){
return Source.releaseId(component.getId());
}

return attachmentsToKeep;
protected Source toSource(Project project){
return Source.releaseId(project.getId());
}

public Set<Attachment> getAllAttachmentsToKeep(Source owner, Set<Attachment> originalAttachments, Set<Attachment> changedAttachments) {
Map<String, Attachment> attachmentsToKeep = nullToEmptySet(changedAttachments).stream()
.collect(Collectors.toMap(Attachment::getAttachmentContentId, a -> a));
Set<Attachment> actualAttachments = nullToEmptySet(originalAttachments);

// prevent deletion of already accepted attachments
Set<Attachment> checkedActualAttachments = actualAttachments.stream()
.filter(a -> (a.getCheckStatus() == CheckStatus.ACCEPTED)).collect(Collectors.toSet());

checkedActualAttachments.forEach(a -> attachmentsToKeep.putIfAbsent(a.getAttachmentContentId(), a));

public AttachmentAwareDatabaseHandler() {
// prevent deletion of used attachments
Set<String> attachmentContentIds = actualAttachments.stream().map(Attachment::getAttachmentContentId).collect(Collectors.toSet());
ImmutableMap<Source, Set<String>> usageSearchParameter = ImmutableMap.of(owner, attachmentContentIds);
Map<Map<Source, String>, Integer> attachmentUsageCount = attachmentDatabaseHandler.getAttachmentUsageCount(usageSearchParameter, null);
Set<Attachment> usedActualAttachments = actualAttachments.stream()
.filter(attachment -> attachmentUsageCount.getOrDefault(ImmutableMap.of(owner, attachment.getAttachmentContentId()), 0) > 0)
.collect(Collectors.toSet());

usedActualAttachments.forEach(a -> attachmentsToKeep.putIfAbsent(a.getAttachmentContentId(), a));

return new HashSet<>(attachmentsToKeep.values());
}

protected void deleteAttachmentUsagesOfUnlinkedReleases(Source usedBy, Set<String> updatedLinkedReleaseIds, Set<String> actualLinkedReleaseIds) throws SW360Exception {
Sets.SetView<String> deletedLinkedReleaseIds = Sets.difference(actualLinkedReleaseIds, updatedLinkedReleaseIds);
Set<Source> owners = deletedLinkedReleaseIds.stream().map(Source::releaseId).collect(Collectors.toSet());
attachmentDatabaseHandler.deleteUsagesBy(usedBy, owners);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,37 @@
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.sw360.attachments.db;
package org.eclipse.sw360.datahandler.db;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import org.eclipse.sw360.datahandler.common.CommonUtils;
import org.eclipse.sw360.datahandler.couchdb.AttachmentConnector;
import org.eclipse.sw360.datahandler.couchdb.DatabaseConnector;
import org.eclipse.sw360.datahandler.thrift.RequestStatus;
import org.eclipse.sw360.datahandler.thrift.RequestSummary;
import org.eclipse.sw360.datahandler.thrift.SW360Exception;
import org.eclipse.sw360.datahandler.thrift.Source;
import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentContent;
import org.eclipse.sw360.datahandler.thrift.attachments.DatabaseAddress;
import org.eclipse.sw360.datahandler.thrift.attachments.AttachmentUsage;
import org.eclipse.sw360.datahandler.thrift.attachments.UsageData;
import org.eclipse.sw360.datahandler.thrift.users.User;
import org.apache.log4j.Logger;
import org.apache.thrift.TException;
import org.ektorp.BulkDeleteDocument;
import org.ektorp.DocumentOperationResult;
import org.ektorp.http.HttpClient;

import java.net.MalformedURLException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static org.eclipse.sw360.datahandler.common.Duration.durationOf;
import static org.eclipse.sw360.datahandler.common.SW360Assert.assertNotNull;
import static org.eclipse.sw360.datahandler.common.SW360Assert.assertUser;
import static org.eclipse.sw360.datahandler.thrift.ThriftValidate.validateAttachment;

/**
Expand All @@ -44,13 +50,15 @@ public class AttachmentDatabaseHandler {
private final DatabaseConnector db;
private final AttachmentRepository repository;
private final AttachmentConnector attachmentConnector;
private final AttachmentUsageRepository attachmentUsageRepository;

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

public AttachmentDatabaseHandler(Supplier<HttpClient> httpClient, String attachmentDbName) throws MalformedURLException {
public AttachmentDatabaseHandler(Supplier<HttpClient> httpClient, String dbName, String attachmentDbName) throws MalformedURLException {
db = new DatabaseConnector(httpClient, attachmentDbName);
attachmentConnector = new AttachmentConnector(httpClient, attachmentDbName, durationOf(30, TimeUnit.SECONDS));
repository = new AttachmentRepository(db);
attachmentUsageRepository = new AttachmentUsageRepository(new DatabaseConnector(httpClient, dbName));
}

public AttachmentConnector getAttachmentConnector(){
Expand Down Expand Up @@ -78,7 +86,7 @@ public AttachmentContent getAttachmentContent(String id) throws TException {
public void updateAttachmentContent(AttachmentContent attachment) throws TException {
attachmentConnector.updateAttachmentContent(attachment);
}
public RequestSummary bulkDelete(List<String> ids) throws TException {
public RequestSummary bulkDelete(List<String> ids) {
final List<DocumentOperationResult> documentOperationResults = repository.deleteIds(ids);
return CommonUtils.getRequestSummary(ids, documentOperationResults);
}
Expand All @@ -88,10 +96,136 @@ public RequestStatus deleteAttachmentContent(String attachmentId) throws TExcept
return RequestStatus.SUCCESS;
}
public RequestSummary vacuumAttachmentDB(User user, Set<String> usedIds) throws TException {
assertUser(user);
return repository.vacuumAttachmentDB(user, usedIds);
}
public String getSha1FromAttachmentContentId(String attachmentContentId){
return attachmentConnector.getSha1FromAttachmentContentId(attachmentContentId);
}

public void deleteUsagesBy(Source usedBy) throws SW360Exception {
List<AttachmentUsage> existingUsages = attachmentUsageRepository.getUsedAttachments(usedBy.getFieldValue().toString());
if (!existingUsages.isEmpty()) {
deleteAttachmentUsages(existingUsages);
}
}

public void deleteUsagesBy(Source usedBy, Set<Source> owners) throws SW360Exception {
List<AttachmentUsage> existingUsages = attachmentUsageRepository.getUsedAttachments(usedBy.getFieldValue().toString());
List<AttachmentUsage> usagesToDelete = existingUsages.stream()
.filter(usage -> owners.contains(usage.getOwner()))
.collect(Collectors.toList());

if (!usagesToDelete.isEmpty()) {
deleteAttachmentUsages(usagesToDelete);
}
}


public void deleteAttachmentUsagesByUsageDataTypes(Source usedBy, Set<UsageData._Fields> typesToReplace, boolean deleteWithEmptyType) throws TException {
List<AttachmentUsage> existingUsages = attachmentUsageRepository.getUsedAttachments(usedBy.getFieldValue().toString());
List<AttachmentUsage> usagesToDelete = existingUsages.stream().filter(usage -> {
if (usage.isSetUsageData()) {
return typesToReplace.contains(usage.getUsageData().getSetField());
} else {
return deleteWithEmptyType;
}
}).collect(Collectors.toList());

if (!usagesToDelete.isEmpty()) {
deleteAttachmentUsages(usagesToDelete);
}
}

public AttachmentUsage makeAttachmentUsage(AttachmentUsage attachmentUsage) {
attachmentUsageRepository.add(attachmentUsage);
return attachmentUsage;
}

public void makeAttachmentUsages(List<AttachmentUsage> attachmentUsages) throws TException {
List<DocumentOperationResult> results = attachmentUsageRepository.executeBulk(attachmentUsages);
if (!results.isEmpty()) {
throw new SW360Exception("Some of the usage documents could not be created: " + results);
}
}

public AttachmentUsage getAttachmentUsage(String id) throws TException {
AttachmentUsage attachmentUsage = attachmentUsageRepository.get(id);
assertNotNull(attachmentUsage);

return attachmentUsage;

}

public AttachmentUsage updateAttachmentUsage(AttachmentUsage attachmentUsage) {
attachmentUsageRepository.update(attachmentUsage);
return attachmentUsage;
}

public void updateAttachmentUsages(List<AttachmentUsage> attachmentUsages) throws TException {
List<DocumentOperationResult> results = attachmentUsageRepository.executeBulk(attachmentUsages);
if (!results.isEmpty()) {
throw new SW360Exception("Some of the usage documents could not be updated: " + results);
}
}

public void deleteAttachmentUsage(AttachmentUsage attachmentUsage) {
attachmentUsageRepository.remove(attachmentUsage);
}

public void deleteAttachmentUsages(List<AttachmentUsage> attachmentUsages) throws SW360Exception {
List<DocumentOperationResult> results = attachmentUsageRepository.executeBulk(
attachmentUsages.stream().map(BulkDeleteDocument::of).collect(Collectors.toList()));
if (!results.isEmpty()) {
throw new SW360Exception("Some of the usage documents could not be deleted: " + results);
}
}

public List<AttachmentUsage> getAttachmentUsages(Source owner, String attachmentContentId, UsageData filter) {
if (filter != null && filter.isSet()) {
return attachmentUsageRepository.getUsageForAttachment(owner.getFieldValue().toString(), attachmentContentId,
filter.getSetField().toString());
} else {
return attachmentUsageRepository.getUsageForAttachment(owner.getFieldValue().toString(), attachmentContentId);
}
}

public List<AttachmentUsage> getAttachmentUsages(Source owner, Set<String> attachmentContentIds, UsageData filter) {
Map<String, Set<String>> attContentIdsByOwnerId = ImmutableMap.of(owner.getFieldValue().toString(), attachmentContentIds);

if (filter != null && filter.isSet()) {
return attachmentUsageRepository.getUsageForAttachments(attContentIdsByOwnerId, filter.getSetField().toString());
} else {
return attachmentUsageRepository.getUsageForAttachments(attContentIdsByOwnerId, null);
}
}

public List<AttachmentUsage> getUsedAttachments(Source usedBy, UsageData filter) {
if (filter != null && filter.isSet()) {
return attachmentUsageRepository.getUsedAttachments(usedBy.getFieldValue().toString(), filter.getSetField().toString());
} else {
return attachmentUsageRepository.getUsedAttachments(usedBy.getFieldValue().toString());
}
}

public Map<Map<Source, String>, Integer> getAttachmentUsageCount(Map<Source, Set<String>> attachments, UsageData filter) {
Map<String, Source._Fields> idToType = Maps.newHashMap();
Map<String, Set<String>> queryFor = attachments.entrySet().stream()
.collect(Collectors.toMap(entry -> {
String sourceId = entry.getKey().getFieldValue().toString();
idToType.put(sourceId, entry.getKey().getSetField());
return sourceId;
}, Map.Entry::getValue));

Map<Map<String, String>, Integer> results;
if (filter != null && filter.isSet()) {
results = attachmentUsageRepository.getAttachmentUsageCount(queryFor, filter.getSetField().toString());
} else {
results = attachmentUsageRepository.getAttachmentUsageCount(queryFor, null);
}

return results.entrySet().stream().collect(Collectors.toMap(entry -> {
Map.Entry<String, String> key = entry.getKey().entrySet().iterator().next();
return ImmutableMap.of(new Source(idToType.get(key.getKey()), key.getKey()), key.getValue());
}, Map.Entry::getValue));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* http://www.eclipse.org/legal/epl-v10.html
*/

package org.eclipse.sw360.attachments.db;
package org.eclipse.sw360.datahandler.db;

import org.eclipse.sw360.datahandler.couchdb.DatabaseConnector;
import org.eclipse.sw360.datahandler.couchdb.DatabaseRepository;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright Siemens AG, 2017. Part of the SW360 Portal Project.
* Copyright Siemens AG, 2017-2018. Part of the SW360 Portal Project.
*
* SPDX-License-Identifier: EPL-1.0
*
Expand All @@ -9,7 +9,7 @@
* http://www.eclipse.org/legal/epl-v10.html
*/

package org.eclipse.sw360.attachments.db;
package org.eclipse.sw360.datahandler.db;

import com.fasterxml.jackson.databind.node.ArrayNode;
import com.google.common.base.Strings;
Expand All @@ -22,6 +22,7 @@
import org.ektorp.ViewQuery;
import org.ektorp.ViewResult;
import org.ektorp.support.View;
import org.jetbrains.annotations.NotNull;

import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -64,13 +65,35 @@ public List<AttachmentUsage> getUsedAttachments(String usedById, String filter)
}

public Map<Map<String, String>, Integer> getAttachmentUsageCount(Map<String, Set<String>> attachments, String filter) {
ViewQuery viewQuery = createUsagesByAttachmentQuery(filter);
List<ComplexKey> complexKeys = prepareKeys(attachments, filter);
ViewResult result = getConnector().queryView(viewQuery.reduce(true).group(true).keys(complexKeys));
// result is: { ..., rows: [ { key: [ "releaseId", "attachmentId" ], value: 3 }, ... ] }
return result.getRows().stream().collect(Collectors.toMap(row -> {
ArrayNode key = (ArrayNode) row.getKeyAsNode();
return ImmutableMap.of(key.get(0).asText(), key.get(1).asText());
}, row -> row.getValueAsInt()));
}

public List<AttachmentUsage> getUsageForAttachments(Map<String, Set<String>> attachments, String filter) {
ViewQuery viewQuery = createUsagesByAttachmentQuery(filter);
viewQuery.includeDocs(true).reduce(false);
List<ComplexKey> complexKeys = prepareKeys(attachments, filter);
return queryView(viewQuery.keys(complexKeys));
}

private ViewQuery createUsagesByAttachmentQuery(String filter) {
ViewQuery viewQuery;
if (Strings.isNullOrEmpty(filter)) {
viewQuery = createQuery("usagesByAttachment");
} else {
viewQuery = createQuery("usagesByAttachmentUsageType");
}
return viewQuery;
}

@NotNull
private List<ComplexKey> prepareKeys(Map<String, Set<String>> attachments, String filter) {
List<ComplexKey> complexKeys = Lists.newArrayList();
for(Entry<String, Set<String>> entry : attachments.entrySet()) {
for(String attachmentId: entry.getValue()) {
Expand All @@ -81,12 +104,6 @@ public Map<Map<String, String>, Integer> getAttachmentUsageCount(Map<String, Set
}
}
}

ViewResult result = getConnector().queryView(viewQuery.reduce(true).group(true).keys(complexKeys));
// result is: { ..., rows: [ { key: [ "releaseId", "attachmentId" ], value: 3 }, ... ] }
return result.getRows().stream().collect(Collectors.toMap(row -> {
ArrayNode key = (ArrayNode) row.getKeyAsNode();
return ImmutableMap.of(key.get(0).asText(), key.get(1).asText());
}, row -> row.getValueAsInt()));
return complexKeys;
}
}
Loading

0 comments on commit dd7025a

Please sign in to comment.