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 org.fcrepo.kernel.api.identifiers.FedoraId;
010import org.fcrepo.kernel.api.observer.Event;
011import org.fcrepo.kernel.api.observer.EventType;
012import org.fcrepo.kernel.api.operations.ResourceOperation;
013import org.fcrepo.kernel.impl.util.UserUtil;
014
015import java.net.URI;
016import java.time.Instant;
017import java.util.HashSet;
018import java.util.Objects;
019import java.util.Set;
020
021/**
022 * Converts a ResourceOperation into an Event.
023 *
024 * @author pwinckles
025 */
026public class ResourceOperationEventBuilder implements EventBuilder {
027
028    private FedoraId fedoraId;
029    private Set<EventType> types;
030    private Set<String> resourceTypes;
031    private String userID;
032    private String userAgent;
033    private String baseUrl;
034    private Instant date;
035    private String userAgentBaseUri;
036
037    /**
038     * Creates a new EventBuilder based on an ResourceOperation
039     *
040     * @param fedoraId the FedoraId the operation is on
041     * @param operation the ResourceOperation to create an event for
042     * @param userAgentBaseUri the base uri of the user agent, optional
043     * @return new builder
044     */
045    public static ResourceOperationEventBuilder fromResourceOperation(final FedoraId fedoraId,
046                                                                      final ResourceOperation operation,
047                                                                      final String userAgentBaseUri) {
048        final var builder = new ResourceOperationEventBuilder();
049        builder.fedoraId = fedoraId;
050        builder.date = Instant.now();
051        builder.resourceTypes = new HashSet<>();
052        builder.userID = operation.getUserPrincipal();
053        builder.types = new HashSet<>();
054        builder.types.add(mapOperationToEventType(operation));
055        builder.userAgentBaseUri = userAgentBaseUri;
056        return builder;
057    }
058
059    private static EventType mapOperationToEventType(final ResourceOperation operation) {
060        switch (operation.getType()) {
061            case CREATE:
062                return EventType.RESOURCE_CREATION;
063            case UPDATE:
064            case UPDATE_HEADERS:
065                return EventType.RESOURCE_MODIFICATION;
066            case DELETE:
067                return EventType.RESOURCE_DELETION;
068            case PURGE:
069                return EventType.RESOURCE_PURGE;
070            case FOLLOW:
071                return EventType.INBOUND_REFERENCE;
072            default:
073                throw new IllegalStateException(
074                        String.format("There is no EventType mapping for ResourceOperation type %s on operation %s",
075                                operation.getType(), operation));
076        }
077    }
078
079    private ResourceOperationEventBuilder() {
080        // Intentionally left blank
081    }
082
083    @Override
084    public EventBuilder merge(final EventBuilder other) {
085        if (other == null) {
086            return this;
087        }
088
089        if (!(other instanceof ResourceOperationEventBuilder)) {
090            throw new IllegalStateException(
091                    String.format("Cannot merge EventBuilders because they are different types <%s> and <%s>",
092                            this.getClass(), other.getClass()));
093        }
094
095        final var otherCast = (ResourceOperationEventBuilder) other;
096
097        if (!this.fedoraId.equals(otherCast.fedoraId)) {
098            throw new IllegalStateException(
099                    String.format("Cannot merge events because they are for different resources: <%s> and <%s>",
100                            this, otherCast));
101        }
102
103        this.types.addAll(otherCast.types);
104        this.resourceTypes.addAll(otherCast.resourceTypes);
105
106        if (this.date.isBefore(otherCast.date)) {
107            this.date = otherCast.date;
108        }
109
110        return this;
111    }
112
113    @Override
114    public EventBuilder withResourceTypes(final Set<String> resourceTypes) {
115        this.resourceTypes = Objects.requireNonNullElse(resourceTypes, new HashSet<>());
116        return this;
117    }
118
119    @Override
120    public EventBuilder withBaseUrl(final String baseUrl) {
121        this.baseUrl = baseUrl;
122        return this;
123    }
124
125    @Override
126    public EventBuilder withUserAgent(final String userAgent) {
127        this.userAgent = userAgent;
128        return this;
129    }
130
131    @Override
132    public Event build() {
133        URI userUri = null;
134        if (userID != null) {
135            userUri = UserUtil.getUserURI(userID, userAgentBaseUri);
136        }
137        return new EventImpl(fedoraId, types, resourceTypes, userID, userUri, userAgent, baseUrl, date);
138    }
139
140    @Override
141    public String toString() {
142        return "ResourceOperationEventBuilder{" +
143                "fedoraId=" + fedoraId +
144                ", types=" + types +
145                ", resourceTypes=" + resourceTypes +
146                ", userID='" + userID + '\'' +
147                ", userAgent='" + userAgent + '\'' +
148                ", baseUrl='" + baseUrl + '\'' +
149                ", date=" + date +
150                '}';
151    }
152
153}