001/* 002 * The contents of this file are subject to the license and copyright 003 * detailed in the LICENSE and NOTICE files at the root of the source 004 * tree. 005 */ 006 007package org.fcrepo.kernel.impl.observer; 008 009import static com.google.common.base.Preconditions.checkNotNull; 010 011import java.net.URI; 012import java.util.Collections; 013import java.util.Map; 014import java.util.Set; 015import java.util.concurrent.ConcurrentHashMap; 016import java.util.stream.Collectors; 017 018import javax.inject.Inject; 019 020import org.fcrepo.config.AuthPropsConfig; 021import org.fcrepo.kernel.api.Transaction; 022import org.fcrepo.kernel.api.identifiers.FedoraId; 023import org.fcrepo.kernel.api.models.ResourceFactory; 024import org.fcrepo.kernel.api.observer.EventAccumulator; 025import org.fcrepo.kernel.api.operations.ResourceOperation; 026 027import org.slf4j.Logger; 028import org.slf4j.LoggerFactory; 029import org.springframework.stereotype.Component; 030 031import com.google.common.collect.Multimap; 032import com.google.common.collect.MultimapBuilder; 033import com.google.common.eventbus.EventBus; 034 035/** 036 * @author pwinckles 037 */ 038@Component 039public class EventAccumulatorImpl implements EventAccumulator { 040 041 private final static Logger LOG = LoggerFactory.getLogger(EventAccumulatorImpl.class); 042 043 private final Map<String, Multimap<FedoraId, EventBuilder>> transactionEventMap; 044 045 @Inject 046 private ResourceFactory resourceFactory; 047 048 @Inject 049 private EventBus eventBus; 050 051 @Inject 052 private AuthPropsConfig authPropsConfig; 053 054 public EventAccumulatorImpl() { 055 this.transactionEventMap = new ConcurrentHashMap<>(); 056 } 057 058 @Override 059 public void recordEventForOperation(final Transaction transaction, final FedoraId fedoraId, 060 final ResourceOperation operation) { 061 checkNotNull(transaction, "transaction cannot be blank"); 062 checkNotNull(fedoraId, "fedoraId cannot be null"); 063 064 final String transactionId = transaction.getId(); 065 final var events = transactionEventMap.computeIfAbsent(transactionId, key -> 066 MultimapBuilder.hashKeys().arrayListValues().build()); 067 final var eventBuilder = ResourceOperationEventBuilder.fromResourceOperation( 068 fedoraId, operation, authPropsConfig.getUserAgentBaseUri()); 069 events.put(fedoraId, eventBuilder); 070 } 071 072 @Override 073 public void emitEvents(final Transaction transaction, final String baseUrl, final String userAgent) { 074 LOG.debug("Emitting events for transaction {}", transaction.getId()); 075 076 final var eventMap = transactionEventMap.remove(transaction.getId()); 077 078 if (eventMap != null) { 079 eventMap.keySet().forEach(fedoraId -> { 080 final var events = eventMap.get(fedoraId); 081 082 try { 083 final var mergedBuilder = events.stream() 084 .reduce(EventBuilder::merge).get(); 085 086 final var event = mergedBuilder 087 .withResourceTypes(loadResourceTypes(transaction, fedoraId)) 088 .withBaseUrl(baseUrl) 089 .withUserAgent(userAgent) 090 .build(); 091 092 LOG.debug("Emitting event: {}", event); 093 eventBus.post(event); 094 } catch (final Exception e) { 095 LOG.error("Failed to emit events: {}", events, e); 096 } 097 }); 098 } 099 } 100 101 @Override 102 public void clearEvents(final Transaction transaction) { 103 LOG.trace("Clearing events for transaction {}", transaction.getId()); 104 transactionEventMap.remove(transaction.getId()); 105 } 106 107 private Set<String> loadResourceTypes(final Transaction transaction, final FedoraId fedoraId) { 108 try { 109 return resourceFactory.getResource(transaction, fedoraId).getTypes().stream() 110 .map(URI::toString) 111 .collect(Collectors.toSet()); 112 } catch (final Exception e) { 113 LOG.debug("Could not load resource types for {}", fedoraId, e); 114 // This can happen if the resource no longer exists 115 return Collections.emptySet(); 116 } 117 } 118 119}