001/* 002 * Licensed to DuraSpace under one or more contributor license agreements. 003 * See the NOTICE file distributed with this work for additional information 004 * regarding copyright ownership. 005 * 006 * DuraSpace licenses this file to you under the Apache License, 007 * Version 2.0 (the "License"); you may not use this file except in 008 * compliance with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.fcrepo.kernel.impl.observer; 020 021import com.google.common.collect.Multimap; 022import com.google.common.collect.MultimapBuilder; 023import com.google.common.eventbus.EventBus; 024 025import org.fcrepo.config.AuthPropsConfig; 026import org.fcrepo.kernel.api.identifiers.FedoraId; 027import org.fcrepo.kernel.api.models.ResourceFactory; 028import org.fcrepo.kernel.api.observer.EventAccumulator; 029import org.fcrepo.kernel.api.operations.ResourceOperation; 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032import org.springframework.stereotype.Component; 033 034import javax.inject.Inject; 035import java.net.URI; 036import java.util.Collections; 037import java.util.Map; 038import java.util.Set; 039import java.util.concurrent.ConcurrentHashMap; 040import java.util.stream.Collectors; 041 042import static com.google.common.base.Preconditions.checkNotNull; 043import static com.google.common.base.Strings.emptyToNull; 044 045/** 046 * @author pwinckles 047 */ 048@Component 049public class EventAccumulatorImpl implements EventAccumulator { 050 051 private final static Logger LOG = LoggerFactory.getLogger(EventAccumulatorImpl.class); 052 053 private final Map<String, Multimap<FedoraId, EventBuilder>> transactionEventMap; 054 055 @Inject 056 private ResourceFactory resourceFactory; 057 058 @Inject 059 private EventBus eventBus; 060 061 @Inject 062 private AuthPropsConfig authPropsConfig; 063 064 public EventAccumulatorImpl() { 065 this.transactionEventMap = new ConcurrentHashMap<>(); 066 } 067 068 @Override 069 public void recordEventForOperation(final String transactionId, final FedoraId fedoraId, 070 final ResourceOperation operation) { 071 checkNotNull(emptyToNull(transactionId), "transactionId cannot be blank"); 072 checkNotNull(fedoraId, "fedoraId cannot be null"); 073 074 final var events = transactionEventMap.computeIfAbsent(transactionId, key -> 075 MultimapBuilder.hashKeys().arrayListValues().build()); 076 final var eventBuilder = ResourceOperationEventBuilder.fromResourceOperation( 077 fedoraId, operation, authPropsConfig.getUserAgentBaseUri()); 078 events.put(fedoraId, eventBuilder); 079 } 080 081 @Override 082 public void emitEvents(final String transactionId, final String baseUrl, final String userAgent) { 083 LOG.debug("Emitting events for transaction {}", transactionId); 084 085 final var eventMap = transactionEventMap.remove(transactionId); 086 087 if (eventMap != null) { 088 eventMap.keySet().forEach(fedoraId -> { 089 final var events = eventMap.get(fedoraId); 090 091 try { 092 final var mergedBuilder = events.stream() 093 .reduce(EventBuilder::merge).get(); 094 095 final var event = mergedBuilder 096 .withResourceTypes(loadResourceTypes(fedoraId)) 097 .withBaseUrl(baseUrl) 098 .withUserAgent(userAgent) 099 .build(); 100 101 LOG.debug("Emitting event: {}", event); 102 eventBus.post(event); 103 } catch (final Exception e) { 104 LOG.error("Failed to emit events: {}", events, e); 105 } 106 }); 107 } 108 } 109 110 @Override 111 public void clearEvents(final String transactionId) { 112 LOG.trace("Clearing events for transaction {}", transactionId); 113 transactionEventMap.remove(transactionId); 114 } 115 116 private Set<String> loadResourceTypes(final FedoraId fedoraId) { 117 try { 118 return resourceFactory.getResource(fedoraId).getTypes().stream() 119 .map(URI::toString) 120 .collect(Collectors.toSet()); 121 } catch (final Exception e) { 122 LOG.debug("Could not load resource types for {}", fedoraId, e); 123 // This can happen if the resource no longer exists 124 return Collections.emptySet(); 125 } 126 } 127 128}