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            case OVERWRITE_TOMBSTONE:
063                return EventType.RESOURCE_CREATION;
064            case UPDATE:
065            case UPDATE_HEADERS:
066                return EventType.RESOURCE_MODIFICATION;
067            case DELETE:
068                return EventType.RESOURCE_DELETION;
069            case PURGE:
070                return EventType.RESOURCE_PURGE;
071            case FOLLOW:
072                return EventType.INBOUND_REFERENCE;
073            default:
074                throw new IllegalStateException(
075                        String.format("There is no EventType mapping for ResourceOperation type %s on operation %s",
076                                operation.getType(), operation));
077        }
078    }
079
080    private ResourceOperationEventBuilder() {
081        // Intentionally left blank
082    }
083
084    @Override
085    public EventBuilder merge(final EventBuilder other) {
086        if (other == null) {
087            return this;
088        }
089
090        if (!(other instanceof ResourceOperationEventBuilder)) {
091            throw new IllegalStateException(
092                    String.format("Cannot merge EventBuilders because they are different types <%s> and <%s>",
093                            this.getClass(), other.getClass()));
094        }
095
096        final var otherCast = (ResourceOperationEventBuilder) other;
097
098        if (!this.fedoraId.equals(otherCast.fedoraId)) {
099            throw new IllegalStateException(
100                    String.format("Cannot merge events because they are for different resources: <%s> and <%s>",
101                            this, otherCast));
102        }
103
104        this.types.addAll(otherCast.types);
105        this.resourceTypes.addAll(otherCast.resourceTypes);
106
107        if (this.date.isBefore(otherCast.date)) {
108            this.date = otherCast.date;
109        }
110
111        return this;
112    }
113
114    @Override
115    public EventBuilder withResourceTypes(final Set<String> resourceTypes) {
116        this.resourceTypes = Objects.requireNonNullElse(resourceTypes, new HashSet<>());
117        return this;
118    }
119
120    @Override
121    public EventBuilder withBaseUrl(final String baseUrl) {
122        this.baseUrl = baseUrl;
123        return this;
124    }
125
126    @Override
127    public EventBuilder withUserAgent(final String userAgent) {
128        this.userAgent = userAgent;
129        return this;
130    }
131
132    @Override
133    public Event build() {
134        URI userUri = null;
135        if (userID != null) {
136            userUri = UserUtil.getUserURI(userID, userAgentBaseUri);
137        }
138        return new EventImpl(fedoraId, types, resourceTypes, userID, userUri, userAgent, baseUrl, date);
139    }
140
141    @Override
142    public String toString() {
143        return "ResourceOperationEventBuilder{" +
144                "fedoraId=" + fedoraId +
145                ", types=" + types +
146                ", resourceTypes=" + resourceTypes +
147                ", userID='" + userID + '\'' +
148                ", userAgent='" + userAgent + '\'' +
149                ", baseUrl='" + baseUrl + '\'' +
150                ", date=" + date +
151                '}';
152    }
153
154}